From 79c761eeb4932e473af91dcc6c34206e37688ce2 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 15:41:26 +0000 Subject: [PATCH 01/64] add db types --- coderd/database/dump.sql | 9 ++++- .../000050_workspace_app_health.down.sql | 4 ++ .../000050_workspace_app_health.up.sql | 4 ++ coderd/database/models.go | 37 +++++++++++++++---- coderd/database/queries.sql.go | 15 +++++--- codersdk/workspaceapps.go | 9 +++++ 6 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 coderd/database/migrations/000050_workspace_app_health.down.sql create mode 100644 coderd/database/migrations/000050_workspace_app_health.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 81557a9022d00..04894a1cf2ead 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -88,6 +88,12 @@ CREATE TYPE user_status AS ENUM ( 'suspended' ); +CREATE TYPE workspace_app_health AS ENUM ( + 'intializing', + 'healthy', + 'unhealthy' +); + CREATE TYPE workspace_transition AS ENUM ( 'start', 'stop', @@ -342,7 +348,8 @@ CREATE TABLE workspace_apps ( icon character varying(256) NOT NULL, command character varying(65534), url character varying(65534), - relative_path boolean DEFAULT false NOT NULL + relative_path boolean DEFAULT false NOT NULL, + health workspace_app_health DEFAULT 'intializing'::public.workspace_app_health NOT NULL ); CREATE TABLE workspace_builds ( diff --git a/coderd/database/migrations/000050_workspace_app_health.down.sql b/coderd/database/migrations/000050_workspace_app_health.down.sql new file mode 100644 index 0000000000000..65042c8759f56 --- /dev/null +++ b/coderd/database/migrations/000050_workspace_app_health.down.sql @@ -0,0 +1,4 @@ +ALTER TABLE ONLY workspace_apps + DROP COLUMN IF EXISTS health; + +DROP TYPE workspace_app_health; diff --git a/coderd/database/migrations/000050_workspace_app_health.up.sql b/coderd/database/migrations/000050_workspace_app_health.up.sql new file mode 100644 index 0000000000000..e9a360e3c6be9 --- /dev/null +++ b/coderd/database/migrations/000050_workspace_app_health.up.sql @@ -0,0 +1,4 @@ +CREATE TYPE workspace_app_health AS ENUM ('intializing', 'healthy', 'unhealthy'); + +ALTER TABLE ONLY workspace_apps + ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'intializing'; diff --git a/coderd/database/models.go b/coderd/database/models.go index b5d48bf6c0c32..a56898c8101fd 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -312,6 +312,26 @@ func (e *UserStatus) Scan(src interface{}) error { return nil } +type WorkspaceAppHealth string + +const ( + WorkspaceAppHealthIntializing WorkspaceAppHealth = "intializing" + WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy" + WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy" +) + +func (e *WorkspaceAppHealth) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = WorkspaceAppHealth(s) + case string: + *e = WorkspaceAppHealth(s) + default: + return fmt.Errorf("unsupported scan type for WorkspaceAppHealth: %T", src) + } + return nil +} + type WorkspaceTransition string const ( @@ -575,14 +595,15 @@ type WorkspaceAgent struct { } type WorkspaceApp struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - Name string `db:"name" json:"name"` - Icon string `db:"icon" json:"icon"` - Command sql.NullString `db:"command" json:"command"` - Url sql.NullString `db:"url" json:"url"` - RelativePath bool `db:"relative_path" json:"relative_path"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + Name string `db:"name" json:"name"` + Icon string `db:"icon" json:"icon"` + Command sql.NullString `db:"command" json:"command"` + Url sql.NullString `db:"url" json:"url"` + RelativePath bool `db:"relative_path" json:"relative_path"` + Health WorkspaceAppHealth `db:"health" json:"health"` } type WorkspaceBuild struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0da956761bf12..d60f7f4fee568 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3869,12 +3869,13 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.Command, &i.Url, &i.RelativePath, + &i.Health, ) return i, err } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3895,6 +3896,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.Command, &i.Url, &i.RelativePath, + &i.Health, ); err != nil { return nil, err } @@ -3910,7 +3912,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3931,6 +3933,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.Command, &i.Url, &i.RelativePath, + &i.Health, ); err != nil { return nil, err } @@ -3946,7 +3949,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3967,6 +3970,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.Command, &i.Url, &i.RelativePath, + &i.Health, ); err != nil { return nil, err } @@ -3994,7 +3998,7 @@ INSERT INTO relative_path ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path + ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, health ` type InsertWorkspaceAppParams struct { @@ -4029,6 +4033,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.Command, &i.Url, &i.RelativePath, + &i.Health, ) return i, err } diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index d993a4dcf49ba..acca92c46cec5 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -4,6 +4,14 @@ import ( "github.com/google/uuid" ) +// type WorkspaceAppHealth string + +// const ( +// WorkspaceAppHealthInitializing = "initializing" +// WorkspaceAppHealthHealthy = "healthy" +// WorkspaceAppHealthUnhealthy = "unhealthy" +// ) + type WorkspaceApp struct { ID uuid.UUID `json:"id"` // Name is a unique identifier attached to an agent. @@ -12,4 +20,5 @@ type WorkspaceApp struct { // Icon is a relative path or external URL that specifies // an icon to be displayed in the dashboard. Icon string `json:"icon,omitempty"` + // Status WorkspaceAppHealth `json:"health"` } From 511be1375c796a863c70ca442258971039de7a59 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 15:44:36 +0000 Subject: [PATCH 02/64] add sdk types --- codersdk/workspaceapps.go | 16 ++++++++-------- site/src/api/typesGenerated.ts | 4 ++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index acca92c46cec5..7380d3360e9af 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -4,13 +4,13 @@ import ( "github.com/google/uuid" ) -// type WorkspaceAppHealth string +type WorkspaceAppHealth string -// const ( -// WorkspaceAppHealthInitializing = "initializing" -// WorkspaceAppHealthHealthy = "healthy" -// WorkspaceAppHealthUnhealthy = "unhealthy" -// ) +const ( + WorkspaceAppInitializing WorkspaceAppHealth = "initializing" + WorkspaceAppHealthy WorkspaceAppHealth = "healthy" + WorkspaceAppUnhealthy WorkspaceAppHealth = "unhealthy" +) type WorkspaceApp struct { ID uuid.UUID `json:"id"` @@ -19,6 +19,6 @@ type WorkspaceApp struct { Command string `json:"command,omitempty"` // Icon is a relative path or external URL that specifies // an icon to be displayed in the dashboard. - Icon string `json:"icon,omitempty"` - // Status WorkspaceAppHealth `json:"health"` + Icon string `json:"icon,omitempty"` + Status WorkspaceAppHealth `json:"health"` } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 192b33166ca3b..f2d33f4f3a006 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -616,6 +616,7 @@ export interface WorkspaceApp { readonly name: string readonly command?: string readonly icon?: string + readonly health: WorkspaceAppHealth } // From codersdk/workspacebuilds.go @@ -738,5 +739,8 @@ export type UserStatus = "active" | "suspended" // From codersdk/workspaceresources.go export type WorkspaceAgentStatus = "connected" | "connecting" | "disconnected" +// From codersdk/workspaceapps.go +export type WorkspaceAppHealth = "healthy" | "initializing" | "unhealthy" + // From codersdk/workspacebuilds.go export type WorkspaceTransition = "delete" | "start" | "stop" From b034b066d1889b89eaf6527f1efce21ce2ca5126 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 16:42:39 +0000 Subject: [PATCH 03/64] add postWorkspaceAppHealth route --- coderd/coderd.go | 1 + coderd/database/dump.sql | 3 +- .../000050_workspace_app_health.up.sql | 3 +- coderd/database/models.go | 1 + coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 36 +++++++-- coderd/database/queries/workspaceapps.sql | 9 +++ coderd/workspaceapps.go | 75 +++++++++++++++++++ codersdk/workspaceapps.go | 5 ++ site/src/api/typesGenerated.ts | 5 ++ 10 files changed, 132 insertions(+), 7 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index a595488687ca5..7be01e05ca482 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -412,6 +412,7 @@ func New(options *Options) *API { r.Use(httpmw.ExtractWorkspaceAgent(options.Database)) r.Get("/metadata", api.workspaceAgentMetadata) r.Post("/version", api.postWorkspaceAgentVersion) + r.Post("/app-healths", api.postWorkspaceAppHealths) r.Get("/gitsshkey", api.agentGitSSHKey) r.Get("/coordinate", api.workspaceAgentCoordinate) r.Get("/report-stats", api.workspaceAgentReportStats) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 04894a1cf2ead..bdc8c94f1f26e 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -349,7 +349,8 @@ CREATE TABLE workspace_apps ( command character varying(65534), url character varying(65534), relative_path boolean DEFAULT false NOT NULL, - health workspace_app_health DEFAULT 'intializing'::public.workspace_app_health NOT NULL + health workspace_app_health DEFAULT 'intializing'::public.workspace_app_health NOT NULL, + updated_at timestamp with time zone DEFAULT '-infinity'::timestamp with time zone NOT NULL ); CREATE TABLE workspace_builds ( diff --git a/coderd/database/migrations/000050_workspace_app_health.up.sql b/coderd/database/migrations/000050_workspace_app_health.up.sql index e9a360e3c6be9..db04128188b8b 100644 --- a/coderd/database/migrations/000050_workspace_app_health.up.sql +++ b/coderd/database/migrations/000050_workspace_app_health.up.sql @@ -1,4 +1,5 @@ CREATE TYPE workspace_app_health AS ENUM ('intializing', 'healthy', 'unhealthy'); ALTER TABLE ONLY workspace_apps - ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'intializing'; + ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'intializing', + ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity'; diff --git a/coderd/database/models.go b/coderd/database/models.go index a56898c8101fd..e144d46753362 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -604,6 +604,7 @@ type WorkspaceApp struct { Url sql.NullString `db:"url" json:"url"` RelativePath bool `db:"relative_path" json:"relative_path"` Health WorkspaceAppHealth `db:"health" json:"health"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } type WorkspaceBuild struct { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 0b38708a2497e..caf3f4ad27b55 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -149,6 +149,7 @@ type querier interface { UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error UpdateWorkspaceAgentVersionByID(ctx context.Context, arg UpdateWorkspaceAgentVersionByIDParams) error + UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index d60f7f4fee568..958bf8d71763c 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3870,12 +3870,13 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.Url, &i.RelativePath, &i.Health, + &i.UpdatedAt, ) return i, err } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3897,6 +3898,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.Url, &i.RelativePath, &i.Health, + &i.UpdatedAt, ); err != nil { return nil, err } @@ -3912,7 +3914,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3934,6 +3936,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.Url, &i.RelativePath, &i.Health, + &i.UpdatedAt, ); err != nil { return nil, err } @@ -3949,7 +3952,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3971,6 +3974,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.Url, &i.RelativePath, &i.Health, + &i.UpdatedAt, ); err != nil { return nil, err } @@ -3998,7 +4002,7 @@ INSERT INTO relative_path ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, health + ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at ` type InsertWorkspaceAppParams struct { @@ -4034,10 +4038,32 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.Url, &i.RelativePath, &i.Health, + &i.UpdatedAt, ) return i, err } +const updateWorkspaceAppHealthByID = `-- name: UpdateWorkspaceAppHealthByID :exec +UPDATE + workspace_apps +SET + updated_at = $2, + health = $3 +WHERE + id = $1 +` + +type UpdateWorkspaceAppHealthByIDParams struct { + ID uuid.UUID `db:"id" json:"id"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + Health WorkspaceAppHealth `db:"health" json:"health"` +} + +func (q *sqlQuerier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceAppHealthByID, arg.ID, arg.UpdatedAt, arg.Health) + return err +} + const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index f25fd67124187..a17b5cd2c752c 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -24,3 +24,12 @@ INSERT INTO ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; + +-- name: UpdateWorkspaceAppHealthByID :exec +UPDATE + workspace_apps +SET + updated_at = $2, + health = $3 +WHERE + id = $1; diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index a73dc184b8aa2..ae1ad64a3c3d8 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -7,9 +7,11 @@ import ( "net/http/httputil" "net/url" "strings" + "time" "github.com/go-chi/chi/v5" "go.opentelemetry.io/otel/trace" + "golang.org/x/xerrors" "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/httpapi" @@ -254,3 +256,76 @@ func (api *API) applicationCookie(authCookie *http.Cookie) *http.Cookie { appCookie.Domain = "." + api.AccessURL.Hostname() return &appCookie } + +func (api *API) postWorkspaceAppHealths(rw http.ResponseWriter, r *http.Request) { + workspaceAgent := httpmw.WorkspaceAgent(r) + var req codersdk.PostWorkspaceAppHealthsRequest + if !httpapi.Read(rw, r, &req) { + return + } + + apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Error getting agent apps", + Detail: err.Error(), + }) + return + } + + var newApps []database.WorkspaceApp + for name, health := range req.Healths { + var found *database.WorkspaceApp + for _, app := range apps { + if app.Name == name { + found = &app + } + } + if found == nil { + httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: xerrors.Errorf("workspace app name %s not found", name).Error(), + }) + return + } + + switch health { + case codersdk.WorkspaceAppInitializing: + found.Health = database.WorkspaceAppHealthIntializing + case codersdk.WorkspaceAppHealthy: + found.Health = database.WorkspaceAppHealthHealthy + case codersdk.WorkspaceAppUnhealthy: + found.Health = database.WorkspaceAppHealthUnhealthy + default: + httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: xerrors.Errorf("workspace app health %s is not a valid value", health).Error(), + }) + return + } + + // don't save if the value hasn't changed + if found.Health == database.WorkspaceAppHealth(health) { + continue + } + + newApps = append(newApps, *found) + } + + for _, app := range newApps { + api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{ + ID: app.ID, + UpdatedAt: time.Now(), + Health: app.Health, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: err.Error(), + }) + return + } + } + + httpapi.Write(rw, http.StatusOK, nil) +} diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 7380d3360e9af..dd320224dc31b 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -22,3 +22,8 @@ type WorkspaceApp struct { Icon string `json:"icon,omitempty"` Status WorkspaceAppHealth `json:"health"` } + +type PostWorkspaceAppHealthsRequest struct { + // Healths is a map of the workspace app name and the status of the app. + Healths map[string]WorkspaceAppHealth +} diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f2d33f4f3a006..fe1b95bcbe20b 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -336,6 +336,11 @@ export interface PostWorkspaceAgentVersionRequest { readonly version: string } +// From codersdk/workspaceapps.go +export interface PostWorkspaceAppHealthsRequest { + readonly Healths: Record +} + // From codersdk/provisionerdaemons.go export interface ProvisionerDaemon { readonly id: string From 419f8e71582ddd1d8ee8ad849d3ead82787250ff Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 17:34:57 +0000 Subject: [PATCH 04/64] Add more healthcheck fields to db schema --- coderd/database/databasefake/databasefake.go | 16 +++++++++ coderd/database/dump.sql | 8 +++-- .../000050_workspace_app_health.down.sql | 6 +++- .../000050_workspace_app_health.up.sql | 9 +++-- coderd/database/models.go | 24 +++++++------ coderd/database/queries.sql.go | 35 +++++++++++++------ coderd/workspaceapps.go | 14 ++++++-- codersdk/workspaceapps.go | 7 ++-- site/src/api/typesGenerated.ts | 2 +- 9 files changed, 88 insertions(+), 33 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index a63643c29e531..102229b7d4ecb 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2032,6 +2032,22 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW return workspaceApp, nil } +func (q *fakeQuerier) UpdateWorkspaceAppHealthByID(_ context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + for index, app := range q.workspaceApps { + if app.ID != arg.ID { + continue + } + app.UpdatedAt = arg.UpdatedAt + app.Health = arg.Health + q.workspaceApps[index] = app + return nil + } + return sql.ErrNoRows +} + func (q *fakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPIKeyByIDParams) error { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index bdc8c94f1f26e..5098af08619e3 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -89,6 +89,7 @@ CREATE TYPE user_status AS ENUM ( ); CREATE TYPE workspace_app_health AS ENUM ( + 'disabled', 'intializing', 'healthy', 'unhealthy' @@ -349,8 +350,11 @@ CREATE TABLE workspace_apps ( command character varying(65534), url character varying(65534), relative_path boolean DEFAULT false NOT NULL, - health workspace_app_health DEFAULT 'intializing'::public.workspace_app_health NOT NULL, - updated_at timestamp with time zone DEFAULT '-infinity'::timestamp with time zone NOT NULL + updated_at timestamp with time zone DEFAULT '-infinity'::timestamp with time zone NOT NULL, + healthcheck_enabled boolean DEFAULT false NOT NULL, + healthcheck_period integer DEFAULT 0 NOT NULL, + unhealthy_threshold integer DEFAULT 0 NOT NULL, + health workspace_app_health DEFAULT 'disabled'::public.workspace_app_health NOT NULL ); CREATE TABLE workspace_builds ( diff --git a/coderd/database/migrations/000050_workspace_app_health.down.sql b/coderd/database/migrations/000050_workspace_app_health.down.sql index 65042c8759f56..ffc8d75cb33a8 100644 --- a/coderd/database/migrations/000050_workspace_app_health.down.sql +++ b/coderd/database/migrations/000050_workspace_app_health.down.sql @@ -1,4 +1,8 @@ ALTER TABLE ONLY workspace_apps - DROP COLUMN IF EXISTS health; + DROP COLUMN IF EXISTS updated_at, + DROP COLUMN IF EXISTS healthcheck_enabled, + DROP COLUMN IF EXISTS healthcheck_period, + DROP COLUMN IF EXISTS unhealthy_threshold, + DROP COLUMN IF EXISTS health; DROP TYPE workspace_app_health; diff --git a/coderd/database/migrations/000050_workspace_app_health.up.sql b/coderd/database/migrations/000050_workspace_app_health.up.sql index db04128188b8b..9ace91a3ae5a9 100644 --- a/coderd/database/migrations/000050_workspace_app_health.up.sql +++ b/coderd/database/migrations/000050_workspace_app_health.up.sql @@ -1,5 +1,8 @@ -CREATE TYPE workspace_app_health AS ENUM ('intializing', 'healthy', 'unhealthy'); +CREATE TYPE workspace_app_health AS ENUM ('disabled', 'intializing', 'healthy', 'unhealthy'); ALTER TABLE ONLY workspace_apps - ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'intializing', - ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity'; + ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity', + ADD COLUMN IF NOT EXISTS healthcheck_enabled boolean NOT NULL DEFAULT FALSE, + ADD COLUMN IF NOT EXISTS healthcheck_period int NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS unhealthy_threshold int NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled'; diff --git a/coderd/database/models.go b/coderd/database/models.go index e144d46753362..eff60d0092233 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -315,6 +315,7 @@ func (e *UserStatus) Scan(src interface{}) error { type WorkspaceAppHealth string const ( + WorkspaceAppHealthDisabled WorkspaceAppHealth = "disabled" WorkspaceAppHealthIntializing WorkspaceAppHealth = "intializing" WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy" WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy" @@ -595,16 +596,19 @@ type WorkspaceAgent struct { } type WorkspaceApp struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - Name string `db:"name" json:"name"` - Icon string `db:"icon" json:"icon"` - Command sql.NullString `db:"command" json:"command"` - Url sql.NullString `db:"url" json:"url"` - RelativePath bool `db:"relative_path" json:"relative_path"` - Health WorkspaceAppHealth `db:"health" json:"health"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + Name string `db:"name" json:"name"` + Icon string `db:"icon" json:"icon"` + Command sql.NullString `db:"command" json:"command"` + Url sql.NullString `db:"url" json:"url"` + RelativePath bool `db:"relative_path" json:"relative_path"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` + HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` + UnhealthyThreshold int32 `db:"unhealthy_threshold" json:"unhealthy_threshold"` + Health WorkspaceAppHealth `db:"health" json:"health"` } type WorkspaceBuild struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 958bf8d71763c..fee23396f26a7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3869,14 +3869,17 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.Command, &i.Url, &i.RelativePath, - &i.Health, &i.UpdatedAt, + &i.HealthcheckEnabled, + &i.HealthcheckPeriod, + &i.UnhealthyThreshold, + &i.Health, ) return i, err } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3897,8 +3900,11 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.Command, &i.Url, &i.RelativePath, - &i.Health, &i.UpdatedAt, + &i.HealthcheckEnabled, + &i.HealthcheckPeriod, + &i.UnhealthyThreshold, + &i.Health, ); err != nil { return nil, err } @@ -3914,7 +3920,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3935,8 +3941,11 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.Command, &i.Url, &i.RelativePath, - &i.Health, &i.UpdatedAt, + &i.HealthcheckEnabled, + &i.HealthcheckPeriod, + &i.UnhealthyThreshold, + &i.Health, ); err != nil { return nil, err } @@ -3952,7 +3961,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3973,8 +3982,11 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.Command, &i.Url, &i.RelativePath, - &i.Health, &i.UpdatedAt, + &i.HealthcheckEnabled, + &i.HealthcheckPeriod, + &i.UnhealthyThreshold, + &i.Health, ); err != nil { return nil, err } @@ -4002,7 +4014,7 @@ INSERT INTO relative_path ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, health, updated_at + ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health ` type InsertWorkspaceAppParams struct { @@ -4037,8 +4049,11 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.Command, &i.Url, &i.RelativePath, - &i.Health, &i.UpdatedAt, + &i.HealthcheckEnabled, + &i.HealthcheckPeriod, + &i.UnhealthyThreshold, + &i.Health, ) return i, err } diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index ae1ad64a3c3d8..fa1b86d98b62e 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -289,12 +289,20 @@ func (api *API) postWorkspaceAppHealths(rw http.ResponseWriter, r *http.Request) return } + if !found.HealthcheckEnabled { + httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(), + }) + return + } + switch health { - case codersdk.WorkspaceAppInitializing: + case codersdk.WorkspaceAppHealthInitializing: found.Health = database.WorkspaceAppHealthIntializing - case codersdk.WorkspaceAppHealthy: + case codersdk.WorkspaceAppHealthHealthy: found.Health = database.WorkspaceAppHealthHealthy - case codersdk.WorkspaceAppUnhealthy: + case codersdk.WorkspaceAppHealthUnhealthy: found.Health = database.WorkspaceAppHealthUnhealthy default: httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index dd320224dc31b..405a268b8c9b0 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -7,9 +7,10 @@ import ( type WorkspaceAppHealth string const ( - WorkspaceAppInitializing WorkspaceAppHealth = "initializing" - WorkspaceAppHealthy WorkspaceAppHealth = "healthy" - WorkspaceAppUnhealthy WorkspaceAppHealth = "unhealthy" + WorkspaceAppHealthDisabled WorkspaceAppHealth = "disabled" + WorkspaceAppHealthInitializing WorkspaceAppHealth = "initializing" + WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy" + WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy" ) type WorkspaceApp struct { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index fe1b95bcbe20b..2c20ff317490f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -745,7 +745,7 @@ export type UserStatus = "active" | "suspended" export type WorkspaceAgentStatus = "connected" | "connecting" | "disconnected" // From codersdk/workspaceapps.go -export type WorkspaceAppHealth = "healthy" | "initializing" | "unhealthy" +export type WorkspaceAppHealth = "disabled" | "healthy" | "initializing" | "unhealthy" // From codersdk/workspacebuilds.go export type WorkspaceTransition = "delete" | "start" | "stop" From 8d0517ed968fdf0ab3f2f16d4df1596b9a5a9ba5 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 17:40:25 +0000 Subject: [PATCH 05/64] healthcheck threshold --- coderd/database/dump.sql | 2 +- .../000050_workspace_app_health.down.sql | 2 +- .../000050_workspace_app_health.up.sql | 2 +- coderd/database/models.go | 26 +++++++++---------- coderd/database/queries.sql.go | 20 +++++++------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 5098af08619e3..f4c6ab864cdd4 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -353,7 +353,7 @@ CREATE TABLE workspace_apps ( updated_at timestamp with time zone DEFAULT '-infinity'::timestamp with time zone NOT NULL, healthcheck_enabled boolean DEFAULT false NOT NULL, healthcheck_period integer DEFAULT 0 NOT NULL, - unhealthy_threshold integer DEFAULT 0 NOT NULL, + healthcheck_threshold integer DEFAULT 0 NOT NULL, health workspace_app_health DEFAULT 'disabled'::public.workspace_app_health NOT NULL ); diff --git a/coderd/database/migrations/000050_workspace_app_health.down.sql b/coderd/database/migrations/000050_workspace_app_health.down.sql index ffc8d75cb33a8..2aef2836d1e0c 100644 --- a/coderd/database/migrations/000050_workspace_app_health.down.sql +++ b/coderd/database/migrations/000050_workspace_app_health.down.sql @@ -2,7 +2,7 @@ ALTER TABLE ONLY workspace_apps DROP COLUMN IF EXISTS updated_at, DROP COLUMN IF EXISTS healthcheck_enabled, DROP COLUMN IF EXISTS healthcheck_period, - DROP COLUMN IF EXISTS unhealthy_threshold, + DROP COLUMN IF EXISTS healthcheck_threshold, DROP COLUMN IF EXISTS health; DROP TYPE workspace_app_health; diff --git a/coderd/database/migrations/000050_workspace_app_health.up.sql b/coderd/database/migrations/000050_workspace_app_health.up.sql index 9ace91a3ae5a9..8879d0fee2359 100644 --- a/coderd/database/migrations/000050_workspace_app_health.up.sql +++ b/coderd/database/migrations/000050_workspace_app_health.up.sql @@ -4,5 +4,5 @@ ALTER TABLE ONLY workspace_apps ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity', ADD COLUMN IF NOT EXISTS healthcheck_enabled boolean NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS healthcheck_period int NOT NULL DEFAULT 0, - ADD COLUMN IF NOT EXISTS unhealthy_threshold int NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled'; diff --git a/coderd/database/models.go b/coderd/database/models.go index eff60d0092233..a20088e221cb9 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -596,19 +596,19 @@ type WorkspaceAgent struct { } type WorkspaceApp struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - Name string `db:"name" json:"name"` - Icon string `db:"icon" json:"icon"` - Command sql.NullString `db:"command" json:"command"` - Url sql.NullString `db:"url" json:"url"` - RelativePath bool `db:"relative_path" json:"relative_path"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` - HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` - UnhealthyThreshold int32 `db:"unhealthy_threshold" json:"unhealthy_threshold"` - Health WorkspaceAppHealth `db:"health" json:"health"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + Name string `db:"name" json:"name"` + Icon string `db:"icon" json:"icon"` + Command sql.NullString `db:"command" json:"command"` + Url sql.NullString `db:"url" json:"url"` + RelativePath bool `db:"relative_path" json:"relative_path"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` + HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` + HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` + Health WorkspaceAppHealth `db:"health" json:"health"` } type WorkspaceBuild struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index fee23396f26a7..64b0015d8f1fb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3872,14 +3872,14 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckPeriod, - &i.UnhealthyThreshold, + &i.HealthcheckThreshold, &i.Health, ) return i, err } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3903,7 +3903,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckPeriod, - &i.UnhealthyThreshold, + &i.HealthcheckThreshold, &i.Health, ); err != nil { return nil, err @@ -3920,7 +3920,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3944,7 +3944,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckPeriod, - &i.UnhealthyThreshold, + &i.HealthcheckThreshold, &i.Health, ); err != nil { return nil, err @@ -3961,7 +3961,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3985,7 +3985,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckPeriod, - &i.UnhealthyThreshold, + &i.HealthcheckThreshold, &i.Health, ); err != nil { return nil, err @@ -4014,7 +4014,7 @@ INSERT INTO relative_path ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, unhealthy_threshold, health + ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health ` type InsertWorkspaceAppParams struct { @@ -4052,7 +4052,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckPeriod, - &i.UnhealthyThreshold, + &i.HealthcheckThreshold, &i.Health, ) return i, err From 719eb4d6fc8c0f41eed262ed3f0154b6336e9da6 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 19:48:51 +0000 Subject: [PATCH 06/64] add storybooks --- codersdk/workspaceapps.go | 9 ++-- site/src/api/typesGenerated.ts | 3 ++ .../components/AppLink/AppLink.stories.tsx | 26 +++++++++ site/src/components/AppLink/AppLink.tsx | 54 +++++++++++++------ site/src/components/Resources/Resources.tsx | 1 + 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 405a268b8c9b0..5feaadaecff5d 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -20,11 +20,14 @@ type WorkspaceApp struct { Command string `json:"command,omitempty"` // Icon is a relative path or external URL that specifies // an icon to be displayed in the dashboard. - Icon string `json:"icon,omitempty"` - Status WorkspaceAppHealth `json:"health"` + Icon string `json:"icon,omitempty"` + HealthcheckEnabled bool `json:"healthcheck_enabled"` + HealthcheckPeriod int32 `json:"healthcheck_period"` + HealthcheckThreshold int32 `json:"healthcheck_threshold"` + Health WorkspaceAppHealth `json:"health"` } type PostWorkspaceAppHealthsRequest struct { - // Healths is a map of the workspace app name and the status of the app. + // Healths is a map of the workspace app name and the health of the app. Healths map[string]WorkspaceAppHealth } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 2c20ff317490f..f6f6832ebb611 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -621,6 +621,9 @@ export interface WorkspaceApp { readonly name: string readonly command?: string readonly icon?: string + readonly healthcheck_enabled: boolean + readonly healthcheck_period: number + readonly healthcheck_threshold: number readonly health: WorkspaceAppHealth } diff --git a/site/src/components/AppLink/AppLink.stories.tsx b/site/src/components/AppLink/AppLink.stories.tsx index b008905ef6c9d..eb7fd8bbb7d14 100644 --- a/site/src/components/AppLink/AppLink.stories.tsx +++ b/site/src/components/AppLink/AppLink.stories.tsx @@ -15,6 +15,7 @@ WithIcon.args = { workspaceName: MockWorkspace.name, appName: "code-server", appIcon: "/icon/code.svg", + health: "healthy", } export const WithoutIcon = Template.bind({}) @@ -22,4 +23,29 @@ WithoutIcon.args = { userName: "developer", workspaceName: MockWorkspace.name, appName: "code-server", + health: "healthy", +} + +export const HealthDisabled = Template.bind({}) +HealthDisabled.args = { + userName: "developer", + workspaceName: MockWorkspace.name, + appName: "code-server", + health: "disabled", +} + +export const HealthInitializing = Template.bind({}) +HealthInitializing.args = { + userName: "developer", + workspaceName: MockWorkspace.name, + appName: "code-server", + health: "initializing", +} + +export const HealthUnhealthy = Template.bind({}) +HealthUnhealthy.args = { + userName: "developer", + workspaceName: MockWorkspace.name, + appName: "code-server", + health: "unhealthy", } diff --git a/site/src/components/AppLink/AppLink.tsx b/site/src/components/AppLink/AppLink.tsx index 7d4d901c4bd96..1c1fc63d511c6 100644 --- a/site/src/components/AppLink/AppLink.tsx +++ b/site/src/components/AppLink/AppLink.tsx @@ -1,6 +1,8 @@ import Button from "@material-ui/core/Button" +import CircularProgress from "@material-ui/core/CircularProgress" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" +import CloseIcon from "@material-ui/icons/Close" import ComputerIcon from "@material-ui/icons/Computer" import { FC, PropsWithChildren } from "react" import * as TypesGen from "../../api/typesGenerated" @@ -17,6 +19,7 @@ export interface AppLinkProps { appName: TypesGen.WorkspaceApp["name"] appIcon?: TypesGen.WorkspaceApp["icon"] appCommand?: TypesGen.WorkspaceApp["command"] + health: TypesGen.WorkspaceApp["health"] } export const AppLink: FC> = ({ @@ -26,6 +29,7 @@ export const AppLink: FC> = ({ appName, appIcon, appCommand, + health, }) => { const styles = useStyles() @@ -38,37 +42,57 @@ export const AppLink: FC> = ({ )}` } + let canClick = true + let icon = appIcon ? {`${appName} : + if (health === "initializing") { + canClick = false + icon = + } + if (health === "unhealthy") { + canClick = false + icon = + } + return ( { - event.preventDefault() - window.open( - href, - Language.appTitle(appName, generateRandomString(12)), - "width=900,height=600", - ) - }} + className={canClick ? styles.link : styles.disabledLink} + onClick={ + canClick + ? (event) => { + event.preventDefault() + window.open( + href, + Language.appTitle(appName, generateRandomString(12)), + "width=900,height=600", + ) + } + : undefined + } > - ) } -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ link: { textDecoration: "none !important", }, + disabledLink: { + pointerEvents: "none", + textDecoration: "none !important", + }, + button: { whiteSpace: "nowrap", }, + + unhealthyIcon: { + color: theme.palette.error.main, + }, })) diff --git a/site/src/components/Resources/Resources.tsx b/site/src/components/Resources/Resources.tsx index d935f2096d1bc..402b5a202144f 100644 --- a/site/src/components/Resources/Resources.tsx +++ b/site/src/components/Resources/Resources.tsx @@ -171,6 +171,7 @@ export const Resources: FC> = ({ userName={workspace.owner_name} workspaceName={workspace.name} agentName={agent.name} + health={app.health} /> ))} From 1bcac738d039ceb475c9a7034c1bebc4d552cf53 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 19:55:08 +0000 Subject: [PATCH 07/64] typo --- coderd/database/dump.sql | 2 +- .../migrations/000050_workspace_app_health.up.sql | 2 +- coderd/database/models.go | 8 ++++---- coderd/workspaceapps.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f4c6ab864cdd4..f2fc81e67e9f6 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -90,7 +90,7 @@ CREATE TYPE user_status AS ENUM ( CREATE TYPE workspace_app_health AS ENUM ( 'disabled', - 'intializing', + 'initializing', 'healthy', 'unhealthy' ); diff --git a/coderd/database/migrations/000050_workspace_app_health.up.sql b/coderd/database/migrations/000050_workspace_app_health.up.sql index 8879d0fee2359..e4f7e4af51e69 100644 --- a/coderd/database/migrations/000050_workspace_app_health.up.sql +++ b/coderd/database/migrations/000050_workspace_app_health.up.sql @@ -1,4 +1,4 @@ -CREATE TYPE workspace_app_health AS ENUM ('disabled', 'intializing', 'healthy', 'unhealthy'); +CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', 'unhealthy'); ALTER TABLE ONLY workspace_apps ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity', diff --git a/coderd/database/models.go b/coderd/database/models.go index a20088e221cb9..139ec5c2e0a17 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -315,10 +315,10 @@ func (e *UserStatus) Scan(src interface{}) error { type WorkspaceAppHealth string const ( - WorkspaceAppHealthDisabled WorkspaceAppHealth = "disabled" - WorkspaceAppHealthIntializing WorkspaceAppHealth = "intializing" - WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy" - WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy" + WorkspaceAppHealthDisabled WorkspaceAppHealth = "disabled" + WorkspaceAppHealthInitializing WorkspaceAppHealth = "initializing" + WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy" + WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy" ) func (e *WorkspaceAppHealth) Scan(src interface{}) error { diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index fa1b86d98b62e..03ad6d5885438 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -299,7 +299,7 @@ func (api *API) postWorkspaceAppHealths(rw http.ResponseWriter, r *http.Request) switch health { case codersdk.WorkspaceAppHealthInitializing: - found.Health = database.WorkspaceAppHealthIntializing + found.Health = database.WorkspaceAppHealthInitializing case codersdk.WorkspaceAppHealthHealthy: found.Health = database.WorkspaceAppHealthHealthy case codersdk.WorkspaceAppHealthUnhealthy: From ae77f1ca8279e3e79d6e8d6a0a7eb5bcbab0a418 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:03:38 +0000 Subject: [PATCH 08/64] change to warning icon --- site/src/components/AppLink/AppLink.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/components/AppLink/AppLink.tsx b/site/src/components/AppLink/AppLink.tsx index 1c1fc63d511c6..99dab104b29ba 100644 --- a/site/src/components/AppLink/AppLink.tsx +++ b/site/src/components/AppLink/AppLink.tsx @@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button" import CircularProgress from "@material-ui/core/CircularProgress" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" -import CloseIcon from "@material-ui/icons/Close" +import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'; import ComputerIcon from "@material-ui/icons/Computer" import { FC, PropsWithChildren } from "react" import * as TypesGen from "../../api/typesGenerated" @@ -50,7 +50,7 @@ export const AppLink: FC> = ({ } if (health === "unhealthy") { canClick = false - icon = + icon = } return ( @@ -93,6 +93,6 @@ const useStyles = makeStyles((theme) => ({ }, unhealthyIcon: { - color: theme.palette.error.main, + color: theme.palette.warning.light, }, })) From 467a71591f9693c6a655eac1c4c73889608c0c6d Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:06:51 +0000 Subject: [PATCH 09/64] fix missing err check --- coderd/workspaceapps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 03ad6d5885438..31dda5de1606d 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -321,7 +321,7 @@ func (api *API) postWorkspaceAppHealths(rw http.ResponseWriter, r *http.Request) } for _, app := range newApps { - api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{ + err = api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{ ID: app.ID, UpdatedAt: time.Now(), Health: app.Health, From 22e275e7754e776223caa5cc6f33eae2305a27e1 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:12:55 +0000 Subject: [PATCH 10/64] gosec --- coderd/workspaceapps.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 31dda5de1606d..1a836a17d806c 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -275,12 +275,15 @@ func (api *API) postWorkspaceAppHealths(rw http.ResponseWriter, r *http.Request) var newApps []database.WorkspaceApp for name, health := range req.Healths { - var found *database.WorkspaceApp - for _, app := range apps { - if app.Name == name { - found = &app + found := func() *database.WorkspaceApp { + for _, app := range apps { + if app.Name == name { + return &app + } } - } + + return nil + }() if found == nil { httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ Message: "Error setting workspace app health", From 9f84cf2a68e626d4a290d74b68315e54e301b194 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:13:59 +0000 Subject: [PATCH 11/64] make fmt --- site/src/components/AppLink/AppLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/AppLink/AppLink.tsx b/site/src/components/AppLink/AppLink.tsx index 99dab104b29ba..1d09404720f7e 100644 --- a/site/src/components/AppLink/AppLink.tsx +++ b/site/src/components/AppLink/AppLink.tsx @@ -2,8 +2,8 @@ import Button from "@material-ui/core/Button" import CircularProgress from "@material-ui/core/CircularProgress" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" -import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'; import ComputerIcon from "@material-ui/icons/Computer" +import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline" import { FC, PropsWithChildren } from "react" import * as TypesGen from "../../api/typesGenerated" import { generateRandomString } from "../../util/random" From 77937997f8c2aba7a4b7eb686263bc73e12e3902 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:28:26 +0000 Subject: [PATCH 12/64] fix js tests --- site/src/testHelpers/entities.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 34b06a219656a..2aad034ee2cb6 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -324,6 +324,10 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { id: "test-app", name: "test-app", icon: "", + health: "disabled", + healthcheck_enabled: false, + healthcheck_period: 0, + healthcheck_threshold: 0, } export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { From 349116cce846b2583140cc938ba0f38c4e5f913b Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:31:36 +0000 Subject: [PATCH 13/64] add authtest skip --- coderd/coderdtest/authorize.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 331c105d13179..5010fc9f93584 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -59,6 +59,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true}, + "POST:/api/v2/workspaceagents/me/app-healths": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true}, // These endpoints have more assertions. This is good, add more endpoints to assert if you can! From 66a6146820fa06e49a193fd9b1a82fcc58083143 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 20:41:50 +0000 Subject: [PATCH 14/64] rebase --- ...e_app_health.down.sql => 000051_workspace_app_health.down.sql} | 0 ...space_app_health.up.sql => 000051_workspace_app_health.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000050_workspace_app_health.down.sql => 000051_workspace_app_health.down.sql} (100%) rename coderd/database/migrations/{000050_workspace_app_health.up.sql => 000051_workspace_app_health.up.sql} (100%) diff --git a/coderd/database/migrations/000050_workspace_app_health.down.sql b/coderd/database/migrations/000051_workspace_app_health.down.sql similarity index 100% rename from coderd/database/migrations/000050_workspace_app_health.down.sql rename to coderd/database/migrations/000051_workspace_app_health.down.sql diff --git a/coderd/database/migrations/000050_workspace_app_health.up.sql b/coderd/database/migrations/000051_workspace_app_health.up.sql similarity index 100% rename from coderd/database/migrations/000050_workspace_app_health.up.sql rename to coderd/database/migrations/000051_workspace_app_health.up.sql From 342cbb049faaa36f847c806e4d1e84d3bc7c57be Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 21:17:52 +0000 Subject: [PATCH 15/64] fix insert --- coderd/database/databasefake/databasefake.go | 20 +++++++----- coderd/database/queries.sql.go | 32 ++++++++++++++------ coderd/database/queries/workspaceapps.sql | 8 +++-- coderd/provisionerdaemons.go | 6 +++- coderd/workspaceagents.go | 12 +++++--- coderd/workspaceresources_test.go | 6 ++++ 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 102229b7d4ecb..a7cf0d271ce3b 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2019,14 +2019,18 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW // nolint:gosimple workspaceApp := database.WorkspaceApp{ - ID: arg.ID, - AgentID: arg.AgentID, - CreatedAt: arg.CreatedAt, - Name: arg.Name, - Icon: arg.Icon, - Command: arg.Command, - Url: arg.Url, - RelativePath: arg.RelativePath, + ID: arg.ID, + AgentID: arg.AgentID, + CreatedAt: arg.CreatedAt, + Name: arg.Name, + Icon: arg.Icon, + Command: arg.Command, + Url: arg.Url, + RelativePath: arg.RelativePath, + HealthcheckEnabled: arg.HealthcheckEnabled, + HealthcheckPeriod: arg.HealthcheckPeriod, + HealthcheckThreshold: arg.HealthcheckThreshold, + Health: arg.Health, } q.workspaceApps = append(q.workspaceApps, workspaceApp) return workspaceApp, nil diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 64b0015d8f1fb..bd3cd678693fd 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4011,21 +4011,29 @@ INSERT INTO icon, command, url, - relative_path + relative_path, + healthcheck_enabled, + healthcheck_period, + healthcheck_threshold, + health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health ` type InsertWorkspaceAppParams struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - Name string `db:"name" json:"name"` - Icon string `db:"icon" json:"icon"` - Command sql.NullString `db:"command" json:"command"` - Url sql.NullString `db:"url" json:"url"` - RelativePath bool `db:"relative_path" json:"relative_path"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + Name string `db:"name" json:"name"` + Icon string `db:"icon" json:"icon"` + Command sql.NullString `db:"command" json:"command"` + Url sql.NullString `db:"url" json:"url"` + RelativePath bool `db:"relative_path" json:"relative_path"` + HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` + HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` + HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` + Health WorkspaceAppHealth `db:"health" json:"health"` } func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) { @@ -4038,6 +4046,10 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace arg.Command, arg.Url, arg.RelativePath, + arg.HealthcheckEnabled, + arg.HealthcheckPeriod, + arg.HealthcheckThreshold, + arg.Health, ) var i WorkspaceApp err := row.Scan( diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index a17b5cd2c752c..0e076cdfe2a62 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -20,10 +20,14 @@ INSERT INTO icon, command, url, - relative_path + relative_path, + healthcheck_enabled, + healthcheck_period, + healthcheck_threshold, + health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *; -- name: UpdateWorkspaceAppHealthByID :exec UPDATE diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 58eb2315e16c2..b559a46956c7e 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -826,7 +826,11 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. String: app.Url, Valid: app.Url != "", }, - RelativePath: app.RelativePath, + RelativePath: app.RelativePath, + HealthcheckEnabled: false, + HealthcheckPeriod: 0, + HealthcheckThreshold: 0, + Health: database.WorkspaceAppHealthDisabled, }) if err != nil { return xerrors.Errorf("insert app: %w", err) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 691564d600409..29f06b7db109e 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -435,10 +435,14 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { apps := make([]codersdk.WorkspaceApp, 0) for _, dbApp := range dbApps { apps = append(apps, codersdk.WorkspaceApp{ - ID: dbApp.ID, - Name: dbApp.Name, - Command: dbApp.Command.String, - Icon: dbApp.Icon, + ID: dbApp.ID, + Name: dbApp.Name, + Command: dbApp.Command.String, + Icon: dbApp.Icon, + HealthcheckEnabled: dbApp.HealthcheckEnabled, + HealthcheckPeriod: dbApp.HealthcheckPeriod, + HealthcheckThreshold: dbApp.HealthcheckThreshold, + Health: codersdk.WorkspaceAppHealth(dbApp.Health), }) } return apps diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index a017f4165102e..58ba9852f1aa0 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -117,6 +117,12 @@ func TestWorkspaceResource(t *testing.T) { require.Equal(t, app.Command, got.Command) require.Equal(t, app.Icon, got.Icon) require.Equal(t, app.Name, got.Name) + + // ensure these are returned as disabled until we enable on the terraform side + require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health) + require.EqualValues(t, false, got.HealthcheckEnabled) + require.EqualValues(t, 0, got.HealthcheckPeriod) + require.EqualValues(t, 0, got.HealthcheckThreshold) }) t.Run("Metadata", func(t *testing.T) { From c8534d789f3c73512626c0038a803740c1c71589 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 21:20:21 +0000 Subject: [PATCH 16/64] whitespace --- coderd/database/queries/workspaceapps.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index 0e076cdfe2a62..657e48453cc3a 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -21,10 +21,10 @@ INSERT INTO command, url, relative_path, - healthcheck_enabled, - healthcheck_period, - healthcheck_threshold, - health + healthcheck_enabled, + healthcheck_period, + healthcheck_threshold, + health ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *; From 48c9c7659133eaa210a7cf43259f24fd5b041d7e Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 21:28:38 +0000 Subject: [PATCH 17/64] whitespace --- coderd/database/queries.sql.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index bd3cd678693fd..91d7aa14a9c96 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4012,10 +4012,10 @@ INSERT INTO command, url, relative_path, - healthcheck_enabled, - healthcheck_period, - healthcheck_threshold, - health + healthcheck_enabled, + healthcheck_period, + healthcheck_threshold, + health ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health From f08718e98b08481f938d56751b80917bda7aa5b0 Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 21:57:40 +0000 Subject: [PATCH 18/64] healthcheck url --- coderd/coderd.go | 2 +- coderd/coderdtest/authorize.go | 2 +- coderd/database/databasefake/databasefake.go | 1 + coderd/database/dump.sql | 1 + .../000051_workspace_app_health.up.sql | 1 + coderd/database/models.go | 1 + coderd/database/queries.sql.go | 18 +++++++++++++----- coderd/database/queries/workspaceapps.sql | 3 ++- coderd/provisionerdaemons.go | 4 +++- coderd/workspaceagents.go | 3 ++- coderd/workspaceapps.go | 2 +- coderd/workspaceresources_test.go | 3 ++- codersdk/workspaceapps.go | 9 ++++++--- site/src/api/typesGenerated.ts | 1 + site/src/testHelpers/entities.ts | 1 + 15 files changed, 37 insertions(+), 15 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index 7be01e05ca482..624f72f82c74b 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -412,7 +412,7 @@ func New(options *Options) *API { r.Use(httpmw.ExtractWorkspaceAgent(options.Database)) r.Get("/metadata", api.workspaceAgentMetadata) r.Post("/version", api.postWorkspaceAgentVersion) - r.Post("/app-healths", api.postWorkspaceAppHealths) + r.Post("/app-health", api.postWorkspaceAppHealth) r.Get("/gitsshkey", api.agentGitSSHKey) r.Get("/coordinate", api.workspaceAgentCoordinate) r.Get("/report-stats", api.workspaceAgentReportStats) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 5010fc9f93584..6d787934bf432 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -59,7 +59,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true}, - "POST:/api/v2/workspaceagents/me/app-healths": {NoAuthorize: true}, + "POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true}, // These endpoints have more assertions. This is good, add more endpoints to assert if you can! diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index a7cf0d271ce3b..52fba86ab9f44 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2028,6 +2028,7 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW Url: arg.Url, RelativePath: arg.RelativePath, HealthcheckEnabled: arg.HealthcheckEnabled, + HealthcheckUrl: arg.HealthcheckUrl, HealthcheckPeriod: arg.HealthcheckPeriod, HealthcheckThreshold: arg.HealthcheckThreshold, Health: arg.Health, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f2fc81e67e9f6..d9ce69b2cce7a 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -352,6 +352,7 @@ CREATE TABLE workspace_apps ( relative_path boolean DEFAULT false NOT NULL, updated_at timestamp with time zone DEFAULT '-infinity'::timestamp with time zone NOT NULL, healthcheck_enabled boolean DEFAULT false NOT NULL, + healthcheck_url text DEFAULT ''::text NOT NULL, healthcheck_period integer DEFAULT 0 NOT NULL, healthcheck_threshold integer DEFAULT 0 NOT NULL, health workspace_app_health DEFAULT 'disabled'::public.workspace_app_health NOT NULL diff --git a/coderd/database/migrations/000051_workspace_app_health.up.sql b/coderd/database/migrations/000051_workspace_app_health.up.sql index e4f7e4af51e69..201400dd994e1 100644 --- a/coderd/database/migrations/000051_workspace_app_health.up.sql +++ b/coderd/database/migrations/000051_workspace_app_health.up.sql @@ -3,6 +3,7 @@ CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', ALTER TABLE ONLY workspace_apps ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity', ADD COLUMN IF NOT EXISTS healthcheck_enabled boolean NOT NULL DEFAULT FALSE, + ADD COLUMN IF NOT EXISTS healthcheck_url text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS healthcheck_period int NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 139ec5c2e0a17..fdb79a861bf7e 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -606,6 +606,7 @@ type WorkspaceApp struct { RelativePath bool `db:"relative_path" json:"relative_path"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` + HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"` HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` Health WorkspaceAppHealth `db:"health" json:"health"` diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 91d7aa14a9c96..776ff4c6095b5 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3871,6 +3871,7 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.RelativePath, &i.UpdatedAt, &i.HealthcheckEnabled, + &i.HealthcheckUrl, &i.HealthcheckPeriod, &i.HealthcheckThreshold, &i.Health, @@ -3879,7 +3880,7 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3902,6 +3903,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.RelativePath, &i.UpdatedAt, &i.HealthcheckEnabled, + &i.HealthcheckUrl, &i.HealthcheckPeriod, &i.HealthcheckThreshold, &i.Health, @@ -3920,7 +3922,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3943,6 +3945,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.RelativePath, &i.UpdatedAt, &i.HealthcheckEnabled, + &i.HealthcheckUrl, &i.HealthcheckPeriod, &i.HealthcheckThreshold, &i.Health, @@ -3961,7 +3964,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3984,6 +3987,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.RelativePath, &i.UpdatedAt, &i.HealthcheckEnabled, + &i.HealthcheckUrl, &i.HealthcheckPeriod, &i.HealthcheckThreshold, &i.Health, @@ -4013,12 +4017,13 @@ INSERT INTO url, relative_path, healthcheck_enabled, + healthcheck_url, healthcheck_period, healthcheck_threshold, health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_period, healthcheck_threshold, health + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health ` type InsertWorkspaceAppParams struct { @@ -4031,6 +4036,7 @@ type InsertWorkspaceAppParams struct { Url sql.NullString `db:"url" json:"url"` RelativePath bool `db:"relative_path" json:"relative_path"` HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` + HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"` HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` Health WorkspaceAppHealth `db:"health" json:"health"` @@ -4047,6 +4053,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace arg.Url, arg.RelativePath, arg.HealthcheckEnabled, + arg.HealthcheckUrl, arg.HealthcheckPeriod, arg.HealthcheckThreshold, arg.Health, @@ -4063,6 +4070,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.RelativePath, &i.UpdatedAt, &i.HealthcheckEnabled, + &i.HealthcheckUrl, &i.HealthcheckPeriod, &i.HealthcheckThreshold, &i.Health, diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index 657e48453cc3a..c73ddb60a45d1 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -22,12 +22,13 @@ INSERT INTO url, relative_path, healthcheck_enabled, + healthcheck_url, healthcheck_period, healthcheck_threshold, health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *; -- name: UpdateWorkspaceAppHealthByID :exec UPDATE diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index b559a46956c7e..a84c727b2548f 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -826,8 +826,10 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. String: app.Url, Valid: app.Url != "", }, - RelativePath: app.RelativePath, + RelativePath: app.RelativePath, + // default to disabled until we tie up the terraform HealthcheckEnabled: false, + HealthcheckUrl: "", HealthcheckPeriod: 0, HealthcheckThreshold: 0, Health: database.WorkspaceAppHealthDisabled, diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 29f06b7db109e..3677ddfd5c4c8 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -440,7 +440,8 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { Command: dbApp.Command.String, Icon: dbApp.Icon, HealthcheckEnabled: dbApp.HealthcheckEnabled, - HealthcheckPeriod: dbApp.HealthcheckPeriod, + HealthcheckURL: dbApp.HealthcheckUrl, + HealthcheckInterval: dbApp.HealthcheckPeriod, HealthcheckThreshold: dbApp.HealthcheckThreshold, Health: codersdk.WorkspaceAppHealth(dbApp.Health), }) diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 1a836a17d806c..90b010b711ab4 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -257,7 +257,7 @@ func (api *API) applicationCookie(authCookie *http.Cookie) *http.Cookie { return &appCookie } -func (api *API) postWorkspaceAppHealths(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgent(r) var req codersdk.PostWorkspaceAppHealthsRequest if !httpapi.Read(rw, r, &req) { diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 58ba9852f1aa0..93187eaf4af79 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -121,7 +121,8 @@ func TestWorkspaceResource(t *testing.T) { // ensure these are returned as disabled until we enable on the terraform side require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health) require.EqualValues(t, false, got.HealthcheckEnabled) - require.EqualValues(t, 0, got.HealthcheckPeriod) + require.EqualValues(t, "", got.HealthcheckURL) + require.EqualValues(t, 0, got.HealthcheckInterval) require.EqualValues(t, 0, got.HealthcheckThreshold) }) diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 5feaadaecff5d..6e31f1411c996 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -20,9 +20,12 @@ type WorkspaceApp struct { Command string `json:"command,omitempty"` // Icon is a relative path or external URL that specifies // an icon to be displayed in the dashboard. - Icon string `json:"icon,omitempty"` - HealthcheckEnabled bool `json:"healthcheck_enabled"` - HealthcheckPeriod int32 `json:"healthcheck_period"` + Icon string `json:"icon,omitempty"` + HealthcheckEnabled bool `json:"healthcheck_enabled"` + HealthcheckURL string `json:"healthcheck_url"` + // HealthcheckInterval specifies the seconds between each health check. + HealthcheckInterval int32 `json:"healthcheck_period"` + // HealthcheckThreshold specifies the number of consecutive failed health checks before returning "unhealthy". HealthcheckThreshold int32 `json:"healthcheck_threshold"` Health WorkspaceAppHealth `json:"health"` } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f6f6832ebb611..99969936ab89a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -622,6 +622,7 @@ export interface WorkspaceApp { readonly command?: string readonly icon?: string readonly healthcheck_enabled: boolean + readonly healthcheck_url: string readonly healthcheck_period: number readonly healthcheck_threshold: number readonly health: WorkspaceAppHealth diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 2aad034ee2cb6..7e56e9a6d027b 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -326,6 +326,7 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { icon: "", health: "disabled", healthcheck_enabled: false, + healthcheck_url: "", healthcheck_period: 0, healthcheck_threshold: 0, } From 737209f3a37a200d3d378cc2056fad150833a52d Mon Sep 17 00:00:00 2001 From: Garrett Date: Mon, 19 Sep 2022 22:11:06 +0000 Subject: [PATCH 19/64] add proto --- provisioner/terraform/resources.go | 30 ++- provisionersdk/proto/provisioner.pb.go | 300 ++++++++++++++----------- provisionersdk/proto/provisioner.proto | 4 + 3 files changed, 195 insertions(+), 139 deletions(-) diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 264cdad139899..412598317be92 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -25,12 +25,16 @@ type agentAttributes struct { // A mapping of attributes on the "coder_app" resource. type agentAppAttributes struct { - AgentID string `mapstructure:"agent_id"` - Name string `mapstructure:"name"` - Icon string `mapstructure:"icon"` - URL string `mapstructure:"url"` - Command string `mapstructure:"command"` - RelativePath bool `mapstructure:"relative_path"` + AgentID string `mapstructure:"agent_id"` + Name string `mapstructure:"name"` + Icon string `mapstructure:"icon"` + URL string `mapstructure:"url"` + Command string `mapstructure:"command"` + RelativePath bool `mapstructure:"relative_path"` + HealthcheckEnabled bool `mapstructure:"healthcheck_enabled"` + HealthcheckURL string `mapstructure:"healthcheck_url"` + HealthcheckInterval int32 `mapstructure:"healthcheck_interval"` + HealthcheckThreshold int32 `mapstructure:"healthcheck_threshold"` } // A mapping of attributes on the "coder_metadata" resource. @@ -225,11 +229,15 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res continue } agent.Apps = append(agent.Apps, &proto.App{ - Name: attrs.Name, - Command: attrs.Command, - Url: attrs.URL, - Icon: attrs.Icon, - RelativePath: attrs.RelativePath, + Name: attrs.Name, + Command: attrs.Command, + Url: attrs.URL, + Icon: attrs.Icon, + RelativePath: attrs.RelativePath, + HealthcheckEnabled: attrs.HealthcheckEnabled, + HealthcheckUrl: attrs.HealthcheckURL, + HealthcheckInterval: attrs.HealthcheckInterval, + HealthcheckThreshold: attrs.HealthcheckThreshold, }) } } diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index b977e00a42e36..6fee4a0fb5487 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -850,11 +850,15 @@ type App struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` - Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` - RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` + RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"` + HealthcheckEnabled bool `protobuf:"varint,6,opt,name=healthcheck_enabled,json=healthcheckEnabled,proto3" json:"healthcheck_enabled,omitempty"` + HealthcheckUrl string `protobuf:"bytes,7,opt,name=healthcheck_url,json=healthcheckUrl,proto3" json:"healthcheck_url,omitempty"` + HealthcheckInterval int32 `protobuf:"varint,8,opt,name=healthcheck_interval,json=healthcheckInterval,proto3" json:"healthcheck_interval,omitempty"` + HealthcheckThreshold int32 `protobuf:"varint,9,opt,name=healthcheck_threshold,json=healthcheckThreshold,proto3" json:"healthcheck_threshold,omitempty"` } func (x *App) Reset() { @@ -924,6 +928,34 @@ func (x *App) GetRelativePath() bool { return false } +func (x *App) GetHealthcheckEnabled() bool { + if x != nil { + return x.HealthcheckEnabled + } + return false +} + +func (x *App) GetHealthcheckUrl() string { + if x != nil { + return x.HealthcheckUrl + } + return "" +} + +func (x *App) GetHealthcheckInterval() int32 { + if x != nil { + return x.HealthcheckInterval + } + return 0 +} + +func (x *App) GetHealthcheckThreshold() int32 { + if x != nil { + return x.HealthcheckThreshold + } + return 0 +} + // Resource represents created infrastructure. type Resource struct { state protoimpl.MessageState @@ -1880,131 +1912,143 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 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, 0x7e, - 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0xad, - 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, 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, 0xfc, - 0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 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, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xc0, + 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, + 0x2f, 0x0a, 0x13, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x68, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x31, 0x0a, 0x14, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x33, 0x0a, 0x15, + 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x74, 0x68, 0x72, 0x65, + 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x22, 0xad, 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, 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, 0xfc, 0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 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, 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, + 0x02, 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, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 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, 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, 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, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x08, + 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 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, 0x6b, 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, 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, 0xae, 0x07, - 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, 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, 0x1a, - 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 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, 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, - 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, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x00, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x18, 0x02, 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, 0x6b, 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, 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, - 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, + 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, 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 57931f4524069..67713a40f2a9f 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -94,6 +94,10 @@ message App { string url = 3; string icon = 4; bool relative_path = 5; + bool healthcheck_enabled = 6; + string healthcheck_url = 7; + int32 healthcheck_interval = 8; + int32 healthcheck_threshold = 9; } // Resource represents created infrastructure. From 466340a64a27353cfb3d88ccaa180321e01bcd2e Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 15:19:12 +0000 Subject: [PATCH 20/64] connect proto --- coderd/database/databasefake/databasefake.go | 3 +- coderd/database/dump.sql | 3 +- .../000051_workspace_app_health.down.sql | 3 +- .../000051_workspace_app_health.up.sql | 3 +- coderd/database/models.go | 3 +- coderd/database/queries.sql.go | 41 ++++++-------- coderd/database/queries/workspaceapps.sql | 5 +- coderd/provisionerdaemons.go | 18 ++++--- coderd/workspaceagents.go | 4 +- coderd/workspaceapps.go | 6 +-- coderd/workspaceresources_test.go | 53 +++++++++++++------ codersdk/workspaceapps.go | 2 +- 12 files changed, 77 insertions(+), 67 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 52fba86ab9f44..a437977bd4a78 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2029,7 +2029,7 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW RelativePath: arg.RelativePath, HealthcheckEnabled: arg.HealthcheckEnabled, HealthcheckUrl: arg.HealthcheckUrl, - HealthcheckPeriod: arg.HealthcheckPeriod, + HealthcheckInterval: arg.HealthcheckInterval, HealthcheckThreshold: arg.HealthcheckThreshold, Health: arg.Health, } @@ -2045,7 +2045,6 @@ func (q *fakeQuerier) UpdateWorkspaceAppHealthByID(_ context.Context, arg databa if app.ID != arg.ID { continue } - app.UpdatedAt = arg.UpdatedAt app.Health = arg.Health q.workspaceApps[index] = app return nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index d9ce69b2cce7a..1403b82c54da5 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -350,10 +350,9 @@ CREATE TABLE workspace_apps ( command character varying(65534), url character varying(65534), relative_path boolean DEFAULT false NOT NULL, - updated_at timestamp with time zone DEFAULT '-infinity'::timestamp with time zone NOT NULL, healthcheck_enabled boolean DEFAULT false NOT NULL, healthcheck_url text DEFAULT ''::text NOT NULL, - healthcheck_period integer DEFAULT 0 NOT NULL, + healthcheck_interval integer DEFAULT 0 NOT NULL, healthcheck_threshold integer DEFAULT 0 NOT NULL, health workspace_app_health DEFAULT 'disabled'::public.workspace_app_health NOT NULL ); diff --git a/coderd/database/migrations/000051_workspace_app_health.down.sql b/coderd/database/migrations/000051_workspace_app_health.down.sql index 2aef2836d1e0c..98173e3a94cd0 100644 --- a/coderd/database/migrations/000051_workspace_app_health.down.sql +++ b/coderd/database/migrations/000051_workspace_app_health.down.sql @@ -1,7 +1,6 @@ ALTER TABLE ONLY workspace_apps - DROP COLUMN IF EXISTS updated_at, DROP COLUMN IF EXISTS healthcheck_enabled, - DROP COLUMN IF EXISTS healthcheck_period, + DROP COLUMN IF EXISTS healthcheck_interval, DROP COLUMN IF EXISTS healthcheck_threshold, DROP COLUMN IF EXISTS health; diff --git a/coderd/database/migrations/000051_workspace_app_health.up.sql b/coderd/database/migrations/000051_workspace_app_health.up.sql index 201400dd994e1..b7bb8a63d2f95 100644 --- a/coderd/database/migrations/000051_workspace_app_health.up.sql +++ b/coderd/database/migrations/000051_workspace_app_health.up.sql @@ -1,9 +1,8 @@ CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', 'unhealthy'); ALTER TABLE ONLY workspace_apps - ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT '-infinity', ADD COLUMN IF NOT EXISTS healthcheck_enabled boolean NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS healthcheck_url text NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS healthcheck_period int NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS healthcheck_interval int NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled'; diff --git a/coderd/database/models.go b/coderd/database/models.go index fdb79a861bf7e..f7da8dd381069 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -604,10 +604,9 @@ type WorkspaceApp struct { Command sql.NullString `db:"command" json:"command"` Url sql.NullString `db:"url" json:"url"` RelativePath bool `db:"relative_path" json:"relative_path"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"` - HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` + HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"` HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` Health WorkspaceAppHealth `db:"health" json:"health"` } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 776ff4c6095b5..f67f8ddd7059a 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3869,10 +3869,9 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.Command, &i.Url, &i.RelativePath, - &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckUrl, - &i.HealthcheckPeriod, + &i.HealthcheckInterval, &i.HealthcheckThreshold, &i.Health, ) @@ -3880,7 +3879,7 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3901,10 +3900,9 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.Command, &i.Url, &i.RelativePath, - &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckUrl, - &i.HealthcheckPeriod, + &i.HealthcheckInterval, &i.HealthcheckThreshold, &i.Health, ); err != nil { @@ -3922,7 +3920,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3943,10 +3941,9 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.Command, &i.Url, &i.RelativePath, - &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckUrl, - &i.HealthcheckPeriod, + &i.HealthcheckInterval, &i.HealthcheckThreshold, &i.Health, ); err != nil { @@ -3964,7 +3961,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3985,10 +3982,9 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.Command, &i.Url, &i.RelativePath, - &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckUrl, - &i.HealthcheckPeriod, + &i.HealthcheckInterval, &i.HealthcheckThreshold, &i.Health, ); err != nil { @@ -4018,12 +4014,12 @@ INSERT INTO relative_path, healthcheck_enabled, healthcheck_url, - healthcheck_period, + healthcheck_interval, healthcheck_threshold, health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, updated_at, healthcheck_enabled, healthcheck_url, healthcheck_period, healthcheck_threshold, health + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health ` type InsertWorkspaceAppParams struct { @@ -4037,7 +4033,7 @@ type InsertWorkspaceAppParams struct { RelativePath bool `db:"relative_path" json:"relative_path"` HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"` - HealthcheckPeriod int32 `db:"healthcheck_period" json:"healthcheck_period"` + HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"` HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` Health WorkspaceAppHealth `db:"health" json:"health"` } @@ -4054,7 +4050,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace arg.RelativePath, arg.HealthcheckEnabled, arg.HealthcheckUrl, - arg.HealthcheckPeriod, + arg.HealthcheckInterval, arg.HealthcheckThreshold, arg.Health, ) @@ -4068,10 +4064,9 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.Command, &i.Url, &i.RelativePath, - &i.UpdatedAt, &i.HealthcheckEnabled, &i.HealthcheckUrl, - &i.HealthcheckPeriod, + &i.HealthcheckInterval, &i.HealthcheckThreshold, &i.Health, ) @@ -4082,20 +4077,18 @@ const updateWorkspaceAppHealthByID = `-- name: UpdateWorkspaceAppHealthByID :exe UPDATE workspace_apps SET - updated_at = $2, - health = $3 + health = $2 WHERE id = $1 ` type UpdateWorkspaceAppHealthByIDParams struct { - ID uuid.UUID `db:"id" json:"id"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - Health WorkspaceAppHealth `db:"health" json:"health"` + ID uuid.UUID `db:"id" json:"id"` + Health WorkspaceAppHealth `db:"health" json:"health"` } func (q *sqlQuerier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error { - _, err := q.db.ExecContext(ctx, updateWorkspaceAppHealthByID, arg.ID, arg.UpdatedAt, arg.Health) + _, err := q.db.ExecContext(ctx, updateWorkspaceAppHealthByID, arg.ID, arg.Health) return err } diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index c73ddb60a45d1..2976f66d72ae5 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -23,7 +23,7 @@ INSERT INTO relative_path, healthcheck_enabled, healthcheck_url, - healthcheck_period, + healthcheck_interval, healthcheck_threshold, health ) @@ -34,7 +34,6 @@ VALUES UPDATE workspace_apps SET - updated_at = $2, - health = $3 + health = $2 WHERE id = $1; diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index a84c727b2548f..9025620372cb5 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -812,6 +812,11 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent)) for _, app := range prAgent.Apps { + health := database.WorkspaceAppHealthDisabled + if app.HealthcheckEnabled { + health = database.WorkspaceAppHealthInitializing + } + dbApp, err := db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{ ID: uuid.New(), CreatedAt: database.Now(), @@ -826,13 +831,12 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. String: app.Url, Valid: app.Url != "", }, - RelativePath: app.RelativePath, - // default to disabled until we tie up the terraform - HealthcheckEnabled: false, - HealthcheckUrl: "", - HealthcheckPeriod: 0, - HealthcheckThreshold: 0, - Health: database.WorkspaceAppHealthDisabled, + RelativePath: app.RelativePath, + HealthcheckEnabled: app.HealthcheckEnabled, + HealthcheckUrl: app.HealthcheckUrl, + HealthcheckInterval: app.HealthcheckInterval, + HealthcheckThreshold: app.HealthcheckThreshold, + Health: health, }) if err != nil { return xerrors.Errorf("insert app: %w", err) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 3677ddfd5c4c8..49906873fbf8d 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -440,8 +440,8 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { Command: dbApp.Command.String, Icon: dbApp.Icon, HealthcheckEnabled: dbApp.HealthcheckEnabled, - HealthcheckURL: dbApp.HealthcheckUrl, - HealthcheckInterval: dbApp.HealthcheckPeriod, + HealthcheckUrl: dbApp.HealthcheckUrl, + HealthcheckInterval: dbApp.HealthcheckInterval, HealthcheckThreshold: dbApp.HealthcheckThreshold, Health: codersdk.WorkspaceAppHealth(dbApp.Health), }) diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 90b010b711ab4..35ed658a20d6d 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -7,7 +7,6 @@ import ( "net/http/httputil" "net/url" "strings" - "time" "github.com/go-chi/chi/v5" "go.opentelemetry.io/otel/trace" @@ -325,9 +324,8 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) for _, app := range newApps { err = api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{ - ID: app.ID, - UpdatedAt: time.Now(), - Health: app.Health, + ID: app.ID, + Health: app.Health, }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 93187eaf4af79..c1976fa45968b 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -74,11 +74,23 @@ func TestWorkspaceResource(t *testing.T) { IncludeProvisionerDaemon: true, }) user := coderdtest.CreateFirstUser(t, client) - app := &proto.App{ - Name: "code-server", - Command: "some-command", - Url: "http://localhost:3000", - Icon: "/code.svg", + apps := []*proto.App{ + { + Name: "code-server", + Command: "some-command", + Url: "http://localhost:3000", + Icon: "/code.svg", + }, + { + Name: "code-server", + Command: "some-command", + Url: "http://localhost:3000", + Icon: "/code.svg", + HealthcheckEnabled: true, + HealthcheckUrl: "http://localhost:3000", + HealthcheckInterval: 5, + HealthcheckThreshold: 6, + }, } version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, @@ -91,7 +103,7 @@ func TestWorkspaceResource(t *testing.T) { Agents: []*proto.Agent{{ Id: "something", Auth: &proto.Agent_Token{}, - Apps: []*proto.App{app}, + Apps: apps, }}, }}, }, @@ -112,18 +124,27 @@ func TestWorkspaceResource(t *testing.T) { require.NoError(t, err) require.Len(t, resource.Agents, 1) agent := resource.Agents[0] - require.Len(t, agent.Apps, 1) + require.Len(t, agent.Apps, 2) got := agent.Apps[0] - require.Equal(t, app.Command, got.Command) - require.Equal(t, app.Icon, got.Icon) - require.Equal(t, app.Name, got.Name) - - // ensure these are returned as disabled until we enable on the terraform side + app := apps[0] + require.EqualValues(t, app.Command, got.Command) + require.EqualValues(t, app.Icon, got.Icon) + require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health) - require.EqualValues(t, false, got.HealthcheckEnabled) - require.EqualValues(t, "", got.HealthcheckURL) - require.EqualValues(t, 0, got.HealthcheckInterval) - require.EqualValues(t, 0, got.HealthcheckThreshold) + require.EqualValues(t, app.HealthcheckEnabled, got.HealthcheckEnabled) + require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckUrl) + require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) + require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) + got = agent.Apps[1] + app = apps[1] + require.EqualValues(t, app.Command, got.Command) + require.EqualValues(t, app.Icon, got.Icon) + require.EqualValues(t, app.Name, got.Name) + require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, got.Health) + require.EqualValues(t, app.HealthcheckEnabled, got.HealthcheckEnabled) + require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckUrl) + require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) + require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) }) t.Run("Metadata", func(t *testing.T) { diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 6e31f1411c996..6760b08cdfba0 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -22,7 +22,7 @@ type WorkspaceApp struct { // an icon to be displayed in the dashboard. Icon string `json:"icon,omitempty"` HealthcheckEnabled bool `json:"healthcheck_enabled"` - HealthcheckURL string `json:"healthcheck_url"` + HealthcheckUrl string `json:"healthcheck_url"` // HealthcheckInterval specifies the seconds between each health check. HealthcheckInterval int32 `json:"healthcheck_period"` // HealthcheckThreshold specifies the number of consecutive failed health checks before returning "unhealthy". From 25fc5d840a1b93585000323092cbfc8fc9645782 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 15:20:59 +0000 Subject: [PATCH 21/64] whitespace --- provisionersdk/proto/provisioner.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 67713a40f2a9f..3dde826e27ece 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -94,10 +94,10 @@ message App { string url = 3; string icon = 4; bool relative_path = 5; - bool healthcheck_enabled = 6; - string healthcheck_url = 7; - int32 healthcheck_interval = 8; - int32 healthcheck_threshold = 9; + bool healthcheck_enabled = 6; + string healthcheck_url = 7; + int32 healthcheck_interval = 8; + int32 healthcheck_threshold = 9; } // Resource represents created infrastructure. From e28c3662f8153964db27de7ea65763605daffc0a Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 15:42:43 +0000 Subject: [PATCH 22/64] lint --- coderd/workspaceagents.go | 2 +- coderd/workspaceresources_test.go | 4 ++-- codersdk/workspaceapps.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 49906873fbf8d..a16f3f4ee72b8 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -440,7 +440,7 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { Command: dbApp.Command.String, Icon: dbApp.Icon, HealthcheckEnabled: dbApp.HealthcheckEnabled, - HealthcheckUrl: dbApp.HealthcheckUrl, + HealthcheckURL: dbApp.HealthcheckUrl, HealthcheckInterval: dbApp.HealthcheckInterval, HealthcheckThreshold: dbApp.HealthcheckThreshold, Health: codersdk.WorkspaceAppHealth(dbApp.Health), diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index c1976fa45968b..28e24372615fc 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -132,7 +132,7 @@ func TestWorkspaceResource(t *testing.T) { require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health) require.EqualValues(t, app.HealthcheckEnabled, got.HealthcheckEnabled) - require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckUrl) + require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckURL) require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) got = agent.Apps[1] @@ -142,7 +142,7 @@ func TestWorkspaceResource(t *testing.T) { require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, got.Health) require.EqualValues(t, app.HealthcheckEnabled, got.HealthcheckEnabled) - require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckUrl) + require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckURL) require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) }) diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 6760b08cdfba0..6e31f1411c996 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -22,7 +22,7 @@ type WorkspaceApp struct { // an icon to be displayed in the dashboard. Icon string `json:"icon,omitempty"` HealthcheckEnabled bool `json:"healthcheck_enabled"` - HealthcheckUrl string `json:"healthcheck_url"` + HealthcheckURL string `json:"healthcheck_url"` // HealthcheckInterval specifies the seconds between each health check. HealthcheckInterval int32 `json:"healthcheck_period"` // HealthcheckThreshold specifies the number of consecutive failed health checks before returning "unhealthy". From 1c179a421bf39ce4b5c3a48eb8864c01bed9112e Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 16:01:21 +0000 Subject: [PATCH 23/64] add workspace agent apps route --- coderd/coderd.go | 1 + coderd/coderdtest/authorize.go | 1 + coderd/workspaceagents.go | 98 ++++++++++++++++++++++++++++++++++ coderd/workspaceapps.go | 84 ----------------------------- 4 files changed, 100 insertions(+), 84 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index 624f72f82c74b..bd01a683b48d1 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -413,6 +413,7 @@ func New(options *Options) *API { r.Get("/metadata", api.workspaceAgentMetadata) r.Post("/version", api.postWorkspaceAgentVersion) r.Post("/app-health", api.postWorkspaceAppHealth) + r.Get("/apps", api.workspaceAgentApps) r.Get("/gitsshkey", api.agentGitSSHKey) r.Get("/coordinate", api.workspaceAgentCoordinate) r.Get("/report-stats", api.workspaceAgentReportStats) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 6d787934bf432..b58508b1dca48 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -60,6 +60,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true}, + "GET:/api/v2/workspaceagents/me/apps": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true}, // These endpoints have more assertions. This is good, add more endpoints to assert if you can! diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index a16f3f4ee72b8..f573f022d1f71 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -681,6 +681,104 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques } } +func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) { + workspaceAgent := httpmw.WorkspaceAgent(r) + var req codersdk.PostWorkspaceAppHealthsRequest + if !httpapi.Read(rw, r, &req) { + return + } + + apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Error getting agent apps", + Detail: err.Error(), + }) + return + } + + var newApps []database.WorkspaceApp + for name, health := range req.Healths { + found := func() *database.WorkspaceApp { + for _, app := range apps { + if app.Name == name { + return &app + } + } + + return nil + }() + if found == nil { + httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: xerrors.Errorf("workspace app name %s not found", name).Error(), + }) + return + } + + if !found.HealthcheckEnabled { + httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(), + }) + return + } + + switch health { + case codersdk.WorkspaceAppHealthInitializing: + found.Health = database.WorkspaceAppHealthInitializing + case codersdk.WorkspaceAppHealthHealthy: + found.Health = database.WorkspaceAppHealthHealthy + case codersdk.WorkspaceAppHealthUnhealthy: + found.Health = database.WorkspaceAppHealthUnhealthy + default: + httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: xerrors.Errorf("workspace app health %s is not a valid value", health).Error(), + }) + return + } + + // don't save if the value hasn't changed + if found.Health == database.WorkspaceAppHealth(health) { + continue + } + + newApps = append(newApps, *found) + } + + for _, app := range newApps { + err = api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{ + ID: app.ID, + Health: app.Health, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Error setting workspace app health", + Detail: err.Error(), + }) + return + } + } + + httpapi.Write(rw, http.StatusOK, nil) +} + +func (api *API) workspaceAgentApps(rw http.ResponseWriter, r *http.Request) { + workspaceAgent := httpmw.WorkspaceAgent(r) + + apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace agent applications.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertApps(apps)) +} + // wsNetConn wraps net.Conn created by websocket.NetConn(). Cancel func // is called if a read or write error is encountered. type wsNetConn struct { diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 35ed658a20d6d..a73dc184b8aa2 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -10,7 +10,6 @@ import ( "github.com/go-chi/chi/v5" "go.opentelemetry.io/otel/trace" - "golang.org/x/xerrors" "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/httpapi" @@ -255,86 +254,3 @@ func (api *API) applicationCookie(authCookie *http.Cookie) *http.Cookie { appCookie.Domain = "." + api.AccessURL.Hostname() return &appCookie } - -func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) { - workspaceAgent := httpmw.WorkspaceAgent(r) - var req codersdk.PostWorkspaceAppHealthsRequest - if !httpapi.Read(rw, r, &req) { - return - } - - apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error getting agent apps", - Detail: err.Error(), - }) - return - } - - var newApps []database.WorkspaceApp - for name, health := range req.Healths { - found := func() *database.WorkspaceApp { - for _, app := range apps { - if app.Name == name { - return &app - } - } - - return nil - }() - if found == nil { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: xerrors.Errorf("workspace app name %s not found", name).Error(), - }) - return - } - - if !found.HealthcheckEnabled { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(), - }) - return - } - - switch health { - case codersdk.WorkspaceAppHealthInitializing: - found.Health = database.WorkspaceAppHealthInitializing - case codersdk.WorkspaceAppHealthHealthy: - found.Health = database.WorkspaceAppHealthHealthy - case codersdk.WorkspaceAppHealthUnhealthy: - found.Health = database.WorkspaceAppHealthUnhealthy - default: - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: xerrors.Errorf("workspace app health %s is not a valid value", health).Error(), - }) - return - } - - // don't save if the value hasn't changed - if found.Health == database.WorkspaceAppHealth(health) { - continue - } - - newApps = append(newApps, *found) - } - - for _, app := range newApps { - err = api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{ - ID: app.ID, - Health: app.Health, - }) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error setting workspace app health", - Detail: err.Error(), - }) - return - } - } - - httpapi.Write(rw, http.StatusOK, nil) -} From 6df69984612d566c94ec933e02dde5d379755335 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 16:21:41 +0000 Subject: [PATCH 24/64] add myWorkspaceAgent --- coderd/coderd.go | 2 +- coderd/coderdtest/authorize.go | 1 - coderd/workspaceagents.go | 37 ++++++++++++++++++++-------------- codersdk/workspaceagents.go | 14 +++++++++++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index bd01a683b48d1..2075b7f410597 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -410,10 +410,10 @@ 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("/", api.myWorkspaceAgent) r.Get("/metadata", api.workspaceAgentMetadata) r.Post("/version", api.postWorkspaceAgentVersion) r.Post("/app-health", api.postWorkspaceAppHealth) - r.Get("/apps", api.workspaceAgentApps) r.Get("/gitsshkey", api.agentGitSSHKey) r.Get("/coordinate", api.workspaceAgentCoordinate) r.Get("/report-stats", api.workspaceAgentReportStats) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index b58508b1dca48..6d787934bf432 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -60,7 +60,6 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true}, - "GET:/api/v2/workspaceagents/me/apps": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true}, // These endpoints have more assertions. This is good, add more endpoints to assert if you can! diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index f573f022d1f71..8a7a4e756ea79 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -61,6 +61,28 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, apiAgent) } +func (api *API) myWorkspaceAgent(rw http.ResponseWriter, r *http.Request) { + workspaceAgent := httpmw.WorkspaceAgent(r) + dbApps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace agent applications.", + Detail: err.Error(), + }) + return + } + apiAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error reading workspace agent.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, apiAgent) +} + func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) @@ -764,21 +786,6 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) httpapi.Write(rw, http.StatusOK, nil) } -func (api *API) workspaceAgentApps(rw http.ResponseWriter, r *http.Request) { - workspaceAgent := httpmw.WorkspaceAgent(r) - - apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace agent applications.", - Detail: err.Error(), - }) - return - } - - httpapi.Write(rw, http.StatusOK, convertApps(apps)) -} - // wsNetConn wraps net.Conn created by websocket.NetConn(). Cancel func // is called if a read or write error is encountered. type wsNetConn struct { diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 95832fc625e11..4611987bd0138 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -348,6 +348,20 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent) } +// WorkspaceAgent returns the requesting agent. +func (c *Client) MyWorkspaceAgent(ctx context.Context) (WorkspaceAgent, error) { + res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me", nil) + if err != nil { + return WorkspaceAgent{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return WorkspaceAgent{}, readBodyAsError(res) + } + var workspaceAgent WorkspaceAgent + return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent) +} + func (c *Client) PostWorkspaceAgentVersion(ctx context.Context, version string) error { // Phone home and tell the mothership what version we're on. versionReq := PostWorkspaceAgentVersionRequest{Version: version} From 18fb1a5f4e5f0601ba28b6581a68becc9764d1c7 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 16:22:41 +0000 Subject: [PATCH 25/64] noauthorize --- coderd/coderdtest/authorize.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 6d787934bf432..e3c3c6aaf64cb 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -55,6 +55,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "POST:/api/v2/workspaceagents/aws-instance-identity": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/azure-instance-identity": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/google-instance-identity": {NoAuthorize: true}, + "GET:/api/v2/workspaceagents/me": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/gitsshkey": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, From dea8070206d545ea86098a34a9ac4dcf9d0e8f1b Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 16:27:15 +0000 Subject: [PATCH 26/64] add postworkspaceagentapphealth --- codersdk/workspaceagents.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 4611987bd0138..aa102ed2673d0 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -362,6 +362,18 @@ func (c *Client) MyWorkspaceAgent(ctx context.Context) (WorkspaceAgent, error) { return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent) } +// WorkspaceAgent returns the requesting agent. +func (c *Client) PostWorkspaceAgentAppHealth(ctx context.Context, req PostWorkspaceAppHealthsRequest) error { + res, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/version", req) + if err != nil { + return readBodyAsError(res) + } + // Discord the response + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + return nil +} + func (c *Client) PostWorkspaceAgentVersion(ctx context.Context, version string) error { // Phone home and tell the mothership what version we're on. versionReq := PostWorkspaceAgentVersionRequest{Version: version} From c098980ad8c7ac1970563b476ee32135c87e9aea Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 16:27:54 +0000 Subject: [PATCH 27/64] docs --- codersdk/workspaceagents.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index aa102ed2673d0..ec15257733c6a 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -348,7 +348,7 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent) } -// WorkspaceAgent returns the requesting agent. +// MyWorkspaceAgent returns the requesting agent. func (c *Client) MyWorkspaceAgent(ctx context.Context) (WorkspaceAgent, error) { res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me", nil) if err != nil { @@ -362,7 +362,7 @@ func (c *Client) MyWorkspaceAgent(ctx context.Context) (WorkspaceAgent, error) { return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent) } -// WorkspaceAgent returns the requesting agent. +// PostWorkspaceAgentAppHealth updates the workspace agent app health status. func (c *Client) PostWorkspaceAgentAppHealth(ctx context.Context, req PostWorkspaceAppHealthsRequest) error { res, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/version", req) if err != nil { From 84c3cf8f5ad860160edf7daf7155e54af8e3af7f Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 18:44:24 +0000 Subject: [PATCH 28/64] add reportAppHealth --- agent/agent.go | 66 +++++++-------- agent/agent_test.go | 59 ++++++------- agent/apphealth.go | 112 +++++++++++++++++++++++++ agent/stats.go | 7 +- cli/portforward.go | 2 +- coderd/coderd.go | 2 +- coderd/workspaceagents.go | 21 ++--- coderd/workspaceagents_test.go | 4 +- coderd/wsconncache/wsconncache.go | 10 +-- coderd/wsconncache/wsconncache_test.go | 23 ++--- agent/conn.go => codersdk/agentconn.go | 44 ++++++---- codersdk/workspaceagents.go | 41 ++++++--- 12 files changed, 258 insertions(+), 133 deletions(-) create mode 100644 agent/apphealth.go rename agent/conn.go => codersdk/agentconn.go (66%) diff --git a/agent/agent.go b/agent/agent.go index 18243ee788789..fae6f650b5ed4 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -33,6 +33,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/agent/usershell" + "github.com/coder/coder/codersdk" "github.com/coder/coder/pty" "github.com/coder/coder/tailnet" "github.com/coder/retry" @@ -49,39 +50,26 @@ const ( MagicSessionErrorCode = 229 ) -var ( - // tailnetIP is a static IPv6 address with the Tailscale prefix that is used to route - // connections from clients to this node. A dynamic address is not required because a Tailnet - // client only dials a single agent at a time. - tailnetIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4") - tailnetSSHPort = 1 - tailnetReconnectingPTYPort = 2 - tailnetSpeedtestPort = 3 -) - type Options struct { - CoordinatorDialer CoordinatorDialer - FetchMetadata FetchMetadata - + CoordinatorDialer CoordinatorDialer + FetchMetadata FetchMetadata + FetchWorkspaceApps FetchWorkspaceApps + PostWorkspaceAppHealth PostWorkspaceAppHealth StatsReporter StatsReporter ReconnectingPTYTimeout time.Duration EnvironmentVariables map[string]string Logger slog.Logger } -type Metadata struct { - DERPMap *tailcfg.DERPMap `json:"derpmap"` - EnvironmentVariables map[string]string `json:"environment_variables"` - StartupScript string `json:"startup_script"` - Directory string `json:"directory"` -} - // CoordinatorDialer is a function that constructs a new broker. // A dialer must be passed in to allow for reconnects. -type CoordinatorDialer func(ctx context.Context) (net.Conn, error) +type CoordinatorDialer func(context.Context) (net.Conn, error) // FetchMetadata is a function to obtain metadata for the agent. -type FetchMetadata func(ctx context.Context) (Metadata, error) +type FetchMetadata func(context.Context) (codersdk.WorkspaceAgentMetadata, error) + +type FetchWorkspaceApps func(context.Context) ([]codersdk.WorkspaceApp, error) +type PostWorkspaceAppHealth func(context.Context, map[string]codersdk.WorkspaceAppHealth) error func New(options Options) io.Closer { if options.ReconnectingPTYTimeout == 0 { @@ -98,6 +86,8 @@ func New(options Options) io.Closer { fetchMetadata: options.FetchMetadata, stats: &Stats{}, statsReporter: options.StatsReporter, + fetchWorkspaceApps: options.FetchWorkspaceApps, + postWorkspaceAppHealth: options.PostWorkspaceAppHealth, } server.init(ctx) return server @@ -120,14 +110,16 @@ type agent struct { fetchMetadata FetchMetadata sshServer *ssh.Server - network *tailnet.Conn - coordinatorDialer CoordinatorDialer - stats *Stats - statsReporter StatsReporter + network *tailnet.Conn + coordinatorDialer CoordinatorDialer + stats *Stats + statsReporter StatsReporter + fetchWorkspaceApps FetchWorkspaceApps + postWorkspaceAppHealth PostWorkspaceAppHealth } func (a *agent) run(ctx context.Context) { - var metadata Metadata + var metadata codersdk.WorkspaceAgentMetadata var err error // An exponential back-off occurs when the connection is failing to dial. // This is to prevent server spam in case of a coderd outage. @@ -168,6 +160,8 @@ func (a *agent) run(ctx context.Context) { if metadata.DERPMap != nil { go a.runTailnet(ctx, metadata.DERPMap) } + + go reportAppHealth(ctx, a.logger, a.fetchWorkspaceApps, a.postWorkspaceAppHealth) } func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { @@ -182,7 +176,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { } var err error a.network, err = tailnet.NewConn(&tailnet.Options{ - Addresses: []netip.Prefix{netip.PrefixFrom(tailnetIP, 128)}, + Addresses: []netip.Prefix{netip.PrefixFrom(codersdk.TailnetIP, 128)}, DERPMap: derpMap, Logger: a.logger.Named("tailnet"), }) @@ -199,7 +193,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { }) go a.runCoordinator(ctx) - sshListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetSSHPort)) + sshListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSSHPort)) if err != nil { a.logger.Critical(ctx, "listen for ssh", slog.Error(err)) return @@ -213,7 +207,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { go a.sshServer.HandleConn(a.stats.wrapConn(conn)) } }() - reconnectingPTYListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetReconnectingPTYPort)) + reconnectingPTYListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetReconnectingPTYPort)) if err != nil { a.logger.Critical(ctx, "listen for reconnecting pty", slog.Error(err)) return @@ -239,7 +233,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { if err != nil { continue } - var msg reconnectingPTYInit + var msg codersdk.ReconnectingPTYInit err = json.Unmarshal(data, &msg) if err != nil { continue @@ -247,7 +241,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { go a.handleReconnectingPTY(ctx, msg, conn) } }() - speedtestListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetSpeedtestPort)) + speedtestListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSpeedtestPort)) if err != nil { a.logger.Critical(ctx, "listen for speedtest", slog.Error(err)) return @@ -434,7 +428,7 @@ func (a *agent) init(ctx context.Context) { go a.run(ctx) if a.statsReporter != nil { - cl, err := a.statsReporter(ctx, a.logger, func() *Stats { + cl, err := a.statsReporter(ctx, a.logger, func() *codersdk.AgentStats { return a.stats.Copy() }) if err != nil { @@ -469,7 +463,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri if rawMetadata == nil { return nil, xerrors.Errorf("no metadata was provided: %w", err) } - metadata, valid := rawMetadata.(Metadata) + metadata, valid := rawMetadata.(codersdk.WorkspaceAgentMetadata) if !valid { return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata) } @@ -625,7 +619,7 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) { return cmd.Wait() } -func (a *agent) handleReconnectingPTY(ctx context.Context, msg reconnectingPTYInit, conn net.Conn) { +func (a *agent) handleReconnectingPTY(ctx context.Context, msg codersdk.ReconnectingPTYInit, conn net.Conn) { defer conn.Close() var rpty *reconnectingPTY @@ -766,7 +760,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, msg reconnectingPTYIn rpty.activeConnsMutex.Unlock() }() decoder := json.NewDecoder(conn) - var req ReconnectingPTYRequest + var req codersdk.ReconnectingPTYRequest for { err = decoder.Decode(&req) if xerrors.Is(err, io.EOF) { diff --git a/agent/agent_test.go b/agent/agent_test.go index 08c7918765319..3499ef5663414 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -35,6 +35,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/agent" + "github.com/coder/coder/codersdk" "github.com/coder/coder/pty/ptytest" "github.com/coder/coder/tailnet" "github.com/coder/coder/tailnet/tailnettest" @@ -52,7 +53,7 @@ func TestAgent(t *testing.T) { t.Run("SSH", func(t *testing.T) { t.Parallel() - conn, stats := setupAgent(t, agent.Metadata{}, 0) + conn, stats := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) sshClient, err := conn.SSHClient() require.NoError(t, err) @@ -69,20 +70,20 @@ func TestAgent(t *testing.T) { t.Run("ReconnectingPTY", func(t *testing.T) { t.Parallel() - conn, stats := setupAgent(t, agent.Metadata{}, 0) + conn, stats := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) ptyConn, err := conn.ReconnectingPTY(uuid.NewString(), 128, 128, "/bin/bash") require.NoError(t, err) defer ptyConn.Close() - data, err := json.Marshal(agent.ReconnectingPTYRequest{ + data, err := json.Marshal(codersdk.ReconnectingPTYRequest{ Data: "echo test\r\n", }) require.NoError(t, err) _, err = ptyConn.Write(data) require.NoError(t, err) - var s *agent.Stats + var s *codersdk.AgentStats require.Eventuallyf(t, func() bool { var ok bool s, ok = (<-stats) @@ -95,7 +96,7 @@ func TestAgent(t *testing.T) { t.Run("SessionExec", func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agent.Metadata{}) + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{}) command := "echo test" if runtime.GOOS == "windows" { @@ -108,7 +109,7 @@ func TestAgent(t *testing.T) { t.Run("GitSSH", func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agent.Metadata{}) + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{}) command := "sh -c 'echo $GIT_SSH_COMMAND'" if runtime.GOOS == "windows" { command = "cmd.exe /c echo %GIT_SSH_COMMAND%" @@ -126,7 +127,7 @@ func TestAgent(t *testing.T) { // it seems like it could be either. t.Skip("ConPTY appears to be inconsistent on Windows.") } - session := setupSSHSession(t, agent.Metadata{}) + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{}) command := "bash" if runtime.GOOS == "windows" { command = "cmd.exe" @@ -154,7 +155,7 @@ func TestAgent(t *testing.T) { t.Run("SessionTTYExitCode", func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agent.Metadata{}) + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{}) command := "areallynotrealcommand" err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{}) require.NoError(t, err) @@ -211,7 +212,7 @@ func TestAgent(t *testing.T) { t.Run("SFTP", func(t *testing.T) { t.Parallel() - conn, _ := setupAgent(t, agent.Metadata{}, 0) + conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) sshClient, err := conn.SSHClient() require.NoError(t, err) defer sshClient.Close() @@ -229,7 +230,7 @@ func TestAgent(t *testing.T) { t.Run("SCP", func(t *testing.T) { t.Parallel() - conn, _ := setupAgent(t, agent.Metadata{}, 0) + conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) sshClient, err := conn.SSHClient() require.NoError(t, err) defer sshClient.Close() @@ -247,7 +248,7 @@ func TestAgent(t *testing.T) { t.Parallel() key := "EXAMPLE" value := "value" - session := setupSSHSession(t, agent.Metadata{ + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{ EnvironmentVariables: map[string]string{ key: value, }, @@ -264,7 +265,7 @@ func TestAgent(t *testing.T) { t.Run("EnvironmentVariableExpansion", func(t *testing.T) { t.Parallel() key := "EXAMPLE" - session := setupSSHSession(t, agent.Metadata{ + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{ EnvironmentVariables: map[string]string{ key: "$SOMETHINGNOTSET", }, @@ -291,7 +292,7 @@ func TestAgent(t *testing.T) { t.Run(key, func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agent.Metadata{}) + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{}) command := "sh -c 'echo $" + key + "'" if runtime.GOOS == "windows" { command = "cmd.exe /c echo %" + key + "%" @@ -314,7 +315,7 @@ func TestAgent(t *testing.T) { t.Run(key, func(t *testing.T) { t.Parallel() - session := setupSSHSession(t, agent.Metadata{}) + session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{}) command := "sh -c 'echo $" + key + "'" if runtime.GOOS == "windows" { command = "cmd.exe /c echo %" + key + "%" @@ -330,7 +331,7 @@ func TestAgent(t *testing.T) { t.Parallel() tempPath := filepath.Join(t.TempDir(), "content.txt") content := "somethingnice" - setupAgent(t, agent.Metadata{ + setupAgent(t, codersdk.WorkspaceAgentMetadata{ StartupScript: fmt.Sprintf("echo %s > %s", content, tempPath), }, 0) @@ -365,7 +366,7 @@ func TestAgent(t *testing.T) { t.Skip("ConPTY appears to be inconsistent on Windows.") } - conn, _ := setupAgent(t, agent.Metadata{}, 0) + conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) id := uuid.NewString() netConn, err := conn.ReconnectingPTY(id, 100, 100, "/bin/bash") require.NoError(t, err) @@ -375,7 +376,7 @@ func TestAgent(t *testing.T) { // the shell is simultaneously sending a prompt. time.Sleep(100 * time.Millisecond) - data, err := json.Marshal(agent.ReconnectingPTYRequest{ + data, err := json.Marshal(codersdk.ReconnectingPTYRequest{ Data: "echo test\r\n", }) require.NoError(t, err) @@ -462,7 +463,7 @@ func TestAgent(t *testing.T) { } }() - conn, _ := setupAgent(t, agent.Metadata{}, 0) + conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) require.Eventually(t, func() bool { _, err := conn.Ping() return err == nil @@ -483,7 +484,7 @@ func TestAgent(t *testing.T) { t.Run("Tailnet", func(t *testing.T) { t.Parallel() derpMap := tailnettest.RunDERPAndSTUN(t) - conn, _ := setupAgent(t, agent.Metadata{ + conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ DERPMap: derpMap, }, 0) defer conn.Close() @@ -499,7 +500,7 @@ func TestAgent(t *testing.T) { t.Skip("The minimum duration for a speedtest is hardcoded in Tailscale to 5s!") } derpMap := tailnettest.RunDERPAndSTUN(t) - conn, _ := setupAgent(t, agent.Metadata{ + conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ DERPMap: derpMap, }, 0) defer conn.Close() @@ -510,7 +511,7 @@ func TestAgent(t *testing.T) { } func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd { - agentConn, _ := setupAgent(t, agent.Metadata{}, 0) + agentConn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) waitGroup := sync.WaitGroup{} @@ -547,7 +548,7 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe return exec.Command("ssh", args...) } -func setupSSHSession(t *testing.T, options agent.Metadata) *ssh.Session { +func setupSSHSession(t *testing.T, options codersdk.WorkspaceAgentMetadata) *ssh.Session { conn, _ := setupAgent(t, options, 0) sshClient, err := conn.SSHClient() require.NoError(t, err) @@ -565,18 +566,18 @@ func (c closeFunc) Close() error { return c() } -func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) ( - *agent.Conn, - <-chan *agent.Stats, +func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeout time.Duration) ( + *codersdk.AgentConn, + <-chan *codersdk.AgentStats, ) { if metadata.DERPMap == nil { metadata.DERPMap = tailnettest.RunDERPAndSTUN(t) } coordinator := tailnet.NewCoordinator() agentID := uuid.New() - statsCh := make(chan *agent.Stats) + statsCh := make(chan *codersdk.AgentStats) closer := agent.New(agent.Options{ - FetchMetadata: func(ctx context.Context) (agent.Metadata, error) { + FetchMetadata: func(ctx context.Context) (codersdk.WorkspaceAgentMetadata, error) { return metadata, nil }, CoordinatorDialer: func(ctx context.Context) (net.Conn, error) { @@ -595,7 +596,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) }, Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), ReconnectingPTYTimeout: ptyTimeout, - StatsReporter: func(ctx context.Context, log slog.Logger, statsFn func() *agent.Stats) (io.Closer, error) { + StatsReporter: func(ctx context.Context, log slog.Logger, statsFn func() *codersdk.AgentStats) (io.Closer, error) { doneCh := make(chan struct{}) ctx, cancel := context.WithCancel(ctx) @@ -648,7 +649,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) return conn.UpdateNodes(node) }) conn.SetNodeCallback(sendNode) - return &agent.Conn{ + return &codersdk.AgentConn{ Conn: conn, }, statsCh } diff --git a/agent/apphealth.go b/agent/apphealth.go new file mode 100644 index 0000000000000..41a3511b950c8 --- /dev/null +++ b/agent/apphealth.go @@ -0,0 +1,112 @@ +package agent + +import ( + "context" + "sync" + "time" + + "cdr.dev/slog" + "github.com/coder/coder/codersdk" +) + +func reportAppHealth(ctx context.Context, logger slog.Logger, fetcher FetchWorkspaceApps, reporter PostWorkspaceAppHealth) { + apps, err := fetcher(ctx) + if err != nil { + logger.Error(ctx, "failed to fetch workspace apps", slog.Error(err)) + return + } + + if len(apps) == 0 { + return + } + + health := make(map[string]codersdk.WorkspaceAppHealth, 0) + for _, app := range apps { + health[app.Name] = app.Health + } + + tickers := make(chan string, 0) + for _, app := range apps { + if shouldStartTicker(app) { + t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-t.C: + tickers <- app.Name + } + } + }() + } + } + var mu sync.RWMutex + var failures map[string]int + go func() { + for { + select { + case <-ctx.Done(): + return + case name := <-tickers: + for _, app := range apps { + if app.Name != name { + continue + } + + func() { + // do curl + var err error + if err != nil { + mu.Lock() + failures[app.Name]++ + mu.Unlock() + return + } + mu.Lock() + failures[app.Name] = 0 + mu.Unlock() + }() + } + } + } + }() + + reportTicker := time.NewTicker(time.Second) + lastHealth := make(map[string]codersdk.WorkspaceAppHealth, 0) + for { + select { + case <-ctx.Done(): + return + case <-reportTicker.C: + mu.RLock() + changed := healthChanged(lastHealth, health) + mu.Unlock() + if changed { + lastHealth = health + err := reporter(ctx, health) + if err != nil { + logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) + } + } + } + } +} + +func shouldStartTicker(app codersdk.WorkspaceApp) bool { + return app.HealthcheckEnabled && app.HealthcheckInterval > 0 && app.HealthcheckThreshold > 0 && app.HealthcheckURL != "" +} + +func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]codersdk.WorkspaceAppHealth) bool { + for name, newValue := range new { + oldValue, found := old[name] + if !found { + panic("workspace app lengths are not equal") + } + if newValue != oldValue { + return true + } + } + + return false +} diff --git a/agent/stats.go b/agent/stats.go index 0015a3e4e1fb1..e47bfcdee2157 100644 --- a/agent/stats.go +++ b/agent/stats.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "cdr.dev/slog" + "github.com/coder/coder/codersdk" ) // statsConn wraps a net.Conn with statistics. @@ -40,8 +41,8 @@ type Stats struct { TxBytes int64 `json:"tx_bytes"` } -func (s *Stats) Copy() *Stats { - return &Stats{ +func (s *Stats) Copy() *codersdk.AgentStats { + return &codersdk.AgentStats{ NumConns: atomic.LoadInt64(&s.NumConns), RxBytes: atomic.LoadInt64(&s.RxBytes), TxBytes: atomic.LoadInt64(&s.TxBytes), @@ -63,5 +64,5 @@ func (s *Stats) wrapConn(conn net.Conn) net.Conn { type StatsReporter func( ctx context.Context, log slog.Logger, - stats func() *Stats, + stats func() *codersdk.AgentStats, ) (io.Closer, error) diff --git a/cli/portforward.go b/cli/portforward.go index 7943291c042c0..2511375922979 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -169,7 +169,7 @@ func portForward() *cobra.Command { return cmd } -func listenAndPortForward(ctx context.Context, cmd *cobra.Command, conn *agent.Conn, wg *sync.WaitGroup, spec portForwardSpec) (net.Listener, error) { +func listenAndPortForward(ctx context.Context, cmd *cobra.Command, conn *codersdk.AgentConn, wg *sync.WaitGroup, spec portForwardSpec) (net.Listener, error) { _, _ = fmt.Fprintf(cmd.OutOrStderr(), "Forwarding '%v://%v' locally to '%v://%v' in the workspace\n", spec.listenNetwork, spec.listenAddress, spec.dialNetwork, spec.dialAddress) var ( diff --git a/coderd/coderd.go b/coderd/coderd.go index 2075b7f410597..e9e633506acba 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -410,7 +410,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("/", api.myWorkspaceAgent) + r.Get("/apps", api.workspaceAgentApps) r.Get("/metadata", api.workspaceAgentMetadata) r.Post("/version", api.postWorkspaceAgentVersion) r.Post("/app-health", api.postWorkspaceAppHealth) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 8a7a4e756ea79..4963aed611bae 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -23,7 +23,6 @@ import ( "tailscale.com/tailcfg" "cdr.dev/slog" - "github.com/coder/coder/agent" "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/coderd/httpmw" @@ -61,26 +60,18 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, apiAgent) } -func (api *API) myWorkspaceAgent(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgentApps(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgent(r) dbApps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent applications.", Detail: err.Error(), }) return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error reading workspace agent.", - Detail: err.Error(), - }) - return - } - httpapi.Write(rw, http.StatusOK, apiAgent) + httpapi.Write(r.Context(), rw, http.StatusOK, convertApps(dbApps)) } func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { @@ -95,7 +86,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) return } - httpapi.Write(ctx, rw, http.StatusOK, agent.Metadata{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentMetadata{ DERPMap: api.DERPMap, EnvironmentVariables: apiAgent.EnvironmentVariables, StartupScript: apiAgent.StartupScript, @@ -227,7 +218,7 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { _, _ = io.Copy(ptNetConn, wsNetConn) } -func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (*agent.Conn, error) { +func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (*codersdk.AgentConn, error) { clientConn, serverConn := net.Pipe() go func() { <-r.Context().Done() @@ -254,7 +245,7 @@ func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (* _ = conn.Close() } }() - return &agent.Conn{ + return &codersdk.AgentConn{ Conn: conn, }, nil } diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index c4514c1134427..cc2a43df22be7 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -324,7 +324,7 @@ func TestWorkspaceAgentPTY(t *testing.T) { // First attempt to resize the TTY. // The websocket will close if it fails! - data, err := json.Marshal(agent.ReconnectingPTYRequest{ + data, err := json.Marshal(codersdk.ReconnectingPTYRequest{ Height: 250, Width: 250, }) @@ -337,7 +337,7 @@ func TestWorkspaceAgentPTY(t *testing.T) { // the shell is simultaneously sending a prompt. time.Sleep(100 * time.Millisecond) - data, err = json.Marshal(agent.ReconnectingPTYRequest{ + data, err = json.Marshal(codersdk.ReconnectingPTYRequest{ Data: "echo test\r\n", }) require.NoError(t, err) diff --git a/coderd/wsconncache/wsconncache.go b/coderd/wsconncache/wsconncache.go index 7d3b741a63b7e..252ef5897f195 100644 --- a/coderd/wsconncache/wsconncache.go +++ b/coderd/wsconncache/wsconncache.go @@ -12,7 +12,7 @@ import ( "golang.org/x/sync/singleflight" "golang.org/x/xerrors" - "github.com/coder/coder/agent" + "github.com/coder/coder/codersdk" ) // New creates a new workspace connection cache that closes @@ -32,11 +32,11 @@ func New(dialer Dialer, inactiveTimeout time.Duration) *Cache { } // Dialer creates a new agent connection by ID. -type Dialer func(r *http.Request, id uuid.UUID) (*agent.Conn, error) +type Dialer func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) // Conn wraps an agent connection with a reusable HTTP transport. type Conn struct { - *agent.Conn + *codersdk.AgentConn locks atomic.Uint64 timeoutMutex sync.Mutex @@ -59,7 +59,7 @@ func (c *Conn) CloseWithError(err error) error { if c.timeout != nil { c.timeout.Stop() } - return c.Conn.CloseWithError(err) + return c.AgentConn.CloseWithError(err) } type Cache struct { @@ -98,7 +98,7 @@ func (c *Cache) Acquire(r *http.Request, id uuid.UUID) (*Conn, func(), error) { transport := defaultTransport.Clone() transport.DialContext = agentConn.DialContext conn := &Conn{ - Conn: agentConn, + AgentConn: agentConn, timeoutCancel: timeoutCancelFunc, transport: transport, } diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index a9ea85a2492ac..003d3cddb8b7a 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -23,6 +23,7 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/agent" "github.com/coder/coder/coderd/wsconncache" + "github.com/coder/coder/codersdk" "github.com/coder/coder/tailnet" "github.com/coder/coder/tailnet/tailnettest" ) @@ -35,8 +36,8 @@ func TestCache(t *testing.T) { t.Parallel() t.Run("Same", func(t *testing.T) { t.Parallel() - cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) { - return setupAgent(t, agent.Metadata{}, 0), nil + cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) { + return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil }, 0) defer func() { _ = cache.Close() @@ -50,9 +51,9 @@ func TestCache(t *testing.T) { t.Run("Expire", func(t *testing.T) { t.Parallel() called := atomic.NewInt32(0) - cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) { + cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) { called.Add(1) - return setupAgent(t, agent.Metadata{}, 0), nil + return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil }, time.Microsecond) defer func() { _ = cache.Close() @@ -69,8 +70,8 @@ func TestCache(t *testing.T) { }) t.Run("NoExpireWhenLocked", func(t *testing.T) { t.Parallel() - cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) { - return setupAgent(t, agent.Metadata{}, 0), nil + cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) { + return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil }, time.Microsecond) defer func() { _ = cache.Close() @@ -102,8 +103,8 @@ func TestCache(t *testing.T) { }() go server.Serve(random) - cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) { - return setupAgent(t, agent.Metadata{}, 0), nil + cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) { + return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil }, time.Microsecond) defer func() { _ = cache.Close() @@ -139,13 +140,13 @@ func TestCache(t *testing.T) { }) } -func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) *agent.Conn { +func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeout time.Duration) *codersdk.AgentConn { metadata.DERPMap = tailnettest.RunDERPAndSTUN(t) coordinator := tailnet.NewCoordinator() agentID := uuid.New() closer := agent.New(agent.Options{ - FetchMetadata: func(ctx context.Context) (agent.Metadata, error) { + FetchMetadata: func(ctx context.Context) (codersdk.WorkspaceAgentMetadata, error) { return metadata, nil }, CoordinatorDialer: func(ctx context.Context) (net.Conn, error) { @@ -180,7 +181,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) return conn.UpdateNodes(node) }) conn.SetNodeCallback(sendNode) - return &agent.Conn{ + return &codersdk.AgentConn{ Conn: conn, } } diff --git a/agent/conn.go b/codersdk/agentconn.go similarity index 66% rename from agent/conn.go rename to codersdk/agentconn.go index b64e935af7ecc..f089d1d2f5f39 100644 --- a/agent/conn.go +++ b/codersdk/agentconn.go @@ -1,4 +1,4 @@ -package agent +package codersdk import ( "context" @@ -18,6 +18,16 @@ import ( "github.com/coder/coder/tailnet" ) +var ( + // TailnetIP is a static IPv6 address with the Tailscale prefix that is used to route + // connections from clients to this node. A dynamic address is not required because a Tailnet + // client only dials a single agent at a time. + TailnetIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4") + TailnetSSHPort = 1 + TailnetReconnectingPTYPort = 2 + TailnetSpeedtestPort = 3 +) + // ReconnectingPTYRequest is sent from the client to the server // to pipe data to a PTY. type ReconnectingPTYRequest struct { @@ -26,15 +36,15 @@ type ReconnectingPTYRequest struct { Width uint16 `json:"width"` } -type Conn struct { +type AgentConn struct { *tailnet.Conn CloseFunc func() } -func (c *Conn) Ping() (time.Duration, error) { +func (c *AgentConn) Ping() (time.Duration, error) { errCh := make(chan error, 1) durCh := make(chan time.Duration, 1) - c.Conn.Ping(tailnetIP, tailcfg.PingICMP, func(pr *ipnstate.PingResult) { + c.Conn.Ping(TailnetIP, tailcfg.PingICMP, func(pr *ipnstate.PingResult) { if pr.Err != "" { errCh <- xerrors.New(pr.Err) return @@ -49,30 +59,30 @@ func (c *Conn) Ping() (time.Duration, error) { } } -func (c *Conn) CloseWithError(_ error) error { +func (c *AgentConn) CloseWithError(_ error) error { return c.Close() } -func (c *Conn) Close() error { +func (c *AgentConn) Close() error { if c.CloseFunc != nil { c.CloseFunc() } return c.Conn.Close() } -type reconnectingPTYInit struct { +type ReconnectingPTYInit struct { ID string Height uint16 Width uint16 Command string } -func (c *Conn) ReconnectingPTY(id string, height, width uint16, command string) (net.Conn, error) { - conn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetReconnectingPTYPort))) +func (c *AgentConn) ReconnectingPTY(id string, height, width uint16, command string) (net.Conn, error) { + conn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(TailnetIP, uint16(TailnetReconnectingPTYPort))) if err != nil { return nil, err } - data, err := json.Marshal(reconnectingPTYInit{ + data, err := json.Marshal(ReconnectingPTYInit{ ID: id, Height: height, Width: width, @@ -93,13 +103,13 @@ func (c *Conn) ReconnectingPTY(id string, height, width uint16, command string) return conn, nil } -func (c *Conn) SSH() (net.Conn, error) { - return c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetSSHPort))) +func (c *AgentConn) SSH() (net.Conn, error) { + return c.DialContextTCP(context.Background(), netip.AddrPortFrom(TailnetIP, uint16(TailnetSSHPort))) } // SSHClient calls SSH to create a client that uses a weak cipher // for high throughput. -func (c *Conn) SSHClient() (*ssh.Client, error) { +func (c *AgentConn) SSHClient() (*ssh.Client, error) { netConn, err := c.SSH() if err != nil { return nil, xerrors.Errorf("ssh: %w", err) @@ -116,8 +126,8 @@ func (c *Conn) SSHClient() (*ssh.Client, error) { return ssh.NewClient(sshConn, channels, requests), nil } -func (c *Conn) Speedtest(direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) { - speedConn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetSpeedtestPort))) +func (c *AgentConn) Speedtest(direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) { + speedConn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(TailnetIP, uint16(TailnetSpeedtestPort))) if err != nil { return nil, xerrors.Errorf("dial speedtest: %w", err) } @@ -128,13 +138,13 @@ func (c *Conn) Speedtest(direction speedtest.Direction, duration time.Duration) return results, err } -func (c *Conn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { +func (c *AgentConn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { if network == "unix" { return nil, xerrors.New("network must be tcp or udp") } _, rawPort, _ := net.SplitHostPort(addr) port, _ := strconv.Atoi(rawPort) - ipp := netip.AddrPortFrom(tailnetIP, uint16(port)) + ipp := netip.AddrPortFrom(TailnetIP, uint16(port)) if network == "udp" { return c.Conn.DialContextUDP(ctx, ipp) } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index ec15257733c6a..d646c1052eeef 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -21,7 +21,6 @@ import ( "cdr.dev/slog" - "github.com/coder/coder/agent" "github.com/coder/coder/tailnet" "github.com/coder/retry" ) @@ -56,6 +55,13 @@ type PostWorkspaceAgentVersionRequest struct { Version string `json:"version"` } +type WorkspaceAgentMetadata struct { + DERPMap *tailcfg.DERPMap `json:"derpmap"` + EnvironmentVariables map[string]string `json:"environment_variables"` + StartupScript string `json:"startup_script"` + Directory string `json:"directory"` +} + // AuthWorkspaceGoogleInstanceIdentity uses the Google Compute Engine Metadata API to // fetch a signed JWT, and exchange it for a session token for a workspace agent. // @@ -185,16 +191,16 @@ func (c *Client) AuthWorkspaceAzureInstanceIdentity(ctx context.Context) (Worksp } // WorkspaceAgentMetadata fetches metadata for the currently authenticated workspace agent. -func (c *Client) WorkspaceAgentMetadata(ctx context.Context) (agent.Metadata, error) { +func (c *Client) WorkspaceAgentMetadata(ctx context.Context) (WorkspaceAgentMetadata, error) { res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/metadata", nil) if err != nil { - return agent.Metadata{}, err + return WorkspaceAgentMetadata{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return agent.Metadata{}, readBodyAsError(res) + return WorkspaceAgentMetadata{}, readBodyAsError(res) } - var agentMetadata agent.Metadata + var agentMetadata WorkspaceAgentMetadata return agentMetadata, json.NewDecoder(res.Body).Decode(&agentMetadata) } @@ -228,7 +234,7 @@ func (c *Client) ListenWorkspaceAgentTailnet(ctx context.Context) (net.Conn, err return websocket.NetConn(ctx, conn, websocket.MessageBinary), nil } -func (c *Client) DialWorkspaceAgentTailnet(ctx context.Context, logger slog.Logger, agentID uuid.UUID) (*agent.Conn, error) { +func (c *Client) DialWorkspaceAgentTailnet(ctx context.Context, logger slog.Logger, agentID uuid.UUID) (*AgentConn, error) { res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/connection", agentID), nil) if err != nil { return nil, err @@ -325,7 +331,7 @@ func (c *Client) DialWorkspaceAgentTailnet(ctx context.Context, logger slog.Logg _ = conn.Close() return nil, err } - return &agent.Conn{ + return &AgentConn{ Conn: conn, CloseFunc: func() { cancelFunc() @@ -349,17 +355,17 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge } // MyWorkspaceAgent returns the requesting agent. -func (c *Client) MyWorkspaceAgent(ctx context.Context) (WorkspaceAgent, error) { +func (c *Client) WorkspaceAgentApps(ctx context.Context) ([]WorkspaceApp, error) { res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me", nil) if err != nil { - return WorkspaceAgent{}, err + return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return WorkspaceAgent{}, readBodyAsError(res) + return nil, readBodyAsError(res) } - var workspaceAgent WorkspaceAgent - return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent) + var workspaceApps []WorkspaceApp + return workspaceApps, json.NewDecoder(res.Body).Decode(&workspaceApps) } // PostWorkspaceAgentAppHealth updates the workspace agent app health status. @@ -418,12 +424,21 @@ func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, agentID, rec return websocket.NetConn(ctx, conn, websocket.MessageBinary), nil } +// Stats records the Agent's network connection statistics for use in +// user-facing metrics and debugging. +// Each member value must be written and read with atomic. +type AgentStats struct { + NumConns int64 `json:"num_comms"` + RxBytes int64 `json:"rx_bytes"` + TxBytes int64 `json:"tx_bytes"` +} + // AgentReportStats begins a stat streaming connection with the Coder server. // It is resilient to network failures and intermittent coderd issues. func (c *Client) AgentReportStats( ctx context.Context, log slog.Logger, - stats func() *agent.Stats, + stats func() *AgentStats, ) (io.Closer, error) { serverURL, err := c.URL.Parse("/api/v2/workspaceagents/me/report-stats") if err != nil { From 7028377a50ef9dc82ac407a3009432301aeea0c6 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 19:11:17 +0000 Subject: [PATCH 29/64] add retry loop --- agent/apphealth.go | 165 ++++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 63 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 41a3511b950c8..3defb5aa32b89 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -2,94 +2,133 @@ package agent import ( "context" + "net/http" + "net/url" "sync" "time" + "golang.org/x/xerrors" + "cdr.dev/slog" "github.com/coder/coder/codersdk" + "github.com/coder/retry" ) -func reportAppHealth(ctx context.Context, logger slog.Logger, fetcher FetchWorkspaceApps, reporter PostWorkspaceAppHealth) { - apps, err := fetcher(ctx) - if err != nil { - logger.Error(ctx, "failed to fetch workspace apps", slog.Error(err)) - return - } +func reportAppHealth(ctx context.Context, logger slog.Logger, fetchApps FetchWorkspaceApps, reportHealth PostWorkspaceAppHealth) { + r := retry.New(time.Second, 30*time.Second) + for { + err := func() error { + apps, err := fetchApps(ctx) + if err != nil { + return xerrors.Errorf("getting workspace apps: %w", err) + } - if len(apps) == 0 { - return - } + if len(apps) == 0 { + return nil + } - health := make(map[string]codersdk.WorkspaceAppHealth, 0) - for _, app := range apps { - health[app.Name] = app.Health - } + health := make(map[string]codersdk.WorkspaceAppHealth, 0) + for _, app := range apps { + health[app.Name] = app.Health + } - tickers := make(chan string, 0) - for _, app := range apps { - if shouldStartTicker(app) { - t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) + tickers := make(chan string, 0) + for _, app := range apps { + if shouldStartTicker(app) { + t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-t.C: + tickers <- app.Name + } + } + }() + } + } + var mu sync.RWMutex + var failures map[string]int go func() { for { select { case <-ctx.Done(): return - case <-t.C: - tickers <- app.Name + case name := <-tickers: + for _, app := range apps { + if app.Name != name { + continue + } + + client := &http.Client{ + Timeout: time.Duration(app.HealthcheckInterval), + } + err := func() error { + u, err := url.Parse(app.HealthcheckURL) + if err != nil { + return err + } + res, err := client.Do(&http.Request{ + Method: http.MethodGet, + URL: u, + }) + if err != nil { + return err + } + res.Body.Close() + if res.StatusCode > 499 { + return xerrors.Errorf("error status code: %d", res.StatusCode) + } + + return nil + }() + if err == nil { + mu.Lock() + failures[app.Name]++ + if failures[app.Name] > int(app.HealthcheckThreshold) { + health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy + } + mu.Unlock() + } else { + mu.Lock() + failures[app.Name] = 0 + health[app.Name] = codersdk.WorkspaceAppHealthHealthy + mu.Unlock() + } + } } } }() - } - } - var mu sync.RWMutex - var failures map[string]int - go func() { - for { - select { - case <-ctx.Done(): - return - case name := <-tickers: - for _, app := range apps { - if app.Name != name { - continue - } - func() { - // do curl - var err error + reportTicker := time.NewTicker(time.Second) + lastHealth := make(map[string]codersdk.WorkspaceAppHealth, 0) + for { + select { + case <-ctx.Done(): + return nil + case <-reportTicker.C: + mu.RLock() + changed := healthChanged(lastHealth, health) + mu.Unlock() + if changed { + lastHealth = health + err := reportHealth(ctx, health) if err != nil { - mu.Lock() - failures[app.Name]++ - mu.Unlock() - return + logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) } - mu.Lock() - failures[app.Name] = 0 - mu.Unlock() - }() + } } } + }() + if err != nil { + logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) + // continue loop with backoff on non-nil errors + r.Wait(ctx) + continue } - }() - reportTicker := time.NewTicker(time.Second) - lastHealth := make(map[string]codersdk.WorkspaceAppHealth, 0) - for { - select { - case <-ctx.Done(): - return - case <-reportTicker.C: - mu.RLock() - changed := healthChanged(lastHealth, health) - mu.Unlock() - if changed { - lastHealth = health - err := reporter(ctx, health) - if err != nil { - logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) - } - } - } + return } } From 947ff9c99531b603ecaa53fc158111cc7f0355dd Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 19:12:45 +0000 Subject: [PATCH 30/64] gosimp --- agent/apphealth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 3defb5aa32b89..ee3ee1520f0eb 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -32,7 +32,7 @@ func reportAppHealth(ctx context.Context, logger slog.Logger, fetchApps FetchWor health[app.Name] = app.Health } - tickers := make(chan string, 0) + tickers := make(chan string) for _, app := range apps { if shouldStartTicker(app) { t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) From 047a2e620303487e379daead5b27f2fc660d334c Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 19:46:04 +0000 Subject: [PATCH 31/64] fix --- agent/apphealth.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index ee3ee1520f0eb..11e2021fd9efa 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -3,7 +3,6 @@ package agent import ( "context" "net/http" - "net/url" "sync" "time" @@ -65,19 +64,16 @@ func reportAppHealth(ctx context.Context, logger slog.Logger, fetchApps FetchWor Timeout: time.Duration(app.HealthcheckInterval), } err := func() error { - u, err := url.Parse(app.HealthcheckURL) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.HealthcheckURL, nil) if err != nil { return err } - res, err := client.Do(&http.Request{ - Method: http.MethodGet, - URL: u, - }) + res, err := client.Do(req) if err != nil { return err } res.Body.Close() - if res.StatusCode > 499 { + if res.StatusCode >= http.StatusInternalServerError { return xerrors.Errorf("error status code: %d", res.StatusCode) } @@ -110,7 +106,7 @@ func reportAppHealth(ctx context.Context, logger slog.Logger, fetchApps FetchWor case <-reportTicker.C: mu.RLock() changed := healthChanged(lastHealth, health) - mu.Unlock() + mu.RUnlock() if changed { lastHealth = health err := reportHealth(ctx, health) From 26d902a11d3e84b6a2d28625908a7649ae4ab0b7 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 19:48:09 +0000 Subject: [PATCH 32/64] authorizer --- coderd/coderdtest/authorize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index e3c3c6aaf64cb..5173231bf6c4f 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -55,7 +55,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "POST:/api/v2/workspaceagents/aws-instance-identity": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/azure-instance-identity": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/google-instance-identity": {NoAuthorize: true}, - "GET:/api/v2/workspaceagents/me": {NoAuthorize: true}, + "GET:/api/v2/workspaceagents/me/apps": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/gitsshkey": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, From 4e652296f48ca24fe956d73c5e993565bf0dc509 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 20:15:47 +0000 Subject: [PATCH 33/64] workspace app health reporter --- agent/agent.go | 51 ++++--- agent/apphealth.go | 192 +++++++++++++------------ cli/agent.go | 6 +- cli/configssh_test.go | 7 +- cli/speedtest_test.go | 7 +- cli/ssh_test.go | 21 +-- coderd/templates_test.go | 9 +- coderd/workspaceagents_test.go | 21 +-- coderd/workspaceapps_test.go | 9 +- coderd/wsconncache/wsconncache_test.go | 5 +- 10 files changed, 172 insertions(+), 156 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index fae6f650b5ed4..fd926b6dd044c 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -51,14 +51,13 @@ const ( ) type Options struct { - CoordinatorDialer CoordinatorDialer - FetchMetadata FetchMetadata - FetchWorkspaceApps FetchWorkspaceApps - PostWorkspaceAppHealth PostWorkspaceAppHealth - StatsReporter StatsReporter - ReconnectingPTYTimeout time.Duration - EnvironmentVariables map[string]string - Logger slog.Logger + CoordinatorDialer CoordinatorDialer + FetchMetadata FetchMetadata + StatsReporter StatsReporter + WorkspaceAppHealthReporter WorkspaceAppHealthReporter + ReconnectingPTYTimeout time.Duration + EnvironmentVariables map[string]string + Logger slog.Logger } // CoordinatorDialer is a function that constructs a new broker. @@ -69,7 +68,7 @@ type CoordinatorDialer func(context.Context) (net.Conn, error) type FetchMetadata func(context.Context) (codersdk.WorkspaceAgentMetadata, error) type FetchWorkspaceApps func(context.Context) ([]codersdk.WorkspaceApp, error) -type PostWorkspaceAppHealth func(context.Context, map[string]codersdk.WorkspaceAppHealth) error +type PostWorkspaceAppHealth func(context.Context, codersdk.PostWorkspaceAppHealthsRequest) error func New(options Options) io.Closer { if options.ReconnectingPTYTimeout == 0 { @@ -77,17 +76,16 @@ func New(options Options) io.Closer { } ctx, cancelFunc := context.WithCancel(context.Background()) server := &agent{ - reconnectingPTYTimeout: options.ReconnectingPTYTimeout, - logger: options.Logger, - closeCancel: cancelFunc, - closed: make(chan struct{}), - envVars: options.EnvironmentVariables, - coordinatorDialer: options.CoordinatorDialer, - fetchMetadata: options.FetchMetadata, - stats: &Stats{}, - statsReporter: options.StatsReporter, - fetchWorkspaceApps: options.FetchWorkspaceApps, - postWorkspaceAppHealth: options.PostWorkspaceAppHealth, + reconnectingPTYTimeout: options.ReconnectingPTYTimeout, + logger: options.Logger, + closeCancel: cancelFunc, + closed: make(chan struct{}), + envVars: options.EnvironmentVariables, + coordinatorDialer: options.CoordinatorDialer, + fetchMetadata: options.FetchMetadata, + stats: &Stats{}, + statsReporter: options.StatsReporter, + workspaceAppHealthReporter: options.WorkspaceAppHealthReporter, } server.init(ctx) return server @@ -110,12 +108,11 @@ type agent struct { fetchMetadata FetchMetadata sshServer *ssh.Server - network *tailnet.Conn - coordinatorDialer CoordinatorDialer - stats *Stats - statsReporter StatsReporter - fetchWorkspaceApps FetchWorkspaceApps - postWorkspaceAppHealth PostWorkspaceAppHealth + network *tailnet.Conn + coordinatorDialer CoordinatorDialer + stats *Stats + statsReporter StatsReporter + workspaceAppHealthReporter WorkspaceAppHealthReporter } func (a *agent) run(ctx context.Context) { @@ -161,7 +158,7 @@ func (a *agent) run(ctx context.Context) { go a.runTailnet(ctx, metadata.DERPMap) } - go reportAppHealth(ctx, a.logger, a.fetchWorkspaceApps, a.postWorkspaceAppHealth) + go a.workspaceAppHealthReporter(ctx) } func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { diff --git a/agent/apphealth.go b/agent/apphealth.go index 11e2021fd9efa..ad9669670f208 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -13,118 +13,124 @@ import ( "github.com/coder/retry" ) -func reportAppHealth(ctx context.Context, logger slog.Logger, fetchApps FetchWorkspaceApps, reportHealth PostWorkspaceAppHealth) { - r := retry.New(time.Second, 30*time.Second) - for { - err := func() error { - apps, err := fetchApps(ctx) - if err != nil { - return xerrors.Errorf("getting workspace apps: %w", err) - } +type WorkspaceAppHealthReporter func(ctx context.Context) - if len(apps) == 0 { - return nil - } +func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) WorkspaceAppHealthReporter { + return func(ctx context.Context) { + r := retry.New(time.Second, 30*time.Second) + for { + err := func() error { + apps, err := client.WorkspaceAgentApps(ctx) + if err != nil { + return xerrors.Errorf("getting workspace apps: %w", err) + } - health := make(map[string]codersdk.WorkspaceAppHealth, 0) - for _, app := range apps { - health[app.Name] = app.Health - } + if len(apps) == 0 { + return nil + } - tickers := make(chan string) - for _, app := range apps { - if shouldStartTicker(app) { - t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) - go func() { - for { - select { - case <-ctx.Done(): - return - case <-t.C: - tickers <- app.Name - } - } - }() + health := make(map[string]codersdk.WorkspaceAppHealth, 0) + for _, app := range apps { + health[app.Name] = app.Health } - } - var mu sync.RWMutex - var failures map[string]int - go func() { - for { - select { - case <-ctx.Done(): - return - case name := <-tickers: - for _, app := range apps { - if app.Name != name { - continue - } - client := &http.Client{ - Timeout: time.Duration(app.HealthcheckInterval), - } - err := func() error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.HealthcheckURL, nil) - if err != nil { - return err + tickers := make(chan string) + for _, app := range apps { + if shouldStartTicker(app) { + t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-t.C: + tickers <- app.Name } - res, err := client.Do(req) - if err != nil { - return err + } + }() + } + } + var mu sync.RWMutex + var failures map[string]int + go func() { + for { + select { + case <-ctx.Done(): + return + case name := <-tickers: + for _, app := range apps { + if app.Name != name { + continue } - res.Body.Close() - if res.StatusCode >= http.StatusInternalServerError { - return xerrors.Errorf("error status code: %d", res.StatusCode) + + client := &http.Client{ + Timeout: time.Duration(app.HealthcheckInterval), } + err := func() error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.HealthcheckURL, nil) + if err != nil { + return err + } + res, err := client.Do(req) + if err != nil { + return err + } + res.Body.Close() + if res.StatusCode >= http.StatusInternalServerError { + return xerrors.Errorf("error status code: %d", res.StatusCode) + } - return nil - }() - if err == nil { - mu.Lock() - failures[app.Name]++ - if failures[app.Name] > int(app.HealthcheckThreshold) { - health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy + return nil + }() + if err == nil { + mu.Lock() + failures[app.Name]++ + if failures[app.Name] > int(app.HealthcheckThreshold) { + health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy + } + mu.Unlock() + } else { + mu.Lock() + failures[app.Name] = 0 + health[app.Name] = codersdk.WorkspaceAppHealthHealthy + mu.Unlock() } - mu.Unlock() - } else { - mu.Lock() - failures[app.Name] = 0 - health[app.Name] = codersdk.WorkspaceAppHealthHealthy - mu.Unlock() } } } - } - }() + }() - reportTicker := time.NewTicker(time.Second) - lastHealth := make(map[string]codersdk.WorkspaceAppHealth, 0) - for { - select { - case <-ctx.Done(): - return nil - case <-reportTicker.C: - mu.RLock() - changed := healthChanged(lastHealth, health) - mu.RUnlock() - if changed { - lastHealth = health - err := reportHealth(ctx, health) - if err != nil { - logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) + reportTicker := time.NewTicker(time.Second) + lastHealth := make(map[string]codersdk.WorkspaceAppHealth, 0) + for { + select { + case <-ctx.Done(): + return nil + case <-reportTicker.C: + mu.RLock() + changed := healthChanged(lastHealth, health) + mu.RUnlock() + if changed { + lastHealth = health + err := client.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: health, + }) + if err != nil { + logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) + } } } } + }() + if err != nil { + logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) + // continue loop with backoff on non-nil errors + r.Wait(ctx) + continue } - }() - if err != nil { - logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) - // continue loop with backoff on non-nil errors - r.Wait(ctx) - continue - } - return + return + } } } diff --git a/cli/agent.go b/cli/agent.go index 837d30eb37176..0b48d30996f3d 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -189,8 +189,10 @@ func workspaceAgent() *cobra.Command { // shells so "gitssh" works! "CODER_AGENT_TOKEN": client.SessionToken, }, - CoordinatorDialer: client.ListenWorkspaceAgentTailnet, - StatsReporter: client.AgentReportStats, + CoordinatorDialer: client.ListenWorkspaceAgentTailnet, + StatsReporter: client.AgentReportStats, + FetchWorkspaceApps: client.WorkspaceAgentApps, + PostWorkspaceAppHealth: client.PostWorkspaceAgentAppHealth, }) <-cmd.Context().Done() return closer.Close() diff --git a/cli/configssh_test.go b/cli/configssh_test.go index e1ae4054b5bea..17cb7a8f7a337 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -107,9 +107,10 @@ func TestConfigSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer func() { _ = agentCloser.Close() diff --git a/cli/speedtest_test.go b/cli/speedtest_test.go index fa432950d1db4..7e6f7ad1f0538 100644 --- a/cli/speedtest_test.go +++ b/cli/speedtest_test.go @@ -24,9 +24,10 @@ func TestSpeedtest(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer agentCloser.Close() coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index b3f148b01519f..cd5905e69fed5 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -89,9 +89,10 @@ func TestSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer func() { _ = agentCloser.Close() @@ -110,9 +111,10 @@ func TestSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + WorkspaceAppHealthReporter: func(context.Context) {}, }) <-ctx.Done() _ = agentCloser.Close() @@ -178,9 +180,10 @@ func TestSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer agentCloser.Close() diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 5052e4f9ba467..a65e82a0954c2 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -602,10 +602,11 @@ func TestTemplateDAUs(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - Logger: slogtest.Make(t, nil), - StatsReporter: agentClient.AgentReportStats, - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil), + StatsReporter: agentClient.AgentReportStats, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer func() { _ = agentCloser.Close() diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index cc2a43df22be7..3a61c604681a9 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -108,9 +108,10 @@ func TestWorkspaceAgentListen(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer func() { _ = agentCloser.Close() @@ -241,9 +242,10 @@ func TestWorkspaceAgentTailnet(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer agentCloser.Close() resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID) @@ -306,9 +308,10 @@ func TestWorkspaceAgentPTY(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + WorkspaceAppHealthReporter: func(context.Context) {}, }) defer func() { _ = agentCloser.Close() diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go index 164ceb2b15de6..b746016da5a3f 100644 --- a/coderd/workspaceapps_test.go +++ b/coderd/workspaceapps_test.go @@ -103,10 +103,11 @@ func setupProxyTest(t *testing.T, workspaceMutators ...func(*codersdk.CreateWork agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - StatsReporter: agentClient.AgentReportStats, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + StatsReporter: agentClient.AgentReportStats, + WorkspaceAppHealthReporter: func(context.Context) {}, }) t.Cleanup(func() { _ = agentCloser.Close() diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index 003d3cddb8b7a..ed904f2656517 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -158,8 +158,9 @@ func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeo go coordinator.ServeAgent(serverConn, agentID) return clientConn, nil }, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo), - ReconnectingPTYTimeout: ptyTimeout, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo), + ReconnectingPTYTimeout: ptyTimeout, + WorkspaceAppHealthReporter: func(context.Context) {}, }) t.Cleanup(func() { _ = closer.Close() From 9129027bcd560a82ce4340f9e6f7eeb1aaa8cb30 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 20:16:32 +0000 Subject: [PATCH 34/64] health --- cli/agent.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cli/agent.go b/cli/agent.go index 0b48d30996f3d..ee6e34d17807e 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -189,10 +189,9 @@ func workspaceAgent() *cobra.Command { // shells so "gitssh" works! "CODER_AGENT_TOKEN": client.SessionToken, }, - CoordinatorDialer: client.ListenWorkspaceAgentTailnet, - StatsReporter: client.AgentReportStats, - FetchWorkspaceApps: client.WorkspaceAgentApps, - PostWorkspaceAppHealth: client.PostWorkspaceAgentAppHealth, + CoordinatorDialer: client.ListenWorkspaceAgentTailnet, + StatsReporter: client.AgentReportStats, + WorkspaceAppHealthReporter: agent.NewWorkspaceAppHealthReporter(logger, client), }) <-cmd.Context().Done() return closer.Close() From e87b48a4977da0e15068eb7ff042e35d88ebeb5c Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 22:01:36 +0000 Subject: [PATCH 35/64] fix types --- codersdk/agentconn.go | 3 +++ codersdk/workspaceagents.go | 8 ++++++++ codersdk/workspaceapps.go | 1 + site/src/api/typesGenerated.ts | 33 --------------------------------- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/codersdk/agentconn.go b/codersdk/agentconn.go index f089d1d2f5f39..3a5dab5158a70 100644 --- a/codersdk/agentconn.go +++ b/codersdk/agentconn.go @@ -30,12 +30,14 @@ var ( // ReconnectingPTYRequest is sent from the client to the server // to pipe data to a PTY. +// @typescript-ignore ReconnectingPTYRequest type ReconnectingPTYRequest struct { Data string `json:"data"` Height uint16 `json:"height"` Width uint16 `json:"width"` } +// @typescript-ignore AgentConn type AgentConn struct { *tailnet.Conn CloseFunc func() @@ -70,6 +72,7 @@ func (c *AgentConn) Close() error { return c.Conn.Close() } +// @typescript-ignore ReconnectingPTYInit type ReconnectingPTYInit struct { ID string Height uint16 diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index d646c1052eeef..3d426e298457e 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -25,15 +25,18 @@ import ( "github.com/coder/retry" ) +// @typescript-ignore GoogleInstanceIdentityToken type GoogleInstanceIdentityToken struct { JSONWebToken string `json:"json_web_token" validate:"required"` } +// @typescript-ignore AWSInstanceIdentityToken type AWSInstanceIdentityToken struct { Signature string `json:"signature" validate:"required"` Document string `json:"document" validate:"required"` } +// @typescript-ignore ReconnectingPTYRequest type AzureInstanceIdentityToken struct { Signature string `json:"signature" validate:"required"` Encoding string `json:"encoding" validate:"required"` @@ -41,20 +44,24 @@ type AzureInstanceIdentityToken struct { // WorkspaceAgentAuthenticateResponse is returned when an instance ID // has been exchanged for a session token. +// @typescript-ignore WorkspaceAgentAuthenticateResponse type WorkspaceAgentAuthenticateResponse struct { SessionToken string `json:"session_token"` } // WorkspaceAgentConnectionInfo returns required information for establishing // a connection with a workspace. +// @typescript-ignore WorkspaceAgentConnectionInfo type WorkspaceAgentConnectionInfo struct { DERPMap *tailcfg.DERPMap `json:"derp_map"` } +// @typescript-ignore PostWorkspaceAgentVersionRequest type PostWorkspaceAgentVersionRequest struct { Version string `json:"version"` } +// @typescript-ignore WorkspaceAgentMetadata type WorkspaceAgentMetadata struct { DERPMap *tailcfg.DERPMap `json:"derpmap"` EnvironmentVariables map[string]string `json:"environment_variables"` @@ -427,6 +434,7 @@ func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, agentID, rec // Stats records the Agent's network connection statistics for use in // user-facing metrics and debugging. // Each member value must be written and read with atomic. +// @typescript-ignore AgentStats type AgentStats struct { NumConns int64 `json:"num_comms"` RxBytes int64 `json:"rx_bytes"` diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 6e31f1411c996..1c5388b21223d 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -30,6 +30,7 @@ type WorkspaceApp struct { Health WorkspaceAppHealth `json:"health"` } +// @typescript-ignore PostWorkspaceAppHealthsRequest type PostWorkspaceAppHealthsRequest struct { // Healths is a map of the workspace app name and the health of the app. Healths map[string]WorkspaceAppHealth diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 99969936ab89a..94a21f5013e0a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -12,12 +12,6 @@ export interface APIKey { readonly lifetime_seconds: number } -// From codersdk/workspaceagents.go -export interface AWSInstanceIdentityToken { - readonly signature: string - readonly document: string -} - // From codersdk/licenses.go export interface AddLicenseRequest { readonly license: string @@ -250,11 +244,6 @@ export interface GitSSHKey { readonly public_key: string } -// From codersdk/workspaceagents.go -export interface GoogleInstanceIdentityToken { - readonly json_web_token: string -} - // From codersdk/licenses.go export interface License { readonly id: number @@ -331,16 +320,6 @@ export interface ParameterSchema { readonly validation_contains?: string[] } -// From codersdk/workspaceagents.go -export interface PostWorkspaceAgentVersionRequest { - readonly version: string -} - -// From codersdk/workspaceapps.go -export interface PostWorkspaceAppHealthsRequest { - readonly Healths: Record -} - // From codersdk/provisionerdaemons.go export interface ProvisionerDaemon { readonly id: string @@ -581,18 +560,6 @@ export interface WorkspaceAgent { readonly latency?: Record } -// From codersdk/workspaceagents.go -export interface WorkspaceAgentAuthenticateResponse { - readonly session_token: string -} - -// From codersdk/workspaceagents.go -export interface WorkspaceAgentConnectionInfo { - // Named type "tailscale.com/tailcfg.DERPMap" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly derp_map?: any -} - // From codersdk/workspaceresources.go export interface WorkspaceAgentInstanceMetadata { readonly jail_orchestrator: string From 2d5d27ac4f01870dfae1ffd2b0556e90e4385cc1 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 20 Sep 2022 22:26:44 +0000 Subject: [PATCH 36/64] handle context --- agent/agent_test.go | 1 + agent/apphealth.go | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index 3499ef5663414..48aed20fbaeea 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -628,6 +628,7 @@ func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeo return nil }), nil }, + WorkspaceAppHealthReporter: func(ctx context.Context) {}, }) t.Cleanup(func() { _ = closer.Close() diff --git a/agent/apphealth.go b/agent/apphealth.go index ad9669670f208..1333ff8c6abe0 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -22,6 +22,9 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) err := func() error { apps, err := client.WorkspaceAgentApps(ctx) if err != nil { + if xerrors.Is(err, context.Canceled) { + return nil + } return xerrors.Errorf("getting workspace apps: %w", err) } @@ -125,8 +128,9 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) if err != nil { logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) // continue loop with backoff on non-nil errors - r.Wait(ctx) - continue + if r.Wait(ctx) { + continue + } } return From fec256d8832e868056e9f23014e8c1e69371c336 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 21 Sep 2022 14:57:52 +0000 Subject: [PATCH 37/64] handle nil interface --- agent/agent.go | 7 +++---- agent/agent_test.go | 1 - cli/configssh_test.go | 7 +++---- cli/speedtest_test.go | 7 +++---- cli/ssh_test.go | 21 +++++++++------------ coderd/templates_test.go | 9 ++++----- coderd/workspaceagents_test.go | 21 +++++++++------------ coderd/workspaceapps_test.go | 9 ++++----- coderd/wsconncache/wsconncache_test.go | 5 ++--- 9 files changed, 37 insertions(+), 50 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index fd926b6dd044c..d25ee0f87938f 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -67,9 +67,6 @@ type CoordinatorDialer func(context.Context) (net.Conn, error) // FetchMetadata is a function to obtain metadata for the agent. type FetchMetadata func(context.Context) (codersdk.WorkspaceAgentMetadata, error) -type FetchWorkspaceApps func(context.Context) ([]codersdk.WorkspaceApp, error) -type PostWorkspaceAppHealth func(context.Context, codersdk.PostWorkspaceAppHealthsRequest) error - func New(options Options) io.Closer { if options.ReconnectingPTYTimeout == 0 { options.ReconnectingPTYTimeout = 5 * time.Minute @@ -158,7 +155,9 @@ func (a *agent) run(ctx context.Context) { go a.runTailnet(ctx, metadata.DERPMap) } - go a.workspaceAppHealthReporter(ctx) + if a.workspaceAppHealthReporter != nil { + go a.workspaceAppHealthReporter(ctx) + } } func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) { diff --git a/agent/agent_test.go b/agent/agent_test.go index 48aed20fbaeea..3499ef5663414 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -628,7 +628,6 @@ func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeo return nil }), nil }, - WorkspaceAppHealthReporter: func(ctx context.Context) {}, }) t.Cleanup(func() { _ = closer.Close() diff --git a/cli/configssh_test.go b/cli/configssh_test.go index 17cb7a8f7a337..e1ae4054b5bea 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -107,10 +107,9 @@ func TestConfigSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), }) defer func() { _ = agentCloser.Close() diff --git a/cli/speedtest_test.go b/cli/speedtest_test.go index 7e6f7ad1f0538..fa432950d1db4 100644 --- a/cli/speedtest_test.go +++ b/cli/speedtest_test.go @@ -24,10 +24,9 @@ func TestSpeedtest(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), }) defer agentCloser.Close() coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index cd5905e69fed5..b3f148b01519f 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -89,10 +89,9 @@ func TestSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), }) defer func() { _ = agentCloser.Close() @@ -111,10 +110,9 @@ func TestSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), }) <-ctx.Done() _ = agentCloser.Close() @@ -180,10 +178,9 @@ func TestSSH(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = agentToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), }) defer agentCloser.Close() diff --git a/coderd/templates_test.go b/coderd/templates_test.go index a65e82a0954c2..5052e4f9ba467 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -602,11 +602,10 @@ func TestTemplateDAUs(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - Logger: slogtest.Make(t, nil), - StatsReporter: agentClient.AgentReportStats, - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - WorkspaceAppHealthReporter: func(context.Context) {}, + Logger: slogtest.Make(t, nil), + StatsReporter: agentClient.AgentReportStats, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, }) defer func() { _ = agentCloser.Close() diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 3a61c604681a9..cc2a43df22be7 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -108,10 +108,9 @@ func TestWorkspaceAgentListen(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), }) defer func() { _ = agentCloser.Close() @@ -242,10 +241,9 @@ func TestWorkspaceAgentTailnet(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), }) defer agentCloser.Close() resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID) @@ -308,10 +306,9 @@ func TestWorkspaceAgentPTY(t *testing.T) { agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), }) defer func() { _ = agentCloser.Close() diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go index b746016da5a3f..164ceb2b15de6 100644 --- a/coderd/workspaceapps_test.go +++ b/coderd/workspaceapps_test.go @@ -103,11 +103,10 @@ func setupProxyTest(t *testing.T, workspaceMutators ...func(*codersdk.CreateWork agentClient := codersdk.New(client.URL) agentClient.SessionToken = authToken agentCloser := agent.New(agent.Options{ - FetchMetadata: agentClient.WorkspaceAgentMetadata, - CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, - Logger: slogtest.Make(t, nil).Named("agent"), - StatsReporter: agentClient.AgentReportStats, - WorkspaceAppHealthReporter: func(context.Context) {}, + FetchMetadata: agentClient.WorkspaceAgentMetadata, + CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet, + Logger: slogtest.Make(t, nil).Named("agent"), + StatsReporter: agentClient.AgentReportStats, }) t.Cleanup(func() { _ = agentCloser.Close() diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index ed904f2656517..003d3cddb8b7a 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -158,9 +158,8 @@ func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeo go coordinator.ServeAgent(serverConn, agentID) return clientConn, nil }, - Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo), - ReconnectingPTYTimeout: ptyTimeout, - WorkspaceAppHealthReporter: func(context.Context) {}, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo), + ReconnectingPTYTimeout: ptyTimeout, }) t.Cleanup(func() { _ = closer.Close() From a3330c7d6a6f5cf7c5d0b0d63654c19c028cab88 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 21 Sep 2022 15:58:45 +0000 Subject: [PATCH 38/64] add test for agent app health routes --- agent/apphealth.go | 2 +- coderd/workspaceagents.go | 27 +++++---- coderd/workspaceagents_test.go | 108 +++++++++++++++++++++++++++++++++ codersdk/workspaceagents.go | 12 ++-- 4 files changed, 132 insertions(+), 17 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 1333ff8c6abe0..1adea66ccc1ae 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -54,7 +54,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } } var mu sync.RWMutex - var failures map[string]int + failures := make(map[string]int, 0) go func() { for { select { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 4963aed611bae..bae79097de059 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -701,6 +701,13 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) return } + if req.Healths == nil || len(req.Healths) == 0 { + httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + Message: "Health field is empty", + }) + return + } + apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ @@ -711,8 +718,8 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) } var newApps []database.WorkspaceApp - for name, health := range req.Healths { - found := func() *database.WorkspaceApp { + for name, newHealth := range req.Healths { + old := func() *database.WorkspaceApp { for _, app := range apps { if app.Name == name { return &app @@ -721,7 +728,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) return nil }() - if found == nil { + if old == nil { httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ Message: "Error setting workspace app health", Detail: xerrors.Errorf("workspace app name %s not found", name).Error(), @@ -729,7 +736,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) return } - if !found.HealthcheckEnabled { + if !old.HealthcheckEnabled { httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ Message: "Error setting workspace app health", Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(), @@ -737,27 +744,25 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) return } - switch health { + switch newHealth { case codersdk.WorkspaceAppHealthInitializing: - found.Health = database.WorkspaceAppHealthInitializing case codersdk.WorkspaceAppHealthHealthy: - found.Health = database.WorkspaceAppHealthHealthy case codersdk.WorkspaceAppHealthUnhealthy: - found.Health = database.WorkspaceAppHealthUnhealthy default: httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ Message: "Error setting workspace app health", - Detail: xerrors.Errorf("workspace app health %s is not a valid value", health).Error(), + Detail: xerrors.Errorf("workspace app health %s is not a valid value", newHealth).Error(), }) return } // don't save if the value hasn't changed - if found.Health == database.WorkspaceAppHealth(health) { + if old.Health == database.WorkspaceAppHealth(newHealth) { continue } + old.Health = database.WorkspaceAppHealth(newHealth) - newApps = append(newApps, *found) + newApps = append(newApps, *old) } for _, app := range newApps { diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index cc2a43df22be7..83ac2215aef2f 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -363,3 +363,111 @@ func TestWorkspaceAgentPTY(t *testing.T) { expectLine(matchEchoCommand) expectLine(matchEchoOutput) } + +func TestWorkspaceAgentAppHealth(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + apps := []*proto.App{ + { + Name: "code-server", + Command: "some-command", + Url: "http://localhost:3000", + Icon: "/code.svg", + }, + { + Name: "code-server-2", + Command: "some-command", + Url: "http://localhost:3000", + Icon: "/code.svg", + HealthcheckEnabled: true, + HealthcheckUrl: "http://localhost:3000", + HealthcheckInterval: 5, + HealthcheckThreshold: 6, + }, + } + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*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, + }, + Apps: apps, + }}, + }}, + }, + }, + }}, + }) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + agentClient := codersdk.New(client.URL) + agentClient.SessionToken = authToken + + apiApps, err := agentClient.WorkspaceAgentApps(ctx) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, apiApps[0].Health) + require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, apiApps[1].Health) + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{}) + require.Error(t, err) + // empty + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{}) + require.Error(t, err) + // invalid name + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: map[string]codersdk.WorkspaceAppHealth{ + "bad-name": codersdk.WorkspaceAppHealthDisabled, + }, + }) + require.Error(t, err) + // app.HealthEnabled == false + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: map[string]codersdk.WorkspaceAppHealth{ + "code-server": codersdk.WorkspaceAppHealthInitializing, + }, + }) + require.Error(t, err) + // invalid value + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: map[string]codersdk.WorkspaceAppHealth{ + "code-server-2": codersdk.WorkspaceAppHealth("bad-value"), + }, + }) + require.Error(t, err) + // update to healthy + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: map[string]codersdk.WorkspaceAppHealth{ + "code-server-2": codersdk.WorkspaceAppHealthHealthy, + }, + }) + require.NoError(t, err) + apiApps, err = agentClient.WorkspaceAgentApps(ctx) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, apiApps[1].Health) + // update to unhealthy + err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: map[string]codersdk.WorkspaceAppHealth{ + "code-server-2": codersdk.WorkspaceAppHealthUnhealthy, + }, + }) + require.NoError(t, err) + apiApps, err = agentClient.WorkspaceAgentApps(ctx) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, apiApps[1].Health) +} diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 3d426e298457e..e876fdafd9940 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -363,7 +363,7 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge // MyWorkspaceAgent returns the requesting agent. func (c *Client) WorkspaceAgentApps(ctx context.Context) ([]WorkspaceApp, error) { - res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me", nil) + res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/apps", nil) if err != nil { return nil, err } @@ -377,13 +377,15 @@ func (c *Client) WorkspaceAgentApps(ctx context.Context) ([]WorkspaceApp, error) // PostWorkspaceAgentAppHealth updates the workspace agent app health status. func (c *Client) PostWorkspaceAgentAppHealth(ctx context.Context, req PostWorkspaceAppHealthsRequest) error { - res, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/version", req) + res, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/app-health", req) if err != nil { + return err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { return readBodyAsError(res) } - // Discord the response - _, _ = io.Copy(io.Discard, res.Body) - _ = res.Body.Close() + return nil } From 18d05a9a13daccabcb1fc21985f211882ad69b1f Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 21 Sep 2022 16:04:12 +0000 Subject: [PATCH 39/64] fix test --- coderd/workspaceresources_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 28e24372615fc..286df8fdec3ba 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -82,7 +82,7 @@ func TestWorkspaceResource(t *testing.T) { Icon: "/code.svg", }, { - Name: "code-server", + Name: "code-server-2", Command: "some-command", Url: "http://localhost:3000", Icon: "/code.svg", From e6dc74233dccb333abe8737f238fda4ed05c7467 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 21 Sep 2022 18:41:42 +0000 Subject: [PATCH 40/64] fix json --- codersdk/workspaceapps.go | 2 +- site/src/api/typesGenerated.ts | 2 +- site/src/testHelpers/entities.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 1c5388b21223d..bf218671f47c0 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -24,7 +24,7 @@ type WorkspaceApp struct { HealthcheckEnabled bool `json:"healthcheck_enabled"` HealthcheckURL string `json:"healthcheck_url"` // HealthcheckInterval specifies the seconds between each health check. - HealthcheckInterval int32 `json:"healthcheck_period"` + HealthcheckInterval int32 `json:"healthcheck_interval"` // HealthcheckThreshold specifies the number of consecutive failed health checks before returning "unhealthy". HealthcheckThreshold int32 `json:"healthcheck_threshold"` Health WorkspaceAppHealth `json:"health"` diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 94a21f5013e0a..086a9390c2def 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -590,7 +590,7 @@ export interface WorkspaceApp { readonly icon?: string readonly healthcheck_enabled: boolean readonly healthcheck_url: string - readonly healthcheck_period: number + readonly healthcheck_interval: number readonly healthcheck_threshold: number readonly health: WorkspaceAppHealth } diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 7e56e9a6d027b..e2e24ef4d29da 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -327,7 +327,7 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { health: "disabled", healthcheck_enabled: false, healthcheck_url: "", - healthcheck_period: 0, + healthcheck_interval: 0, healthcheck_threshold: 0, } From 1947adc3bc5052dfd8a32d87af351b1f07e32f6a Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 21 Sep 2022 20:47:34 +0000 Subject: [PATCH 41/64] remove healthcheck_enabled --- agent/apphealth.go | 2 +- coderd/database/databasefake/databasefake.go | 1 - coderd/database/dump.sql | 1 - .../000051_workspace_app_health.down.sql | 2 +- .../000051_workspace_app_health.up.sql | 1 - coderd/database/models.go | 1 - coderd/database/queries.sql.go | 18 +- coderd/database/queries/workspaceapps.sql | 3 +- coderd/provisionerdaemons.go | 3 +- coderd/workspaceagents.go | 3 +- coderd/workspaceagents_test.go | 1 - coderd/workspaceresources_test.go | 3 - codersdk/workspaceapps.go | 11 +- provisioner/terraform/resources.go | 2 - provisionersdk/proto/provisioner.pb.go | 261 +++++++++--------- provisionersdk/proto/provisioner.proto | 1 - site/src/api/typesGenerated.ts | 1 - site/src/testHelpers/entities.ts | 1 - 18 files changed, 141 insertions(+), 175 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 1adea66ccc1ae..df74f81fb6387 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -139,7 +139,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } func shouldStartTicker(app codersdk.WorkspaceApp) bool { - return app.HealthcheckEnabled && app.HealthcheckInterval > 0 && app.HealthcheckThreshold > 0 && app.HealthcheckURL != "" + return app.HealthcheckURL != "" && app.HealthcheckInterval > 0 && app.HealthcheckThreshold > 0 } func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]codersdk.WorkspaceAppHealth) bool { diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index a437977bd4a78..c502780145ccd 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2027,7 +2027,6 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW Command: arg.Command, Url: arg.Url, RelativePath: arg.RelativePath, - HealthcheckEnabled: arg.HealthcheckEnabled, HealthcheckUrl: arg.HealthcheckUrl, HealthcheckInterval: arg.HealthcheckInterval, HealthcheckThreshold: arg.HealthcheckThreshold, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 1403b82c54da5..a09e90e519530 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -350,7 +350,6 @@ CREATE TABLE workspace_apps ( command character varying(65534), url character varying(65534), relative_path boolean DEFAULT false NOT NULL, - healthcheck_enabled boolean DEFAULT false NOT NULL, healthcheck_url text DEFAULT ''::text NOT NULL, healthcheck_interval integer DEFAULT 0 NOT NULL, healthcheck_threshold integer DEFAULT 0 NOT NULL, diff --git a/coderd/database/migrations/000051_workspace_app_health.down.sql b/coderd/database/migrations/000051_workspace_app_health.down.sql index 98173e3a94cd0..33508eb9fc3d0 100644 --- a/coderd/database/migrations/000051_workspace_app_health.down.sql +++ b/coderd/database/migrations/000051_workspace_app_health.down.sql @@ -1,5 +1,5 @@ ALTER TABLE ONLY workspace_apps - DROP COLUMN IF EXISTS healthcheck_enabled, + DROP COLUMN IF EXISTS healthcheck_url, DROP COLUMN IF EXISTS healthcheck_interval, DROP COLUMN IF EXISTS healthcheck_threshold, DROP COLUMN IF EXISTS health; diff --git a/coderd/database/migrations/000051_workspace_app_health.up.sql b/coderd/database/migrations/000051_workspace_app_health.up.sql index b7bb8a63d2f95..3546174b40b85 100644 --- a/coderd/database/migrations/000051_workspace_app_health.up.sql +++ b/coderd/database/migrations/000051_workspace_app_health.up.sql @@ -1,7 +1,6 @@ CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', 'unhealthy'); ALTER TABLE ONLY workspace_apps - ADD COLUMN IF NOT EXISTS healthcheck_enabled boolean NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS healthcheck_url text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS healthcheck_interval int NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0, diff --git a/coderd/database/models.go b/coderd/database/models.go index f7da8dd381069..0e24548ec450d 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -604,7 +604,6 @@ type WorkspaceApp struct { Command sql.NullString `db:"command" json:"command"` Url sql.NullString `db:"url" json:"url"` RelativePath bool `db:"relative_path" json:"relative_path"` - HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"` HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"` HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index f67f8ddd7059a..424d72f4efddb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up } const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2 ` type GetWorkspaceAppByAgentIDAndNameParams struct { @@ -3869,7 +3869,6 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge &i.Command, &i.Url, &i.RelativePath, - &i.HealthcheckEnabled, &i.HealthcheckUrl, &i.HealthcheckInterval, &i.HealthcheckThreshold, @@ -3879,7 +3878,7 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -3900,7 +3899,6 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.Command, &i.Url, &i.RelativePath, - &i.HealthcheckEnabled, &i.HealthcheckUrl, &i.HealthcheckInterval, &i.HealthcheckThreshold, @@ -3920,7 +3918,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -3941,7 +3939,6 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.Command, &i.Url, &i.RelativePath, - &i.HealthcheckEnabled, &i.HealthcheckUrl, &i.HealthcheckInterval, &i.HealthcheckThreshold, @@ -3961,7 +3958,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. } const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many -SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC +SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC ` func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) { @@ -3982,7 +3979,6 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt &i.Command, &i.Url, &i.RelativePath, - &i.HealthcheckEnabled, &i.HealthcheckUrl, &i.HealthcheckInterval, &i.HealthcheckThreshold, @@ -4012,14 +4008,13 @@ INSERT INTO command, url, relative_path, - healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health ` type InsertWorkspaceAppParams struct { @@ -4031,7 +4026,6 @@ type InsertWorkspaceAppParams struct { Command sql.NullString `db:"command" json:"command"` Url sql.NullString `db:"url" json:"url"` RelativePath bool `db:"relative_path" json:"relative_path"` - HealthcheckEnabled bool `db:"healthcheck_enabled" json:"healthcheck_enabled"` HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"` HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"` HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"` @@ -4048,7 +4042,6 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace arg.Command, arg.Url, arg.RelativePath, - arg.HealthcheckEnabled, arg.HealthcheckUrl, arg.HealthcheckInterval, arg.HealthcheckThreshold, @@ -4064,7 +4057,6 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.Command, &i.Url, &i.RelativePath, - &i.HealthcheckEnabled, &i.HealthcheckUrl, &i.HealthcheckInterval, &i.HealthcheckThreshold, diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index 2976f66d72ae5..61ea2d7e397a4 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -21,14 +21,13 @@ INSERT INTO command, url, relative_path, - healthcheck_enabled, healthcheck_url, healthcheck_interval, healthcheck_threshold, health ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *; -- name: UpdateWorkspaceAppHealthByID :exec UPDATE diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 9025620372cb5..6c98bd528ec52 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -813,7 +813,7 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. for _, app := range prAgent.Apps { health := database.WorkspaceAppHealthDisabled - if app.HealthcheckEnabled { + if app.HealthcheckUrl != "" { health = database.WorkspaceAppHealthInitializing } @@ -832,7 +832,6 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. Valid: app.Url != "", }, RelativePath: app.RelativePath, - HealthcheckEnabled: app.HealthcheckEnabled, HealthcheckUrl: app.HealthcheckUrl, HealthcheckInterval: app.HealthcheckInterval, HealthcheckThreshold: app.HealthcheckThreshold, diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index bae79097de059..1918c155eea72 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -452,7 +452,6 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { Name: dbApp.Name, Command: dbApp.Command.String, Icon: dbApp.Icon, - HealthcheckEnabled: dbApp.HealthcheckEnabled, HealthcheckURL: dbApp.HealthcheckUrl, HealthcheckInterval: dbApp.HealthcheckInterval, HealthcheckThreshold: dbApp.HealthcheckThreshold, @@ -736,7 +735,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) return } - if !old.HealthcheckEnabled { + if old.HealthcheckUrl == "" { httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ Message: "Error setting workspace app health", Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(), diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 83ac2215aef2f..790b2e2a07080 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -383,7 +383,6 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { Command: "some-command", Url: "http://localhost:3000", Icon: "/code.svg", - HealthcheckEnabled: true, HealthcheckUrl: "http://localhost:3000", HealthcheckInterval: 5, HealthcheckThreshold: 6, diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 286df8fdec3ba..f6042a81dbe3d 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -86,7 +86,6 @@ func TestWorkspaceResource(t *testing.T) { Command: "some-command", Url: "http://localhost:3000", Icon: "/code.svg", - HealthcheckEnabled: true, HealthcheckUrl: "http://localhost:3000", HealthcheckInterval: 5, HealthcheckThreshold: 6, @@ -131,7 +130,6 @@ func TestWorkspaceResource(t *testing.T) { require.EqualValues(t, app.Icon, got.Icon) require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health) - require.EqualValues(t, app.HealthcheckEnabled, got.HealthcheckEnabled) require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckURL) require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) @@ -141,7 +139,6 @@ func TestWorkspaceResource(t *testing.T) { require.EqualValues(t, app.Icon, got.Icon) require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, got.Health) - require.EqualValues(t, app.HealthcheckEnabled, got.HealthcheckEnabled) require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckURL) require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index bf218671f47c0..74f8ca1b08c71 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -20,14 +20,15 @@ type WorkspaceApp struct { Command string `json:"command,omitempty"` // Icon is a relative path or external URL that specifies // an icon to be displayed in the dashboard. - Icon string `json:"icon,omitempty"` - HealthcheckEnabled bool `json:"healthcheck_enabled"` - HealthcheckURL string `json:"healthcheck_url"` + Icon string `json:"icon,omitempty"` + // HealthcheckURL specifies the url to check for the app health. + HealthcheckURL string `json:"healthcheck_url"` // HealthcheckInterval specifies the seconds between each health check. HealthcheckInterval int32 `json:"healthcheck_interval"` // HealthcheckThreshold specifies the number of consecutive failed health checks before returning "unhealthy". - HealthcheckThreshold int32 `json:"healthcheck_threshold"` - Health WorkspaceAppHealth `json:"health"` + HealthcheckThreshold int32 `json:"healthcheck_threshold"` + // Health specifies the current status of the app's health. + Health WorkspaceAppHealth `json:"health"` } // @typescript-ignore PostWorkspaceAppHealthsRequest diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 412598317be92..d9969b2f21460 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -31,7 +31,6 @@ type agentAppAttributes struct { URL string `mapstructure:"url"` Command string `mapstructure:"command"` RelativePath bool `mapstructure:"relative_path"` - HealthcheckEnabled bool `mapstructure:"healthcheck_enabled"` HealthcheckURL string `mapstructure:"healthcheck_url"` HealthcheckInterval int32 `mapstructure:"healthcheck_interval"` HealthcheckThreshold int32 `mapstructure:"healthcheck_threshold"` @@ -234,7 +233,6 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res Url: attrs.URL, Icon: attrs.Icon, RelativePath: attrs.RelativePath, - HealthcheckEnabled: attrs.HealthcheckEnabled, HealthcheckUrl: attrs.HealthcheckURL, HealthcheckInterval: attrs.HealthcheckInterval, HealthcheckThreshold: attrs.HealthcheckThreshold, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 6fee4a0fb5487..92d22202709e5 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -855,7 +855,6 @@ type App struct { Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"` - HealthcheckEnabled bool `protobuf:"varint,6,opt,name=healthcheck_enabled,json=healthcheckEnabled,proto3" json:"healthcheck_enabled,omitempty"` HealthcheckUrl string `protobuf:"bytes,7,opt,name=healthcheck_url,json=healthcheckUrl,proto3" json:"healthcheck_url,omitempty"` HealthcheckInterval int32 `protobuf:"varint,8,opt,name=healthcheck_interval,json=healthcheckInterval,proto3" json:"healthcheck_interval,omitempty"` HealthcheckThreshold int32 `protobuf:"varint,9,opt,name=healthcheck_threshold,json=healthcheckThreshold,proto3" json:"healthcheck_threshold,omitempty"` @@ -928,13 +927,6 @@ func (x *App) GetRelativePath() bool { return false } -func (x *App) GetHealthcheckEnabled() bool { - if x != nil { - return x.HealthcheckEnabled - } - return false -} - func (x *App) GetHealthcheckUrl() string { if x != nil { return x.HealthcheckUrl @@ -1912,7 +1904,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 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, 0xc0, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x8f, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, @@ -1921,134 +1913,131 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, - 0x2f, 0x0a, 0x13, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x31, 0x0a, 0x14, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x33, 0x0a, 0x15, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x74, 0x68, 0x72, 0x65, - 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x22, 0xad, 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, 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, 0xfc, 0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 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, + 0x27, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x31, 0x0a, 0x14, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x33, 0x0a, 0x15, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, + 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, + 0x22, 0xad, 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, 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, 0xfc, 0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 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, + 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, + 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, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 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, 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, 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, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x08, 0x0a, + 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x18, 0x02, 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, 0x6b, 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, 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, - 0x22, 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, - 0x02, 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, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 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, 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, 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, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x08, - 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 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, 0x6b, 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, 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, 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, + 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, 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 3dde826e27ece..01344892dd904 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -94,7 +94,6 @@ message App { string url = 3; string icon = 4; bool relative_path = 5; - bool healthcheck_enabled = 6; string healthcheck_url = 7; int32 healthcheck_interval = 8; int32 healthcheck_threshold = 9; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 086a9390c2def..62dc475a082cb 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -588,7 +588,6 @@ export interface WorkspaceApp { readonly name: string readonly command?: string readonly icon?: string - readonly healthcheck_enabled: boolean readonly healthcheck_url: string readonly healthcheck_interval: number readonly healthcheck_threshold: number diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index e2e24ef4d29da..1979193ce1444 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -325,7 +325,6 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { name: "test-app", icon: "", health: "disabled", - healthcheck_enabled: false, healthcheck_url: "", healthcheck_interval: 0, healthcheck_threshold: 0, From bb5aa3e9264ddd57f83dd9d0ca544d7aaa3daf93 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 15:19:55 +0000 Subject: [PATCH 42/64] add healthcheck type --- agent/apphealth.go | 10 +- coderd/provisionerdaemons.go | 11 +- coderd/workspaceagents.go | 18 +- coderd/workspaceagents_test.go | 16 +- coderd/workspaceresources_test.go | 28 +- codersdk/workspaceapps.go | 20 +- provisioner/terraform/resources.go | 18 +- provisionersdk/proto/provisioner.pb.go | 526 ++++++++++++++----------- provisionersdk/proto/provisioner.proto | 11 +- site/src/api/typesGenerated.ts | 11 +- 10 files changed, 378 insertions(+), 291 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index df74f81fb6387..30814e244661a 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -40,7 +40,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) tickers := make(chan string) for _, app := range apps { if shouldStartTicker(app) { - t := time.NewTicker(time.Duration(app.HealthcheckInterval) * time.Second) + t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second) go func() { for { select { @@ -67,10 +67,10 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } client := &http.Client{ - Timeout: time.Duration(app.HealthcheckInterval), + Timeout: time.Duration(app.Healthcheck.Interval), } err := func() error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.HealthcheckURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) if err != nil { return err } @@ -88,7 +88,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) if err == nil { mu.Lock() failures[app.Name]++ - if failures[app.Name] > int(app.HealthcheckThreshold) { + if failures[app.Name] > int(app.Healthcheck.Threshold) { health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy } mu.Unlock() @@ -139,7 +139,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } func shouldStartTicker(app codersdk.WorkspaceApp) bool { - return app.HealthcheckURL != "" && app.HealthcheckInterval > 0 && app.HealthcheckThreshold > 0 + return app.Healthcheck.URL != "" && app.Healthcheck.Interval > 0 && app.Healthcheck.Threshold > 0 } func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]codersdk.WorkspaceAppHealth) bool { diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 6c98bd528ec52..aca06000dc98a 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -813,7 +813,10 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. for _, app := range prAgent.Apps { health := database.WorkspaceAppHealthDisabled - if app.HealthcheckUrl != "" { + if app.Healthcheck == nil { + app.Healthcheck = &sdkproto.Healthcheck{} + } + if app.Healthcheck.Url != "" { health = database.WorkspaceAppHealthInitializing } @@ -832,9 +835,9 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. Valid: app.Url != "", }, RelativePath: app.RelativePath, - HealthcheckUrl: app.HealthcheckUrl, - HealthcheckInterval: app.HealthcheckInterval, - HealthcheckThreshold: app.HealthcheckThreshold, + HealthcheckUrl: app.Healthcheck.Url, + HealthcheckInterval: app.Healthcheck.Interval, + HealthcheckThreshold: app.Healthcheck.Threshold, Health: health, }) if err != nil { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 1918c155eea72..bba389cbf3809 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -448,14 +448,16 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { apps := make([]codersdk.WorkspaceApp, 0) for _, dbApp := range dbApps { apps = append(apps, codersdk.WorkspaceApp{ - ID: dbApp.ID, - Name: dbApp.Name, - Command: dbApp.Command.String, - Icon: dbApp.Icon, - HealthcheckURL: dbApp.HealthcheckUrl, - HealthcheckInterval: dbApp.HealthcheckInterval, - HealthcheckThreshold: dbApp.HealthcheckThreshold, - Health: codersdk.WorkspaceAppHealth(dbApp.Health), + ID: dbApp.ID, + Name: dbApp.Name, + Command: dbApp.Command.String, + Icon: dbApp.Icon, + Healthcheck: codersdk.Healthcheck{ + URL: dbApp.HealthcheckUrl, + Interval: dbApp.HealthcheckInterval, + Threshold: dbApp.HealthcheckThreshold, + }, + Health: codersdk.WorkspaceAppHealth(dbApp.Health), }) } return apps diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 790b2e2a07080..fba4ce9953821 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -379,13 +379,15 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { Icon: "/code.svg", }, { - Name: "code-server-2", - Command: "some-command", - Url: "http://localhost:3000", - Icon: "/code.svg", - HealthcheckUrl: "http://localhost:3000", - HealthcheckInterval: 5, - HealthcheckThreshold: 6, + Name: "code-server-2", + Command: "some-command", + Url: "http://localhost:3000", + Icon: "/code.svg", + Healthcheck: &proto.Healthcheck{ + Url: "http://localhost:3000", + Interval: 5, + Threshold: 6, + }, }, } version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index f6042a81dbe3d..6fd0f97fb5e57 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -82,13 +82,15 @@ func TestWorkspaceResource(t *testing.T) { Icon: "/code.svg", }, { - Name: "code-server-2", - Command: "some-command", - Url: "http://localhost:3000", - Icon: "/code.svg", - HealthcheckUrl: "http://localhost:3000", - HealthcheckInterval: 5, - HealthcheckThreshold: 6, + Name: "code-server-2", + Command: "some-command", + Url: "http://localhost:3000", + Icon: "/code.svg", + Healthcheck: &proto.Healthcheck{ + Url: "http://localhost:3000", + Interval: 5, + Threshold: 6, + }, }, } version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ @@ -130,18 +132,18 @@ func TestWorkspaceResource(t *testing.T) { require.EqualValues(t, app.Icon, got.Icon) require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health) - require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckURL) - require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) - require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) + require.EqualValues(t, "", got.Healthcheck.URL) + require.EqualValues(t, 0, got.Healthcheck.Interval) + require.EqualValues(t, 0, got.Healthcheck.Threshold) got = agent.Apps[1] app = apps[1] require.EqualValues(t, app.Command, got.Command) require.EqualValues(t, app.Icon, got.Icon) require.EqualValues(t, app.Name, got.Name) require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, got.Health) - require.EqualValues(t, app.HealthcheckUrl, got.HealthcheckURL) - require.EqualValues(t, app.HealthcheckInterval, got.HealthcheckInterval) - require.EqualValues(t, app.HealthcheckThreshold, got.HealthcheckThreshold) + require.EqualValues(t, app.Healthcheck.Url, got.Healthcheck.URL) + require.EqualValues(t, app.Healthcheck.Interval, got.Healthcheck.Interval) + require.EqualValues(t, app.Healthcheck.Threshold, got.Healthcheck.Threshold) }) t.Run("Metadata", func(t *testing.T) { diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 74f8ca1b08c71..168e3c0d597c9 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -21,14 +21,18 @@ type WorkspaceApp struct { // Icon is a relative path or external URL that specifies // an icon to be displayed in the dashboard. Icon string `json:"icon,omitempty"` - // HealthcheckURL specifies the url to check for the app health. - HealthcheckURL string `json:"healthcheck_url"` - // HealthcheckInterval specifies the seconds between each health check. - HealthcheckInterval int32 `json:"healthcheck_interval"` - // HealthcheckThreshold specifies the number of consecutive failed health checks before returning "unhealthy". - HealthcheckThreshold int32 `json:"healthcheck_threshold"` - // Health specifies the current status of the app's health. - Health WorkspaceAppHealth `json:"health"` + // Healthcheck specifies the configuration for checking app health. + Healthcheck Healthcheck `json:"healthcheck"` + Health WorkspaceAppHealth `json:"health"` +} + +type Healthcheck struct { + // URL specifies the url to check for the app health. + URL string `json:"url"` + // Interval specifies the seconds between each health check. + Interval int32 `json:"interval"` + // Threshold specifies the number of consecutive failed health checks before returning "unhealthy". + Threshold int32 `json:"threshold"` } // @typescript-ignore PostWorkspaceAppHealthsRequest diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index d9969b2f21460..f62b20fb2472f 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -228,14 +228,16 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res continue } agent.Apps = append(agent.Apps, &proto.App{ - Name: attrs.Name, - Command: attrs.Command, - Url: attrs.URL, - Icon: attrs.Icon, - RelativePath: attrs.RelativePath, - HealthcheckUrl: attrs.HealthcheckURL, - HealthcheckInterval: attrs.HealthcheckInterval, - HealthcheckThreshold: attrs.HealthcheckThreshold, + Name: attrs.Name, + Command: attrs.Command, + Url: attrs.URL, + Icon: attrs.Icon, + RelativePath: attrs.RelativePath, + Healthcheck: &proto.Healthcheck{ + Url: attrs.HealthcheckURL, + Interval: attrs.HealthcheckInterval, + Threshold: attrs.HealthcheckThreshold, + }, }) } } diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 92d22202709e5..e6134519976f3 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -850,14 +850,12 @@ type App struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` - Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` - RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"` - HealthcheckUrl string `protobuf:"bytes,7,opt,name=healthcheck_url,json=healthcheckUrl,proto3" json:"healthcheck_url,omitempty"` - HealthcheckInterval int32 `protobuf:"varint,8,opt,name=healthcheck_interval,json=healthcheckInterval,proto3" json:"healthcheck_interval,omitempty"` - HealthcheckThreshold int32 `protobuf:"varint,9,opt,name=healthcheck_threshold,json=healthcheckThreshold,proto3" json:"healthcheck_threshold,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` + RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"` + Healthcheck *Healthcheck `protobuf:"bytes,6,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"` } func (x *App) Reset() { @@ -927,23 +925,73 @@ func (x *App) GetRelativePath() bool { return false } -func (x *App) GetHealthcheckUrl() string { +func (x *App) GetHealthcheck() *Healthcheck { if x != nil { - return x.HealthcheckUrl + return x.Healthcheck + } + return nil +} + +// Healthcheck represents configuration for checking for app readiness. +type Healthcheck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Interval int32 `protobuf:"varint,2,opt,name=interval,proto3" json:"interval,omitempty"` + Threshold int32 `protobuf:"varint,3,opt,name=threshold,proto3" json:"threshold,omitempty"` +} + +func (x *Healthcheck) Reset() { + *x = Healthcheck{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Healthcheck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Healthcheck) ProtoMessage() {} + +func (x *Healthcheck) ProtoReflect() protoreflect.Message { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9] + 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 Healthcheck.ProtoReflect.Descriptor instead. +func (*Healthcheck) Descriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9} +} + +func (x *Healthcheck) GetUrl() string { + if x != nil { + return x.Url } return "" } -func (x *App) GetHealthcheckInterval() int32 { +func (x *Healthcheck) GetInterval() int32 { if x != nil { - return x.HealthcheckInterval + return x.Interval } return 0 } -func (x *App) GetHealthcheckThreshold() int32 { +func (x *Healthcheck) GetThreshold() int32 { if x != nil { - return x.HealthcheckThreshold + return x.Threshold } return 0 } @@ -965,7 +1013,7 @@ type Resource struct { func (x *Resource) Reset() { *x = Resource{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -978,7 +1026,7 @@ func (x *Resource) String() string { func (*Resource) ProtoMessage() {} func (x *Resource) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -991,7 +1039,7 @@ func (x *Resource) ProtoReflect() protoreflect.Message { // Deprecated: Use Resource.ProtoReflect.Descriptor instead. func (*Resource) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10} } func (x *Resource) GetName() string { @@ -1046,7 +1094,7 @@ type Parse struct { func (x *Parse) Reset() { *x = Parse{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1059,7 +1107,7 @@ func (x *Parse) String() string { func (*Parse) ProtoMessage() {} func (x *Parse) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1072,7 +1120,7 @@ func (x *Parse) ProtoReflect() protoreflect.Message { // Deprecated: Use Parse.ProtoReflect.Descriptor instead. func (*Parse) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11} } // Provision consumes source-code from a directory to produce resources. @@ -1085,7 +1133,7 @@ type Provision struct { func (x *Provision) Reset() { *x = Provision{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1098,7 +1146,7 @@ func (x *Provision) String() string { func (*Provision) ProtoMessage() {} func (x *Provision) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1111,7 +1159,7 @@ func (x *Provision) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision.ProtoReflect.Descriptor instead. func (*Provision) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12} } type Resource_Metadata struct { @@ -1128,7 +1176,7 @@ type Resource_Metadata struct { func (x *Resource_Metadata) Reset() { *x = Resource_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1141,7 +1189,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[13] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1154,7 +1202,7 @@ func (x *Resource_Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Resource_Metadata.ProtoReflect.Descriptor instead. func (*Resource_Metadata) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9, 0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 0} } func (x *Resource_Metadata) GetKey() string { @@ -1196,7 +1244,7 @@ type Parse_Request struct { func (x *Parse_Request) Reset() { *x = Parse_Request{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1209,7 +1257,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[14] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1222,7 +1270,7 @@ func (x *Parse_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use Parse_Request.ProtoReflect.Descriptor instead. func (*Parse_Request) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 0} } func (x *Parse_Request) GetDirectory() string { @@ -1243,7 +1291,7 @@ type Parse_Complete struct { func (x *Parse_Complete) Reset() { *x = Parse_Complete{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1256,7 +1304,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[15] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1269,7 +1317,7 @@ func (x *Parse_Complete) ProtoReflect() protoreflect.Message { // Deprecated: Use Parse_Complete.ProtoReflect.Descriptor instead. func (*Parse_Complete) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 1} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 1} } func (x *Parse_Complete) GetParameterSchemas() []*ParameterSchema { @@ -1294,7 +1342,7 @@ type Parse_Response struct { func (x *Parse_Response) Reset() { *x = Parse_Response{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1307,7 +1355,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[16] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1320,7 +1368,7 @@ func (x *Parse_Response) ProtoReflect() protoreflect.Message { // Deprecated: Use Parse_Response.ProtoReflect.Descriptor instead. func (*Parse_Response) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 2} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 2} } func (m *Parse_Response) GetType() isParse_Response_Type { @@ -1377,7 +1425,7 @@ type Provision_Metadata struct { func (x *Provision_Metadata) Reset() { *x = Provision_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1390,7 +1438,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[17] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1403,7 +1451,7 @@ func (x *Provision_Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision_Metadata.ProtoReflect.Descriptor instead. func (*Provision_Metadata) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 0} } func (x *Provision_Metadata) GetCoderUrl() string { @@ -1470,7 +1518,7 @@ type Provision_Start struct { func (x *Provision_Start) Reset() { *x = Provision_Start{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1483,7 +1531,7 @@ func (x *Provision_Start) String() string { func (*Provision_Start) ProtoMessage() {} func (x *Provision_Start) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1496,7 +1544,7 @@ func (x *Provision_Start) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision_Start.ProtoReflect.Descriptor instead. func (*Provision_Start) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 1} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 1} } func (x *Provision_Start) GetDirectory() string { @@ -1543,7 +1591,7 @@ type Provision_Cancel struct { func (x *Provision_Cancel) Reset() { *x = Provision_Cancel{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1556,7 +1604,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[19] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1569,7 +1617,7 @@ func (x *Provision_Cancel) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision_Cancel.ProtoReflect.Descriptor instead. func (*Provision_Cancel) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 2} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 2} } type Provision_Request struct { @@ -1587,7 +1635,7 @@ type Provision_Request struct { func (x *Provision_Request) Reset() { *x = Provision_Request{} 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) } @@ -1600,7 +1648,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[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 { @@ -1613,7 +1661,7 @@ func (x *Provision_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision_Request.ProtoReflect.Descriptor instead. func (*Provision_Request) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 3} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 3} } func (m *Provision_Request) GetType() isProvision_Request_Type { @@ -1666,7 +1714,7 @@ type Provision_Complete struct { func (x *Provision_Complete) Reset() { *x = Provision_Complete{} 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) } @@ -1679,7 +1727,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[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 { @@ -1692,7 +1740,7 @@ func (x *Provision_Complete) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision_Complete.ProtoReflect.Descriptor instead. func (*Provision_Complete) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 4} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 4} } func (x *Provision_Complete) GetState() []byte { @@ -1731,7 +1779,7 @@ type Provision_Response struct { func (x *Provision_Response) Reset() { *x = Provision_Response{} 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) } @@ -1744,7 +1792,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[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 { @@ -1757,7 +1805,7 @@ func (x *Provision_Response) ProtoReflect() protoreflect.Message { // Deprecated: Use Provision_Response.ProtoReflect.Descriptor instead. func (*Provision_Response) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 5} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 5} } func (m *Provision_Response) GetType() isProvision_Response_Type { @@ -1904,8 +1952,8 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 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, 0x8f, - 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xba, + 0x01, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, @@ -1913,131 +1961,131 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, - 0x27, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x31, 0x0a, 0x14, 0x68, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x33, 0x0a, 0x15, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x68, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, - 0x22, 0xad, 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, 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, 0xfc, 0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 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, - 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, - 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, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, + 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, + 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, 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, 0xad, 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, 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, 0xfc, 0x01, 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, 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, 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, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x08, 0x0a, - 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 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, 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, 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, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, + 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, 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, 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, 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, 0x6b, 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, 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, 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, + 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, + 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, + 0x52, 0x75, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, + 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, + 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 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, 0x6b, 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, 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, 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 ( @@ -2053,7 +2101,7 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte { } var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: provisioner.LogLevel (WorkspaceTransition)(0), // 1: provisioner.WorkspaceTransition @@ -2069,20 +2117,21 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (*InstanceIdentityAuth)(nil), // 11: provisioner.InstanceIdentityAuth (*Agent)(nil), // 12: provisioner.Agent (*App)(nil), // 13: provisioner.App - (*Resource)(nil), // 14: provisioner.Resource - (*Parse)(nil), // 15: provisioner.Parse - (*Provision)(nil), // 16: provisioner.Provision - nil, // 17: provisioner.Agent.EnvEntry - (*Resource_Metadata)(nil), // 18: provisioner.Resource.Metadata - (*Parse_Request)(nil), // 19: provisioner.Parse.Request - (*Parse_Complete)(nil), // 20: provisioner.Parse.Complete - (*Parse_Response)(nil), // 21: provisioner.Parse.Response - (*Provision_Metadata)(nil), // 22: provisioner.Provision.Metadata - (*Provision_Start)(nil), // 23: provisioner.Provision.Start - (*Provision_Cancel)(nil), // 24: provisioner.Provision.Cancel - (*Provision_Request)(nil), // 25: provisioner.Provision.Request - (*Provision_Complete)(nil), // 26: provisioner.Provision.Complete - (*Provision_Response)(nil), // 27: provisioner.Provision.Response + (*Healthcheck)(nil), // 14: provisioner.Healthcheck + (*Resource)(nil), // 15: provisioner.Resource + (*Parse)(nil), // 16: provisioner.Parse + (*Provision)(nil), // 17: provisioner.Provision + nil, // 18: provisioner.Agent.EnvEntry + (*Resource_Metadata)(nil), // 19: provisioner.Resource.Metadata + (*Parse_Request)(nil), // 20: provisioner.Parse.Request + (*Parse_Complete)(nil), // 21: provisioner.Parse.Complete + (*Parse_Response)(nil), // 22: provisioner.Parse.Response + (*Provision_Metadata)(nil), // 23: provisioner.Provision.Metadata + (*Provision_Start)(nil), // 24: provisioner.Provision.Start + (*Provision_Cancel)(nil), // 25: provisioner.Provision.Cancel + (*Provision_Request)(nil), // 26: provisioner.Provision.Request + (*Provision_Complete)(nil), // 27: provisioner.Provision.Complete + (*Provision_Response)(nil), // 28: provisioner.Provision.Response } var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 2, // 0: provisioner.ParameterSource.scheme:type_name -> provisioner.ParameterSource.Scheme @@ -2092,30 +2141,31 @@ var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 7, // 4: provisioner.ParameterSchema.default_destination:type_name -> provisioner.ParameterDestination 4, // 5: provisioner.ParameterSchema.validation_type_system:type_name -> provisioner.ParameterSchema.TypeSystem 0, // 6: provisioner.Log.level:type_name -> provisioner.LogLevel - 17, // 7: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry + 18, // 7: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry 13, // 8: provisioner.Agent.apps:type_name -> provisioner.App - 12, // 9: provisioner.Resource.agents:type_name -> provisioner.Agent - 18, // 10: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata - 9, // 11: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema - 10, // 12: provisioner.Parse.Response.log:type_name -> provisioner.Log - 20, // 13: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete - 1, // 14: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 8, // 15: provisioner.Provision.Start.parameter_values:type_name -> provisioner.ParameterValue - 22, // 16: provisioner.Provision.Start.metadata:type_name -> provisioner.Provision.Metadata - 23, // 17: provisioner.Provision.Request.start:type_name -> provisioner.Provision.Start - 24, // 18: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel - 14, // 19: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource - 10, // 20: provisioner.Provision.Response.log:type_name -> provisioner.Log - 26, // 21: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete - 19, // 22: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request - 25, // 23: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request - 21, // 24: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response - 27, // 25: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response - 24, // [24:26] is the sub-list for method output_type - 22, // [22:24] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 14, // 9: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck + 12, // 10: provisioner.Resource.agents:type_name -> provisioner.Agent + 19, // 11: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata + 9, // 12: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema + 10, // 13: provisioner.Parse.Response.log:type_name -> provisioner.Log + 21, // 14: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete + 1, // 15: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition + 8, // 16: provisioner.Provision.Start.parameter_values:type_name -> provisioner.ParameterValue + 23, // 17: provisioner.Provision.Start.metadata:type_name -> provisioner.Provision.Metadata + 24, // 18: provisioner.Provision.Request.start:type_name -> provisioner.Provision.Start + 25, // 19: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel + 15, // 20: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource + 10, // 21: provisioner.Provision.Response.log:type_name -> provisioner.Log + 27, // 22: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete + 20, // 23: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request + 26, // 24: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request + 22, // 25: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response + 28, // 26: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response + 25, // [25:27] is the sub-list for method output_type + 23, // [23:25] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_provisionersdk_proto_provisioner_proto_init() } @@ -2233,7 +2283,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Resource); i { + switch v := v.(*Healthcheck); i { case 0: return &v.state case 1: @@ -2245,7 +2295,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Parse); i { + switch v := v.(*Resource); i { case 0: return &v.state case 1: @@ -2257,6 +2307,18 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Parse); 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[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision); i { case 0: return &v.state @@ -2268,7 +2330,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Resource_Metadata); i { case 0: return &v.state @@ -2280,7 +2342,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Parse_Request); i { case 0: return &v.state @@ -2292,7 +2354,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Parse_Complete); i { case 0: return &v.state @@ -2304,7 +2366,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Parse_Response); i { case 0: return &v.state @@ -2316,7 +2378,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Metadata); i { case 0: return &v.state @@ -2328,7 +2390,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Start); i { case 0: return &v.state @@ -2340,7 +2402,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Cancel); i { case 0: return &v.state @@ -2352,7 +2414,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Request); i { case 0: return &v.state @@ -2364,7 +2426,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Complete); i { case 0: return &v.state @@ -2376,7 +2438,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision_Response); i { case 0: return &v.state @@ -2393,15 +2455,15 @@ func file_provisionersdk_proto_provisioner_proto_init() { (*Agent_Token)(nil), (*Agent_InstanceId)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[16].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[17].OneofWrappers = []interface{}{ (*Parse_Response_Log)(nil), (*Parse_Response_Complete)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[20].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[21].OneofWrappers = []interface{}{ (*Provision_Request_Start)(nil), (*Provision_Request_Cancel)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[22].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[23].OneofWrappers = []interface{}{ (*Provision_Response_Log)(nil), (*Provision_Response_Complete)(nil), } @@ -2411,7 +2473,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc, NumEnums: 5, - NumMessages: 23, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 01344892dd904..70898979ba62e 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -94,9 +94,14 @@ message App { string url = 3; string icon = 4; bool relative_path = 5; - string healthcheck_url = 7; - int32 healthcheck_interval = 8; - int32 healthcheck_threshold = 9; + Healthcheck healthcheck = 6; +} + +// Healthcheck represents configuration for checking for app readiness. +message Healthcheck { + string url = 1; + int32 interval = 2; + int32 threshold = 3; } // Resource represents created infrastructure. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 62dc475a082cb..c541b830dcbbc 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -244,6 +244,13 @@ export interface GitSSHKey { readonly public_key: string } +// From codersdk/workspaceapps.go +export interface Healthcheck { + readonly url: string + readonly interval: number + readonly threshold: number +} + // From codersdk/licenses.go export interface License { readonly id: number @@ -588,9 +595,7 @@ export interface WorkspaceApp { readonly name: string readonly command?: string readonly icon?: string - readonly healthcheck_url: string - readonly healthcheck_interval: number - readonly healthcheck_threshold: number + readonly healthcheck: Healthcheck readonly health: WorkspaceAppHealth } From d7c2ef20fcf3d4ad3671a0c5734ccc24b5bf91c5 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 15:27:06 +0000 Subject: [PATCH 43/64] fix merge --- coderd/workspaceagents.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index bba389cbf3809..89c93e3e3a8f0 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -698,12 +698,12 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgent(r) var req codersdk.PostWorkspaceAppHealthsRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(r.Context(), rw, r, &req) { return } if req.Healths == nil || len(req.Healths) == 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{ Message: "Health field is empty", }) return @@ -711,7 +711,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{ Message: "Error getting agent apps", Detail: err.Error(), }) @@ -730,7 +730,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) return nil }() if old == nil { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusNotFound, codersdk.Response{ Message: "Error setting workspace app health", Detail: xerrors.Errorf("workspace app name %s not found", name).Error(), }) @@ -738,7 +738,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) } if old.HealthcheckUrl == "" { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusNotFound, codersdk.Response{ Message: "Error setting workspace app health", Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(), }) @@ -750,7 +750,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) case codersdk.WorkspaceAppHealthHealthy: case codersdk.WorkspaceAppHealthUnhealthy: default: - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{ Message: "Error setting workspace app health", Detail: xerrors.Errorf("workspace app health %s is not a valid value", newHealth).Error(), }) @@ -772,7 +772,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) Health: app.Health, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{ Message: "Error setting workspace app health", Detail: err.Error(), }) @@ -780,7 +780,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) } } - httpapi.Write(rw, http.StatusOK, nil) + httpapi.Write(r.Context(), rw, http.StatusOK, nil) } // wsNetConn wraps net.Conn created by websocket.NetConn(). Cancel func From e7a27980ab52f4d3d5c6531e55c8be4f0947229e Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 15:45:06 +0000 Subject: [PATCH 44/64] fix nil --- provisioner/terraform/resources.go | 37 +++++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index f62b20fb2472f..4a119c906b5b3 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -25,15 +25,20 @@ type agentAttributes struct { // A mapping of attributes on the "coder_app" resource. type agentAppAttributes struct { - AgentID string `mapstructure:"agent_id"` - Name string `mapstructure:"name"` - Icon string `mapstructure:"icon"` - URL string `mapstructure:"url"` - Command string `mapstructure:"command"` - RelativePath bool `mapstructure:"relative_path"` - HealthcheckURL string `mapstructure:"healthcheck_url"` - HealthcheckInterval int32 `mapstructure:"healthcheck_interval"` - HealthcheckThreshold int32 `mapstructure:"healthcheck_threshold"` + AgentID string `mapstructure:"agent_id"` + Name string `mapstructure:"name"` + Icon string `mapstructure:"icon"` + URL string `mapstructure:"url"` + Command string `mapstructure:"command"` + RelativePath bool `mapstructure:"relative_path"` + Healthcheck *appHealthcheckAttributes `mapstructure:"healthcheck"` +} + +// A mapping of attributes on the "healthcheck" resource. +type appHealthcheckAttributes struct { + URL string `mapstructure:"url"` + Interval int32 `mapstructure:"interval"` + Threshold int32 `mapstructure:"threshold"` } // A mapping of attributes on the "coder_metadata" resource. @@ -221,6 +226,14 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res // Default to the resource name if none is set! attrs.Name = resource.Name } + var healthcheck *proto.Healthcheck + if attrs.Healthcheck != nil { + healthcheck = &proto.Healthcheck{ + Url: attrs.Healthcheck.URL, + Interval: attrs.Healthcheck.Interval, + Threshold: attrs.Healthcheck.Threshold, + } + } for _, agents := range resourceAgents { for _, agent := range agents { // Find agents with the matching ID and associate them! @@ -233,11 +246,7 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res Url: attrs.URL, Icon: attrs.Icon, RelativePath: attrs.RelativePath, - Healthcheck: &proto.Healthcheck{ - Url: attrs.HealthcheckURL, - Interval: attrs.HealthcheckInterval, - Threshold: attrs.HealthcheckThreshold, - }, + Healthcheck: healthcheck, }) } } From b774aee13d2f466baa02f37ff89af6449c423772 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 15:46:53 +0000 Subject: [PATCH 45/64] fix js --- site/src/testHelpers/entities.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 1979193ce1444..396909b60e5df 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -325,9 +325,11 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { name: "test-app", icon: "", health: "disabled", - healthcheck_url: "", - healthcheck_interval: 0, - healthcheck_threshold: 0, + healthcheck: { + url: "", + interval: 0, + threshold: 0, + } } export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { From 8cfef1a3f66d72df78ab18a23898f8d9d7684651 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 17:52:52 +0000 Subject: [PATCH 46/64] update tf provider --- dogfood/main.tf | 2 +- examples/templates/aws-ecs-container/main.tf | 2 +- examples/templates/aws-linux/main.tf | 2 +- examples/templates/aws-windows/main.tf | 2 +- examples/templates/azure-linux/main.tf | 2 +- examples/templates/do-linux/main.tf | 2 +- examples/templates/docker-code-server/main.tf | 2 +- .../templates/docker-image-builds/main.tf | 2 +- .../templates/docker-with-dotfiles/main.tf | 2 +- examples/templates/docker/main.tf | 2 +- examples/templates/gcp-linux/main.tf | 2 +- examples/templates/gcp-vm-container/main.tf | 2 +- examples/templates/gcp-windows/main.tf | 2 +- examples/templates/kubernetes/main.tf | 2 +- provisioner/terraform/resources.go | 26 ++++---- provisioner/terraform/resources_test.go | 5 ++ .../testdata/calling-module/calling-module.tf | 2 +- .../calling-module.tfstate.json | 8 +-- .../chaining-resources/chaining-resources.tf | 2 +- .../chaining-resources.tfstate.json | 8 +-- .../conflicting-resources.tf | 2 +- .../conflicting-resources.tfstate.json | 8 +-- .../testdata/instance-id/instance-id.tf | 2 +- .../instance-id/instance-id.tfstate.json | 10 +-- .../multiple-agents/multiple-agents.tf | 2 +- .../multiple-agents.tfstate.json | 14 ++--- .../testdata/multiple-apps/multiple-apps.tf | 7 ++- .../multiple-apps/multiple-apps.tfplan.json | 61 ++++++++++++++++--- .../multiple-apps/multiple-apps.tfstate.json | 32 +++++++--- .../resource-metadata/resource-metadata.tf | 2 +- .../resource-metadata.tfstate.json | 10 +-- 31 files changed, 152 insertions(+), 77 deletions(-) diff --git a/dogfood/main.tf b/dogfood/main.tf index d8aaa943a47b8..2032adc18212e 100644 --- a/dogfood/main.tf +++ b/dogfood/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.5" + version = "0.4.15" } docker = { source = "kreuzwerker/docker" diff --git a/examples/templates/aws-ecs-container/main.tf b/examples/templates/aws-ecs-container/main.tf index 63dd2a1fb0b08..783279a2213b1 100644 --- a/examples/templates/aws-ecs-container/main.tf +++ b/examples/templates/aws-ecs-container/main.tf @@ -6,7 +6,7 @@ terraform { } coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/examples/templates/aws-linux/main.tf b/examples/templates/aws-linux/main.tf index 05c4cb42e8639..7d1e156d7fcd3 100644 --- a/examples/templates/aws-linux/main.tf +++ b/examples/templates/aws-linux/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/examples/templates/aws-windows/main.tf b/examples/templates/aws-windows/main.tf index 965cb2573bd4a..2c995c1e87579 100644 --- a/examples/templates/aws-windows/main.tf +++ b/examples/templates/aws-windows/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/examples/templates/azure-linux/main.tf b/examples/templates/azure-linux/main.tf index 85b86a4296675..33ed6d0170342 100644 --- a/examples/templates/azure-linux/main.tf +++ b/examples/templates/azure-linux/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } azurerm = { source = "hashicorp/azurerm" diff --git a/examples/templates/do-linux/main.tf b/examples/templates/do-linux/main.tf index 0468c45c21e9a..526bd4f65bba9 100644 --- a/examples/templates/do-linux/main.tf +++ b/examples/templates/do-linux/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } digitalocean = { source = "digitalocean/digitalocean" diff --git a/examples/templates/docker-code-server/main.tf b/examples/templates/docker-code-server/main.tf index 16fcf07a49095..8efcbfb48092d 100644 --- a/examples/templates/docker-code-server/main.tf +++ b/examples/templates/docker-code-server/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } docker = { source = "kreuzwerker/docker" diff --git a/examples/templates/docker-image-builds/main.tf b/examples/templates/docker-image-builds/main.tf index fc54239e793c5..7ffb3991ca11a 100644 --- a/examples/templates/docker-image-builds/main.tf +++ b/examples/templates/docker-image-builds/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } docker = { source = "kreuzwerker/docker" diff --git a/examples/templates/docker-with-dotfiles/main.tf b/examples/templates/docker-with-dotfiles/main.tf index ceb2f21d801d4..594cdb72d39e4 100644 --- a/examples/templates/docker-with-dotfiles/main.tf +++ b/examples/templates/docker-with-dotfiles/main.tf @@ -9,7 +9,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } docker = { source = "kreuzwerker/docker" diff --git a/examples/templates/docker/main.tf b/examples/templates/docker/main.tf index 68d2f39a73833..14261ea83e342 100644 --- a/examples/templates/docker/main.tf +++ b/examples/templates/docker/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } docker = { source = "kreuzwerker/docker" diff --git a/examples/templates/gcp-linux/main.tf b/examples/templates/gcp-linux/main.tf index 647fb6ac61ec9..533866cd44723 100644 --- a/examples/templates/gcp-linux/main.tf +++ b/examples/templates/gcp-linux/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } google = { source = "hashicorp/google" diff --git a/examples/templates/gcp-vm-container/main.tf b/examples/templates/gcp-vm-container/main.tf index 17cf48884f035..8f7bccaf81149 100644 --- a/examples/templates/gcp-vm-container/main.tf +++ b/examples/templates/gcp-vm-container/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } google = { source = "hashicorp/google" diff --git a/examples/templates/gcp-windows/main.tf b/examples/templates/gcp-windows/main.tf index eaba792b94c87..25e1e90bd9f9c 100644 --- a/examples/templates/gcp-windows/main.tf +++ b/examples/templates/gcp-windows/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } google = { source = "hashicorp/google" diff --git a/examples/templates/kubernetes/main.tf b/examples/templates/kubernetes/main.tf index f2380b436cb5c..edcd4cce19f55 100644 --- a/examples/templates/kubernetes/main.tf +++ b/examples/templates/kubernetes/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } kubernetes = { source = "hashicorp/kubernetes" diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 4a119c906b5b3..22685c566120a 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -1,6 +1,8 @@ package terraform import ( + "encoding/json" + "fmt" "strings" "github.com/awalterschulze/gographviz" @@ -25,13 +27,13 @@ type agentAttributes struct { // A mapping of attributes on the "coder_app" resource. type agentAppAttributes struct { - AgentID string `mapstructure:"agent_id"` - Name string `mapstructure:"name"` - Icon string `mapstructure:"icon"` - URL string `mapstructure:"url"` - Command string `mapstructure:"command"` - RelativePath bool `mapstructure:"relative_path"` - Healthcheck *appHealthcheckAttributes `mapstructure:"healthcheck"` + AgentID string `mapstructure:"agent_id"` + Name string `mapstructure:"name"` + Icon string `mapstructure:"icon"` + URL string `mapstructure:"url"` + Command string `mapstructure:"command"` + RelativePath bool `mapstructure:"relative_path"` + Healthcheck []appHealthcheckAttributes `mapstructure:"healthcheck"` } // A mapping of attributes on the "healthcheck" resource. @@ -220,6 +222,8 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res var attrs agentAppAttributes err = mapstructure.Decode(resource.AttributeValues, &attrs) if err != nil { + d, _ := json.MarshalIndent(resource.AttributeValues, "", " ") + fmt.Print(string(d)) return nil, xerrors.Errorf("decode app attributes: %w", err) } if attrs.Name == "" { @@ -227,11 +231,11 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res attrs.Name = resource.Name } var healthcheck *proto.Healthcheck - if attrs.Healthcheck != nil { + if len(attrs.Healthcheck) != 0 { healthcheck = &proto.Healthcheck{ - Url: attrs.Healthcheck.URL, - Interval: attrs.Healthcheck.Interval, - Threshold: attrs.Healthcheck.Threshold, + Url: attrs.Healthcheck[0].URL, + Interval: attrs.Healthcheck[0].Interval, + Threshold: attrs.Healthcheck[0].Threshold, } } for _, agents := range resourceAgents { diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index 5e2cdbc7fc588..7330a215eac17 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -112,6 +112,11 @@ func TestConvertResources(t *testing.T) { Name: "app1", }, { Name: "app2", + Healthcheck: &proto.Healthcheck{ + Url: "http://localhost:13337/healthz", + Interval: 5, + Threshold: 6, + }, }}, Auth: &proto.Agent_Token{}, }}, diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tf b/provisioner/terraform/testdata/calling-module/calling-module.tf index 14303795cff4c..894e082e30aab 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tf +++ b/provisioner/terraform/testdata/calling-module/calling-module.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json index 0518f8dbced64..fe61412e402c2 100644 --- a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json +++ b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json @@ -16,11 +16,11 @@ "auth": "token", "dir": null, "env": null, - "id": "f05ddf9e-106a-4669-bba8-5e2289bd891d", + "id": "f5435556-71b4-4e9c-a961-474ef4c70836", "init_script": "", "os": "linux", "startup_script": null, - "token": "ed4655b9-e917-44af-8706-a1215384a35f" + "token": "cbe1cec2-8c52-4411-ab1b-c7e9aa4e93ea" }, "sensitive_values": {} } @@ -44,7 +44,7 @@ "outputs": { "script": "" }, - "random": "7640853885488752810" + "random": "2977741887145450154" }, "sensitive_values": { "inputs": {}, @@ -59,7 +59,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "6481148597794195898", + "id": "3098344175322958112", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tf b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tf index 75347019d2247..fb9c1fb0ffaa4 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tf +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json index 95c62fe5cde09..1986782431efb 100644 --- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json +++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json @@ -16,11 +16,11 @@ "auth": "token", "dir": null, "env": null, - "id": "fcd8018c-7e4a-4e92-855b-e02319ab051e", + "id": "846b2cd1-1dcc-4b26-ad71-8508c8d71738", "init_script": "", "os": "linux", "startup_script": null, - "token": "ad906408-0eb0-4844-83f7-0f5070427e1c" + "token": "3a3e4e25-6be2-4b51-a369-957fdb243a4f" }, "sensitive_values": {} }, @@ -32,7 +32,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2672857180605476162", + "id": "8441562949971496089", "triggers": null }, "sensitive_values": {}, @@ -49,7 +49,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "264584188140644760", + "id": "4737933879128730392", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tf b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tf index db0787b2dd550..d5b50dcd864b9 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tf +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json index 58152817465d9..c9c411da2b9fd 100644 --- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json +++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json @@ -16,11 +16,11 @@ "auth": "token", "dir": null, "env": null, - "id": "e3df7d56-17ce-4d8a-9d4e-30ea41cc8a93", + "id": "2efd4acf-bb30-4713-98b5-21fef293c995", "init_script": "", "os": "linux", "startup_script": null, - "token": "1717f79d-2c72-440e-a5c6-e4b8c3fef084" + "token": "7db84d6e-c079-4b4a-99e0-e2414a70df84" }, "sensitive_values": {} }, @@ -32,7 +32,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2957375211969224115", + "id": "6618109150570768254", "triggers": null }, "sensitive_values": {}, @@ -48,7 +48,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "6924176854496195292", + "id": "4505836003282545145", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tf b/provisioner/terraform/testdata/instance-id/instance-id.tf index a56988dcc1f81..3e92a8d7799a6 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tf +++ b/provisioner/terraform/testdata/instance-id/instance-id.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json index c288ecbe3d770..c726acf85432d 100644 --- a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json +++ b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json @@ -16,11 +16,11 @@ "auth": "google-instance-identity", "dir": null, "env": null, - "id": "9a37096a-7f01-42cd-93d8-9f4572c94489", + "id": "e2d2e12e-1975-4bca-8a96-67d6b303b25b", "init_script": "", "os": "linux", "startup_script": null, - "token": "7784ea1f-7fe5-463f-af8d-255c32d12992" + "token": "87ba2736-3519-4368-b9ee-4132bd042fe3" }, "sensitive_values": {} }, @@ -32,8 +32,8 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "9a37096a-7f01-42cd-93d8-9f4572c94489", - "id": "8ed448e2-51d7-4cc7-9e26-a3a77f252b1d", + "agent_id": "e2d2e12e-1975-4bca-8a96-67d6b303b25b", + "id": "979121e7-2a41-432a-aa90-8b0d2d802b50", "instance_id": "example" }, "sensitive_values": {}, @@ -49,7 +49,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "771742387122791362", + "id": "3316746911978433294", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf index 2aea125c0fec9..5186fc26a09b2 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json index 3755a14d44abd..56b5e1cc708c3 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json @@ -16,11 +16,11 @@ "auth": "token", "dir": null, "env": null, - "id": "0c3c20d8-8a1d-4fc9-bc73-ed45ddad9a9d", + "id": "032d71aa-570d-4d0a-bce8-57b9d884b694", "init_script": "", "os": "linux", "startup_script": null, - "token": "48b3f4c4-4bb9-477c-8d32-d1e14188e5f8" + "token": "7a0df6bf-313d-4f73-ba2c-6532d72cb808" }, "sensitive_values": {} }, @@ -36,11 +36,11 @@ "auth": "token", "dir": null, "env": null, - "id": "08e8ebc8-4660-47f0-acb5-6ca46747919d", + "id": "019ae4b9-ae5c-4837-be16-dae99b911acf", "init_script": "", "os": "darwin", "startup_script": null, - "token": "827a1f01-a2d7-4794-ab73-8fd8442010d5" + "token": "9f4adbf4-9113-42f4-bb84-d1621262b1e2" }, "sensitive_values": {} }, @@ -56,11 +56,11 @@ "auth": "token", "dir": null, "env": null, - "id": "50f52bd4-a52b-4c73-bf99-fe956913bca4", + "id": "8f2c3b12-e112-405e-9fbf-fe540ed3fe21", "init_script": "", "os": "windows", "startup_script": null, - "token": "159d6407-a913-4e05-8ba7-786d47a7e34b" + "token": "1a6ddbc7-77a9-43c2-9e60-c84d3ecf512a" }, "sensitive_values": {} }, @@ -72,7 +72,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "2529387636030139440", + "id": "6351611769218065391", "triggers": null }, "sensitive_values": {}, diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf index 456d00ec6abc1..70453b754db16 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } @@ -18,6 +18,11 @@ resource "coder_app" "app1" { resource "coder_app" "app2" { agent_id = coder_agent.dev1.id + healthcheck { + url = "http://localhost:13337/healthz" + interval = 5 + threshold = 6 + } } resource "null_resource" "dev" { diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json index d728eb2c88c43..6b117d913769a 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json @@ -30,12 +30,15 @@ "schema_version": 0, "values": { "command": null, + "healthcheck": [], "icon": null, "name": null, "relative_path": null, "url": null }, - "sensitive_values": {} + "sensitive_values": { + "healthcheck": [] + } }, { "address": "coder_app.app2", @@ -46,12 +49,23 @@ "schema_version": 0, "values": { "command": null, + "healthcheck": [ + { + "interval": 5, + "threshold": 6, + "url": "http://localhost:13337/healthz" + } + ], "icon": null, "name": null, "relative_path": null, "url": null }, - "sensitive_values": {} + "sensitive_values": { + "healthcheck": [ + {} + ] + } }, { "address": "null_resource.dev", @@ -94,7 +108,9 @@ "token": true }, "before_sensitive": false, - "after_sensitive": {} + "after_sensitive": { + "token": true + } } }, { @@ -110,6 +126,7 @@ "before": null, "after": { "command": null, + "healthcheck": [], "icon": null, "name": null, "relative_path": null, @@ -117,10 +134,13 @@ }, "after_unknown": { "agent_id": true, + "healthcheck": [], "id": true }, "before_sensitive": false, - "after_sensitive": {} + "after_sensitive": { + "healthcheck": [] + } } }, { @@ -136,6 +156,13 @@ "before": null, "after": { "command": null, + "healthcheck": [ + { + "interval": 5, + "threshold": 6, + "url": "http://localhost:13337/healthz" + } + ], "icon": null, "name": null, "relative_path": null, @@ -143,10 +170,17 @@ }, "after_unknown": { "agent_id": true, + "healthcheck": [ + {} + ], "id": true }, "before_sensitive": false, - "after_sensitive": {} + "after_sensitive": { + "healthcheck": [ + {} + ] + } } }, { @@ -176,7 +210,7 @@ "coder": { "name": "coder", "full_name": "registry.terraform.io/coder/coder", - "version_constraint": "0.4.11" + "version_constraint": "0.4.14" }, "null": { "name": "null", @@ -229,7 +263,20 @@ "coder_agent.dev1.id", "coder_agent.dev1" ] - } + }, + "healthcheck": [ + { + "interval": { + "constant_value": 5 + }, + "threshold": { + "constant_value": 6 + }, + "url": { + "constant_value": "http://localhost:13337/healthz" + } + } + ] }, "schema_version": 0 }, diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json index ca16a470ca1bb..c703dd490e878 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json @@ -16,11 +16,11 @@ "auth": "token", "dir": null, "env": null, - "id": "3d4ee1d5-6413-4dc7-baec-2fa9dbd870ba", + "id": "685dba1f-09de-40c0-8fc0-4d8ca00ef946", "init_script": "", "os": "linux", "startup_script": null, - "token": "32e082d7-af02-42f1-a5bd-f6adc34220a1" + "token": "2c73d680-ef4c-4bc1-80f0-f6916e4e5255" }, "sensitive_values": {} }, @@ -32,15 +32,18 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "3d4ee1d5-6413-4dc7-baec-2fa9dbd870ba", + "agent_id": "685dba1f-09de-40c0-8fc0-4d8ca00ef946", "command": null, + "healthcheck": [], "icon": null, - "id": "90e045f9-19f1-4d8a-8021-be61c44ee54f", + "id": "46f8d3cd-bcf7-4792-8d54-66e01e63018a", "name": null, "relative_path": null, "url": null }, - "sensitive_values": {}, + "sensitive_values": { + "healthcheck": [] + }, "depends_on": [ "coder_agent.dev1" ] @@ -53,15 +56,26 @@ "provider_name": "registry.terraform.io/coder/coder", "schema_version": 0, "values": { - "agent_id": "3d4ee1d5-6413-4dc7-baec-2fa9dbd870ba", + "agent_id": "685dba1f-09de-40c0-8fc0-4d8ca00ef946", "command": null, + "healthcheck": [ + { + "interval": 5, + "threshold": 6, + "url": "http://localhost:13337/healthz" + } + ], "icon": null, - "id": "873026f8-3050-4b0b-bebf-41e13e5949bb", + "id": "e4556c74-2f67-4266-b1e8-7ee61d754583", "name": null, "relative_path": null, "url": null }, - "sensitive_values": {}, + "sensitive_values": { + "healthcheck": [ + {} + ] + }, "depends_on": [ "coder_agent.dev1" ] @@ -74,7 +88,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "4447693752005094678", + "id": "2997000197756647168", "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 bed06efe7520e..110f07099db70 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.11" + version = "0.4.15" } } } diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json index bc017d9e13ca9..2873d610f87ba 100644 --- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json +++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json @@ -16,11 +16,11 @@ "auth": "token", "dir": null, "env": null, - "id": "09aac2a4-9d8e-43ef-83cb-34657db199f4", + "id": "50a0466c-d983-422f-8bed-9dd0bf705a9a", "init_script": "", "os": "linux", "startup_script": null, - "token": "a0f6b8af-8edc-447f-b6d2-67a60ecd2a77" + "token": "aa714059-3579-49d1-a0e2-3519dbe43688" }, "sensitive_values": {} }, @@ -34,7 +34,7 @@ "values": { "hide": true, "icon": "/icon/server.svg", - "id": "a7f9cf03-de78-4d17-bcbb-21dc34c2d86a", + "id": "64a47d31-28d0-4a50-8e09-a3e705278305", "item": [ { "is_null": false, @@ -61,7 +61,7 @@ "value": "squirrel" } ], - "resource_id": "6209384655473556868" + "resource_id": "4887255791781048166" }, "sensitive_values": { "item": [ @@ -83,7 +83,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "6209384655473556868", + "id": "4887255791781048166", "triggers": null }, "sensitive_values": {} From aaabc5a24303dfbc97c5df0c19e0ac412c7c3526 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 17:56:37 +0000 Subject: [PATCH 47/64] make fmt --- provisioner/terraform/testdata/multiple-apps/multiple-apps.tf | 4 ++-- provisionersdk/proto/provisioner.proto | 2 +- site/src/testHelpers/entities.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf index 70453b754db16..02e42868839c8 100644 --- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf +++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tf @@ -19,8 +19,8 @@ resource "coder_app" "app1" { resource "coder_app" "app2" { agent_id = coder_agent.dev1.id healthcheck { - url = "http://localhost:13337/healthz" - interval = 5 + url = "http://localhost:13337/healthz" + interval = 5 threshold = 6 } } diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 70898979ba62e..af30e32f10524 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -99,7 +99,7 @@ message App { // Healthcheck represents configuration for checking for app readiness. message Healthcheck { - string url = 1; + string url = 1; int32 interval = 2; int32 threshold = 3; } diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 396909b60e5df..c5392745ed5f8 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -329,7 +329,7 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { url: "", interval: 0, threshold: 0, - } + }, } export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { From 7c7049513769318e7d9c3b1e74e8bbc25c5664bf Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 19:13:57 +0000 Subject: [PATCH 48/64] add to example --- agent/apphealth.go | 14 ++++++++++++-- examples/templates/docker/main.tf | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 30814e244661a..9b1a88f87c21e 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -103,8 +103,18 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } }() + copyHealth := func(h1 map[string]codersdk.WorkspaceAppHealth) map[string]codersdk.WorkspaceAppHealth { + h2 := make(map[string]codersdk.WorkspaceAppHealth, 0) + mu.RLock() + for k, v := range h1 { + h2[k] = v + } + mu.RUnlock() + + return h2 + } + lastHealth := copyHealth(health) reportTicker := time.NewTicker(time.Second) - lastHealth := make(map[string]codersdk.WorkspaceAppHealth, 0) for { select { case <-ctx.Done(): @@ -114,7 +124,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) changed := healthChanged(lastHealth, health) mu.RUnlock() if changed { - lastHealth = health + lastHealth = copyHealth(health) err := client.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ Healths: health, }) diff --git a/examples/templates/docker/main.tf b/examples/templates/docker/main.tf index 14261ea83e342..17b5c82dad8c7 100644 --- a/examples/templates/docker/main.tf +++ b/examples/templates/docker/main.tf @@ -47,6 +47,11 @@ resource "coder_app" "code-server" { name = "code-server" url = "http://localhost:13337/?folder=/home/coder" icon = "/icon/code.svg" + healthcheck { + url = "http://localhost:13337/healthz" + interval = 5 + threshold = 6 + } } From 5aedcdc5354bda2ecade184d8266cc9711e02594 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 21:09:34 +0000 Subject: [PATCH 49/64] fix agent logic --- agent/apphealth.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 9b1a88f87c21e..e31c7c1bbecdc 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -67,7 +67,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } client := &http.Client{ - Timeout: time.Duration(app.Healthcheck.Interval), + Timeout: time.Duration(time.Duration(app.Healthcheck.Interval) * time.Second), } err := func() error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) @@ -85,9 +85,11 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) return nil }() - if err == nil { + if err != nil { + logger.Debug(ctx, "failed healthcheck", slog.Error(err), slog.F("app", app)) mu.Lock() failures[app.Name]++ + logger.Debug(ctx, "failed healthcheck", slog.Error(err), slog.F("app", app), slog.F("failures", failures[app.Name])) if failures[app.Name] > int(app.Healthcheck.Threshold) { health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy } From 2654c1a6066e362bed1c2932e2f28e084eadd453 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 22 Sep 2022 21:14:15 +0000 Subject: [PATCH 50/64] fix cast --- agent/apphealth.go | 2 +- examples/templates/docker/main.tf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index e31c7c1bbecdc..dff3fe101f6af 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -67,7 +67,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } client := &http.Client{ - Timeout: time.Duration(time.Duration(app.Healthcheck.Interval) * time.Second), + Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, } err := func() error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) diff --git a/examples/templates/docker/main.tf b/examples/templates/docker/main.tf index 17b5c82dad8c7..36ce85da7f5f3 100644 --- a/examples/templates/docker/main.tf +++ b/examples/templates/docker/main.tf @@ -48,8 +48,8 @@ resource "coder_app" "code-server" { url = "http://localhost:13337/?folder=/home/coder" icon = "/icon/code.svg" healthcheck { - url = "http://localhost:13337/healthz" - interval = 5 + url = "http://localhost:13337/healthz" + interval = 5 threshold = 6 } } From 8b293fe47d5f1b3888a8373632c5878996f1b9fb Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:29:11 +0000 Subject: [PATCH 51/64] add apphealth_test.go --- agent/agent.go | 51 ++++++------ agent/apphealth.go | 66 ++++++++++----- agent/apphealth_test.go | 173 ++++++++++++++++++++++++++++++++++++++++ cli/agent.go | 7 +- 4 files changed, 252 insertions(+), 45 deletions(-) create mode 100644 agent/apphealth_test.go diff --git a/agent/agent.go b/agent/agent.go index d25ee0f87938f..6dff747b8b415 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -51,13 +51,14 @@ const ( ) type Options struct { - CoordinatorDialer CoordinatorDialer - FetchMetadata FetchMetadata - StatsReporter StatsReporter - WorkspaceAppHealthReporter WorkspaceAppHealthReporter - ReconnectingPTYTimeout time.Duration - EnvironmentVariables map[string]string - Logger slog.Logger + CoordinatorDialer CoordinatorDialer + FetchMetadata FetchMetadata + StatsReporter StatsReporter + WorkspaceAgentApps WorkspaceAgentApps + PostWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth + ReconnectingPTYTimeout time.Duration + EnvironmentVariables map[string]string + Logger slog.Logger } // CoordinatorDialer is a function that constructs a new broker. @@ -73,16 +74,17 @@ func New(options Options) io.Closer { } ctx, cancelFunc := context.WithCancel(context.Background()) server := &agent{ - reconnectingPTYTimeout: options.ReconnectingPTYTimeout, - logger: options.Logger, - closeCancel: cancelFunc, - closed: make(chan struct{}), - envVars: options.EnvironmentVariables, - coordinatorDialer: options.CoordinatorDialer, - fetchMetadata: options.FetchMetadata, - stats: &Stats{}, - statsReporter: options.StatsReporter, - workspaceAppHealthReporter: options.WorkspaceAppHealthReporter, + reconnectingPTYTimeout: options.ReconnectingPTYTimeout, + logger: options.Logger, + closeCancel: cancelFunc, + closed: make(chan struct{}), + envVars: options.EnvironmentVariables, + coordinatorDialer: options.CoordinatorDialer, + fetchMetadata: options.FetchMetadata, + stats: &Stats{}, + statsReporter: options.StatsReporter, + workspaceAgentApps: options.WorkspaceAgentApps, + postWorkspaceAgentAppHealth: options.PostWorkspaceAgentAppHealth, } server.init(ctx) return server @@ -105,11 +107,12 @@ type agent struct { fetchMetadata FetchMetadata sshServer *ssh.Server - network *tailnet.Conn - coordinatorDialer CoordinatorDialer - stats *Stats - statsReporter StatsReporter - workspaceAppHealthReporter WorkspaceAppHealthReporter + network *tailnet.Conn + coordinatorDialer CoordinatorDialer + stats *Stats + statsReporter StatsReporter + workspaceAgentApps WorkspaceAgentApps + postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth } func (a *agent) run(ctx context.Context) { @@ -155,8 +158,8 @@ func (a *agent) run(ctx context.Context) { go a.runTailnet(ctx, metadata.DERPMap) } - if a.workspaceAppHealthReporter != nil { - go a.workspaceAppHealthReporter(ctx) + if a.workspaceAgentApps != nil && a.postWorkspaceAgentAppHealth != nil { + go NewWorkspaceAppHealthReporter(a.logger, a.workspaceAgentApps, a.postWorkspaceAgentAppHealth)(ctx) } } diff --git a/agent/apphealth.go b/agent/apphealth.go index dff3fe101f6af..b4996d84a274b 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -13,14 +13,22 @@ import ( "github.com/coder/retry" ) +// WorkspaceAgentApps fetches the workspace apps. +type WorkspaceAgentApps func(context.Context) ([]codersdk.WorkspaceApp, error) + +// PostWorkspaceAgentAppHealth updates the workspace app health. +type PostWorkspaceAgentAppHealth func(context.Context, codersdk.PostWorkspaceAppHealthsRequest) error + +// WorkspaceAppHealthReporter is a function that checks and reports the health of the workspace apps until the passed context is canceled. type WorkspaceAppHealthReporter func(ctx context.Context) -func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) WorkspaceAppHealthReporter { +// NewWorkspaceAppHealthReporter creates a WorkspaceAppHealthReporter that reports app health to coderd. +func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps WorkspaceAgentApps, postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth) WorkspaceAppHealthReporter { return func(ctx context.Context) { r := retry.New(time.Second, 30*time.Second) for { err := func() error { - apps, err := client.WorkspaceAgentApps(ctx) + apps, err := workspaceAgentApps(ctx) if err != nil { if xerrors.Is(err, context.Canceled) { return nil @@ -28,15 +36,26 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) return xerrors.Errorf("getting workspace apps: %w", err) } + // no need to run this loop if no apps for this workspace. if len(apps) == 0 { return nil } + hasHealthchecksEnabled := false health := make(map[string]codersdk.WorkspaceAppHealth, 0) for _, app := range apps { health[app.Name] = app.Health + if !hasHealthchecksEnabled && app.Health != codersdk.WorkspaceAppHealthDisabled { + hasHealthchecksEnabled = true + } + } + + // no need to run this loop if no health checks are configured. + if !hasHealthchecksEnabled { + return nil } + // run a ticker for each app health check. tickers := make(chan string) for _, app := range apps { if shouldStartTicker(app) { @@ -66,6 +85,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) continue } + // we set the http timeout to the healthcheck interval to prevent getting too backed up. client := &http.Client{ Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, } @@ -78,6 +98,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) if err != nil { return err } + // successful healthcheck is a non-5XX status code res.Body.Close() if res.StatusCode >= http.StatusInternalServerError { return xerrors.Errorf("error status code: %d", res.StatusCode) @@ -86,18 +107,22 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) return nil }() if err != nil { - logger.Debug(ctx, "failed healthcheck", slog.Error(err), slog.F("app", app)) mu.Lock() - failures[app.Name]++ - logger.Debug(ctx, "failed healthcheck", slog.Error(err), slog.F("app", app), slog.F("failures", failures[app.Name])) - if failures[app.Name] > int(app.Healthcheck.Threshold) { + if failures[app.Name] < int(app.Healthcheck.Threshold) { + // increment the failure count and keep status the same. + // we will change it when we hit the threshold. + failures[app.Name]++ + } else { + // set to unhealthy if we hit the failure threshold. + // we stop incrementing at the threshold to prevent the failure value from increasing forever. health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy } mu.Unlock() } else { mu.Lock() - failures[app.Name] = 0 + // we only need one successful health check to be considered healthy. health[app.Name] = codersdk.WorkspaceAppHealthHealthy + failures[app.Name] = 0 mu.Unlock() } } @@ -105,18 +130,12 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) } }() - copyHealth := func(h1 map[string]codersdk.WorkspaceAppHealth) map[string]codersdk.WorkspaceAppHealth { - h2 := make(map[string]codersdk.WorkspaceAppHealth, 0) - mu.RLock() - for k, v := range h1 { - h2[k] = v - } - mu.RUnlock() - - return h2 - } + mu.Lock() lastHealth := copyHealth(health) + mu.Unlock() reportTicker := time.NewTicker(time.Second) + // every second we check if the health values of the apps have changed + // and if there is a change we will report the new values. for { select { case <-ctx.Done(): @@ -126,8 +145,10 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, client *codersdk.Client) changed := healthChanged(lastHealth, health) mu.RUnlock() if changed { + mu.Lock() lastHealth = copyHealth(health) - err := client.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + mu.Unlock() + err := postWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ Healths: health, }) if err != nil { @@ -167,3 +188,12 @@ func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]co return false } + +func copyHealth(h1 map[string]codersdk.WorkspaceAppHealth) map[string]codersdk.WorkspaceAppHealth { + h2 := make(map[string]codersdk.WorkspaceAppHealth, 0) + for k, v := range h1 { + h2[k] = v + } + + return h2 +} diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go new file mode 100644 index 0000000000000..2777951e6b093 --- /dev/null +++ b/agent/apphealth_test.go @@ -0,0 +1,173 @@ +package agent_test + +import ( + "context" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/agent" + "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/codersdk" +) + +func TestAppHealth(t *testing.T) { + t.Parallel() + t.Run("Healthy", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + apps := []codersdk.WorkspaceApp{ + { + Name: "app1", + Healthcheck: codersdk.Healthcheck{}, + Health: codersdk.WorkspaceAppHealthDisabled, + }, + { + Name: "app2", + Healthcheck: codersdk.Healthcheck{ + // URL: We don't set the URL for this test because the setup will + // create a httptest server for us and set it for us. + Interval: 1, + Threshold: 3, + }, + Health: codersdk.WorkspaceAppHealthInitializing, + }, + } + handlers := []http.Handler{ + nil, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + httpapi.Write(r.Context(), w, http.StatusOK, nil) + }), + } + getApps, closeFn := setupAppReporter(ctx, t, apps, handlers) + defer closeFn() + apps, err := getApps(ctx) + require.NoError(t, err) + require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, apps[0].Health) + require.Eventually(t, func() bool { + apps, err := getApps(ctx) + if err != nil { + return false + } + + return apps[1].Health == codersdk.WorkspaceAppHealthHealthy + }, 5*time.Second, 1*time.Second) + }) + + t.Run("500", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + apps := []codersdk.WorkspaceApp{ + { + Name: "app2", + Healthcheck: codersdk.Healthcheck{ + // URL: We don't set the URL for this test because the setup will + // create a httptest server for us and set it for us. + Interval: 1, + Threshold: 3, + }, + Health: codersdk.WorkspaceAppHealthInitializing, + }, + } + handlers := []http.Handler{ + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + httpapi.Write(r.Context(), w, http.StatusInternalServerError, nil) + }), + } + getApps, closeFn := setupAppReporter(ctx, t, apps, handlers) + defer closeFn() + require.Eventually(t, func() bool { + apps, err := getApps(ctx) + if err != nil { + return false + } + + return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy + }, 10*time.Second, 1*time.Second) + }) + + t.Run("Timeout", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + apps := []codersdk.WorkspaceApp{ + { + Name: "app2", + Healthcheck: codersdk.Healthcheck{ + // URL: We don't set the URL for this test because the setup will + // create a httptest server for us and set it for us. + Interval: 1, + Threshold: 3, + }, + Health: codersdk.WorkspaceAppHealthInitializing, + }, + } + handlers := []http.Handler{ + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // sleep longer than the timeout to cause the health check to time out + time.Sleep(2 * time.Second) + httpapi.Write(r.Context(), w, http.StatusOK, nil) + }), + } + getApps, closeFn := setupAppReporter(ctx, t, apps, handlers) + defer closeFn() + require.Eventually(t, func() bool { + apps, err := getApps(ctx) + if err != nil { + return false + } + + return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy + }, 10*time.Second, 1*time.Second) + }) +} + +func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.WorkspaceApp, handlers []http.Handler) (agent.WorkspaceAgentApps, func()) { + closers := []func(){} + for i, handler := range handlers { + if handler == nil { + continue + } + ts := httptest.NewServer(handler) + app := apps[i] + app.Healthcheck.URL = ts.URL + apps[i] = app + closers = append(closers, ts.Close) + } + + var mu sync.Mutex + workspaceAgentApps := func(context.Context) ([]codersdk.WorkspaceApp, error) { + return apps, nil + } + postWorkspaceAgentAppHealth := func(_ context.Context, req codersdk.PostWorkspaceAppHealthsRequest) error { + for name, health := range req.Healths { + mu.Lock() + for i, app := range apps { + if app.Name != name { + continue + } + app.Health = health + apps[i] = app + } + mu.Unlock() + } + + return nil + } + + go agent.NewWorkspaceAppHealthReporter(slogtest.Make(t, nil).Leveled(slog.LevelDebug), workspaceAgentApps, postWorkspaceAgentAppHealth)(ctx) + + return workspaceAgentApps, func() { + for _, closeFn := range closers { + closeFn() + } + } +} diff --git a/cli/agent.go b/cli/agent.go index ee6e34d17807e..052c89d5888dc 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -189,9 +189,10 @@ func workspaceAgent() *cobra.Command { // shells so "gitssh" works! "CODER_AGENT_TOKEN": client.SessionToken, }, - CoordinatorDialer: client.ListenWorkspaceAgentTailnet, - StatsReporter: client.AgentReportStats, - WorkspaceAppHealthReporter: agent.NewWorkspaceAppHealthReporter(logger, client), + CoordinatorDialer: client.ListenWorkspaceAgentTailnet, + StatsReporter: client.AgentReportStats, + WorkspaceAgentApps: client.WorkspaceAgentApps, + PostWorkspaceAgentAppHealth: client.PostWorkspaceAgentAppHealth, }) <-cmd.Context().Done() return closer.Close() From 6b95dddd0ad0d6e137884d1fc7227ab9fe99db22 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:32:52 +0000 Subject: [PATCH 52/64] lint --- agent/apphealth_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index 2777951e6b093..ef7fad90f90ad 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -15,13 +15,14 @@ import ( "github.com/coder/coder/agent" "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/codersdk" + "github.com/coder/coder/testutil" ) func TestAppHealth(t *testing.T) { t.Parallel() t.Run("Healthy", func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() apps := []codersdk.WorkspaceApp{ { @@ -58,12 +59,12 @@ func TestAppHealth(t *testing.T) { } return apps[1].Health == codersdk.WorkspaceAppHealthHealthy - }, 5*time.Second, 1*time.Second) + }, testutil.WaitLong, testutil.IntervalSlow) }) t.Run("500", func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() apps := []codersdk.WorkspaceApp{ { @@ -91,7 +92,7 @@ func TestAppHealth(t *testing.T) { } return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy - }, 10*time.Second, 1*time.Second) + }, testutil.WaitLong, testutil.IntervalSlow) }) t.Run("Timeout", func(t *testing.T) { From cf53ce6934ec92792c339153a8cef54586197a45 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:33:18 +0000 Subject: [PATCH 53/64] lint --- agent/apphealth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index ef7fad90f90ad..7413cfcba6266 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -127,7 +127,7 @@ func TestAppHealth(t *testing.T) { } return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy - }, 10*time.Second, 1*time.Second) + }, testutil.WaitLong, testutil.IntervalSlow) }) } From 2f17c5a6cb14e263d2714e384336bb0f608f4f80 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:36:09 +0000 Subject: [PATCH 54/64] lint --- agent/apphealth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index 7413cfcba6266..50856c29032fb 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -97,7 +97,7 @@ func TestAppHealth(t *testing.T) { t.Run("Timeout", func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() apps := []codersdk.WorkspaceApp{ { From e63769b3ccade8bd453b8f11e21b0ad62c5f45ad Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:38:59 +0000 Subject: [PATCH 55/64] make tests more reliable --- agent/apphealth_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index 50856c29032fb..ae44ce644e690 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -36,7 +36,7 @@ func TestAppHealth(t *testing.T) { // URL: We don't set the URL for this test because the setup will // create a httptest server for us and set it for us. Interval: 1, - Threshold: 3, + Threshold: 1, }, Health: codersdk.WorkspaceAppHealthInitializing, }, @@ -73,7 +73,7 @@ func TestAppHealth(t *testing.T) { // URL: We don't set the URL for this test because the setup will // create a httptest server for us and set it for us. Interval: 1, - Threshold: 3, + Threshold: 1, }, Health: codersdk.WorkspaceAppHealthInitializing, }, @@ -106,7 +106,7 @@ func TestAppHealth(t *testing.T) { // URL: We don't set the URL for this test because the setup will // create a httptest server for us and set it for us. Interval: 1, - Threshold: 3, + Threshold: 1, }, Health: codersdk.WorkspaceAppHealthInitializing, }, From 0fbd2514799173bb6323feb6a30d4650cd87384c Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:40:19 +0000 Subject: [PATCH 56/64] fix migration number --- .../migrations/000051_workspace_app_health.down.sql | 7 ------- .../database/migrations/000051_workspace_app_health.up.sql | 7 ------- 2 files changed, 14 deletions(-) delete mode 100644 coderd/database/migrations/000051_workspace_app_health.down.sql delete mode 100644 coderd/database/migrations/000051_workspace_app_health.up.sql diff --git a/coderd/database/migrations/000051_workspace_app_health.down.sql b/coderd/database/migrations/000051_workspace_app_health.down.sql deleted file mode 100644 index 33508eb9fc3d0..0000000000000 --- a/coderd/database/migrations/000051_workspace_app_health.down.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE ONLY workspace_apps - DROP COLUMN IF EXISTS healthcheck_url, - DROP COLUMN IF EXISTS healthcheck_interval, - DROP COLUMN IF EXISTS healthcheck_threshold, - DROP COLUMN IF EXISTS health; - -DROP TYPE workspace_app_health; diff --git a/coderd/database/migrations/000051_workspace_app_health.up.sql b/coderd/database/migrations/000051_workspace_app_health.up.sql deleted file mode 100644 index 3546174b40b85..0000000000000 --- a/coderd/database/migrations/000051_workspace_app_health.up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', 'unhealthy'); - -ALTER TABLE ONLY workspace_apps - ADD COLUMN IF NOT EXISTS healthcheck_url text NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS healthcheck_interval int NOT NULL DEFAULT 0, - ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0, - ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled'; From 1cde12bb74efae2a1e32dd5a049d567cf6140715 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:41:43 +0000 Subject: [PATCH 57/64] fix migration number --- .../migrations/000052_workspace_app_health.down.sql | 7 +++++++ .../database/migrations/000052_workspace_app_health.up.sql | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 coderd/database/migrations/000052_workspace_app_health.down.sql create mode 100644 coderd/database/migrations/000052_workspace_app_health.up.sql diff --git a/coderd/database/migrations/000052_workspace_app_health.down.sql b/coderd/database/migrations/000052_workspace_app_health.down.sql new file mode 100644 index 0000000000000..33508eb9fc3d0 --- /dev/null +++ b/coderd/database/migrations/000052_workspace_app_health.down.sql @@ -0,0 +1,7 @@ +ALTER TABLE ONLY workspace_apps + DROP COLUMN IF EXISTS healthcheck_url, + DROP COLUMN IF EXISTS healthcheck_interval, + DROP COLUMN IF EXISTS healthcheck_threshold, + DROP COLUMN IF EXISTS health; + +DROP TYPE workspace_app_health; diff --git a/coderd/database/migrations/000052_workspace_app_health.up.sql b/coderd/database/migrations/000052_workspace_app_health.up.sql new file mode 100644 index 0000000000000..3546174b40b85 --- /dev/null +++ b/coderd/database/migrations/000052_workspace_app_health.up.sql @@ -0,0 +1,7 @@ +CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', 'unhealthy'); + +ALTER TABLE ONLY workspace_apps + ADD COLUMN IF NOT EXISTS healthcheck_url text NOT NULL DEFAULT '', + ADD COLUMN IF NOT EXISTS healthcheck_interval int NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled'; From e7f93a949e7e9c7234bf06a43a4180e507fda4dd Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 16:59:26 +0000 Subject: [PATCH 58/64] fix goleak --- agent/apphealth.go | 89 +++++++++++++++++++++-------------------- agent/apphealth_test.go | 2 +- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index b4996d84a274b..c9ad1f3f5de5e 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -56,17 +56,19 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp } // run a ticker for each app health check. - tickers := make(chan string) + tickers := make(map[string]*time.Ticker) + tickerC := make(chan codersdk.WorkspaceApp) for _, app := range apps { if shouldStartTicker(app) { t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second) + tickers[app.Name] = t go func() { for { select { case <-ctx.Done(): return case <-t.C: - tickers <- app.Name + tickerC <- app } } }() @@ -79,53 +81,49 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp select { case <-ctx.Done(): return - case name := <-tickers: - for _, app := range apps { - if app.Name != name { - continue - } - - // we set the http timeout to the healthcheck interval to prevent getting too backed up. - client := &http.Client{ - Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, + case app := <-tickerC: + // we set the http timeout to the healthcheck interval to prevent getting too backed up. + client := &http.Client{ + Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, + } + err := func() error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) + if err != nil { + return err } - err := func() error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) - if err != nil { - return err - } - res, err := client.Do(req) - if err != nil { - return err - } - // successful healthcheck is a non-5XX status code - res.Body.Close() - if res.StatusCode >= http.StatusInternalServerError { - return xerrors.Errorf("error status code: %d", res.StatusCode) - } - - return nil - }() + res, err := client.Do(req) if err != nil { - mu.Lock() - if failures[app.Name] < int(app.Healthcheck.Threshold) { - // increment the failure count and keep status the same. - // we will change it when we hit the threshold. - failures[app.Name]++ - } else { - // set to unhealthy if we hit the failure threshold. - // we stop incrementing at the threshold to prevent the failure value from increasing forever. - health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy - } - mu.Unlock() + return err + } + // successful healthcheck is a non-5XX status code + res.Body.Close() + if res.StatusCode >= http.StatusInternalServerError { + return xerrors.Errorf("error status code: %d", res.StatusCode) + } + + return nil + }() + if err != nil { + mu.Lock() + if failures[app.Name] < int(app.Healthcheck.Threshold) { + // increment the failure count and keep status the same. + // we will change it when we hit the threshold. + failures[app.Name]++ } else { - mu.Lock() - // we only need one successful health check to be considered healthy. - health[app.Name] = codersdk.WorkspaceAppHealthHealthy - failures[app.Name] = 0 - mu.Unlock() + // set to unhealthy if we hit the failure threshold. + // we stop incrementing at the threshold to prevent the failure value from increasing forever. + health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy } + mu.Unlock() + } else { + mu.Lock() + // we only need one successful health check to be considered healthy. + health[app.Name] = codersdk.WorkspaceAppHealthHealthy + failures[app.Name] = 0 + mu.Unlock() } + + tickers[app.Name].Reset(time.Duration(app.Healthcheck.Interval)) } } }() @@ -159,6 +157,9 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp } }() if err != nil { + if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { + return + } logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) // continue loop with backoff on non-nil errors if r.Wait(ctx) { diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index ae44ce644e690..809fdbd68e5cd 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -113,7 +113,7 @@ func TestAppHealth(t *testing.T) { } handlers := []http.Handler{ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // sleep longer than the timeout to cause the health check to time out + // sleep longer than the interval to cause the health check to time out time.Sleep(2 * time.Second) httpapi.Write(r.Context(), w, http.StatusOK, nil) }), From d304f641820f0d20ad1b3aae1689fdf0b8532476 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 17:24:38 +0000 Subject: [PATCH 59/64] simplify goroutines --- agent/apphealth.go | 101 ++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index c9ad1f3f5de5e..1115bae0c8394 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -56,77 +56,64 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp } // run a ticker for each app health check. - tickers := make(map[string]*time.Ticker) - tickerC := make(chan codersdk.WorkspaceApp) + var mu sync.RWMutex + failures := make(map[string]int, 0) for _, app := range apps { if shouldStartTicker(app) { t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second) - tickers[app.Name] = t go func() { for { select { case <-ctx.Done(): return case <-t.C: - tickerC <- app + // we set the http timeout to the healthcheck interval to prevent getting too backed up. + client := &http.Client{ + Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, + } + err := func() error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) + if err != nil { + return err + } + res, err := client.Do(req) + if err != nil { + return err + } + // successful healthcheck is a non-5XX status code + res.Body.Close() + if res.StatusCode >= http.StatusInternalServerError { + return xerrors.Errorf("error status code: %d", res.StatusCode) + } + + return nil + }() + if err != nil { + mu.Lock() + if failures[app.Name] < int(app.Healthcheck.Threshold) { + // increment the failure count and keep status the same. + // we will change it when we hit the threshold. + failures[app.Name]++ + } else { + // set to unhealthy if we hit the failure threshold. + // we stop incrementing at the threshold to prevent the failure value from increasing forever. + health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy + } + mu.Unlock() + } else { + mu.Lock() + // we only need one successful health check to be considered healthy. + health[app.Name] = codersdk.WorkspaceAppHealthHealthy + failures[app.Name] = 0 + mu.Unlock() + } + + t.Reset(time.Duration(app.Healthcheck.Interval)) } } }() } } - var mu sync.RWMutex - failures := make(map[string]int, 0) - go func() { - for { - select { - case <-ctx.Done(): - return - case app := <-tickerC: - // we set the http timeout to the healthcheck interval to prevent getting too backed up. - client := &http.Client{ - Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, - } - err := func() error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) - if err != nil { - return err - } - res, err := client.Do(req) - if err != nil { - return err - } - // successful healthcheck is a non-5XX status code - res.Body.Close() - if res.StatusCode >= http.StatusInternalServerError { - return xerrors.Errorf("error status code: %d", res.StatusCode) - } - - return nil - }() - if err != nil { - mu.Lock() - if failures[app.Name] < int(app.Healthcheck.Threshold) { - // increment the failure count and keep status the same. - // we will change it when we hit the threshold. - failures[app.Name]++ - } else { - // set to unhealthy if we hit the failure threshold. - // we stop incrementing at the threshold to prevent the failure value from increasing forever. - health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy - } - mu.Unlock() - } else { - mu.Lock() - // we only need one successful health check to be considered healthy. - health[app.Name] = codersdk.WorkspaceAppHealthHealthy - failures[app.Name] = 0 - mu.Unlock() - } - - tickers[app.Name].Reset(time.Duration(app.Healthcheck.Interval)) - } - } - }() mu.Lock() lastHealth := copyHealth(health) From 634fb64cdca97c324fcb1b5de4a2bc9059053fe5 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 18:29:39 +0000 Subject: [PATCH 60/64] pr comments --- agent/apphealth.go | 221 +++++++++++++++++---------------- agent/apphealth_test.go | 3 +- coderd/workspaceagents_test.go | 2 +- 3 files changed, 116 insertions(+), 110 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 1115bae0c8394..cbbd4601301b9 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -24,125 +24,130 @@ type WorkspaceAppHealthReporter func(ctx context.Context) // NewWorkspaceAppHealthReporter creates a WorkspaceAppHealthReporter that reports app health to coderd. func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps WorkspaceAgentApps, postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth) WorkspaceAppHealthReporter { - return func(ctx context.Context) { - r := retry.New(time.Second, 30*time.Second) - for { - err := func() error { - apps, err := workspaceAgentApps(ctx) - if err != nil { - if xerrors.Is(err, context.Canceled) { - return nil - } - return xerrors.Errorf("getting workspace apps: %w", err) - } - - // no need to run this loop if no apps for this workspace. - if len(apps) == 0 { - return nil - } + runHealthcheckLoop := func(ctx context.Context) error { + apps, err := workspaceAgentApps(ctx) + if err != nil { + if xerrors.Is(err, context.Canceled) { + return nil + } + return xerrors.Errorf("getting workspace apps: %w", err) + } - hasHealthchecksEnabled := false - health := make(map[string]codersdk.WorkspaceAppHealth, 0) - for _, app := range apps { - health[app.Name] = app.Health - if !hasHealthchecksEnabled && app.Health != codersdk.WorkspaceAppHealthDisabled { - hasHealthchecksEnabled = true - } - } + // no need to run this loop if no apps for this workspace. + if len(apps) == 0 { + return nil + } - // no need to run this loop if no health checks are configured. - if !hasHealthchecksEnabled { - return nil - } + hasHealthchecksEnabled := false + health := make(map[string]codersdk.WorkspaceAppHealth, 0) + for _, app := range apps { + health[app.Name] = app.Health + if !hasHealthchecksEnabled && app.Health != codersdk.WorkspaceAppHealthDisabled { + hasHealthchecksEnabled = true + } + } - // run a ticker for each app health check. - var mu sync.RWMutex - failures := make(map[string]int, 0) - for _, app := range apps { - if shouldStartTicker(app) { - t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second) - go func() { - for { - select { - case <-ctx.Done(): - return - case <-t.C: - // we set the http timeout to the healthcheck interval to prevent getting too backed up. - client := &http.Client{ - Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, - } - err := func() error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) - if err != nil { - return err - } - res, err := client.Do(req) - if err != nil { - return err - } - // successful healthcheck is a non-5XX status code - res.Body.Close() - if res.StatusCode >= http.StatusInternalServerError { - return xerrors.Errorf("error status code: %d", res.StatusCode) - } - - return nil - }() - if err != nil { - mu.Lock() - if failures[app.Name] < int(app.Healthcheck.Threshold) { - // increment the failure count and keep status the same. - // we will change it when we hit the threshold. - failures[app.Name]++ - } else { - // set to unhealthy if we hit the failure threshold. - // we stop incrementing at the threshold to prevent the failure value from increasing forever. - health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy - } - mu.Unlock() - } else { - mu.Lock() - // we only need one successful health check to be considered healthy. - health[app.Name] = codersdk.WorkspaceAppHealthHealthy - failures[app.Name] = 0 - mu.Unlock() - } - - t.Reset(time.Duration(app.Healthcheck.Interval)) - } - } - }() - } - } + // no need to run this loop if no health checks are configured. + if !hasHealthchecksEnabled { + return nil + } - mu.Lock() - lastHealth := copyHealth(health) - mu.Unlock() - reportTicker := time.NewTicker(time.Second) - // every second we check if the health values of the apps have changed - // and if there is a change we will report the new values. + // run a ticker for each app health check. + var mu sync.RWMutex + failures := make(map[string]int, 0) + for _, nextApp := range apps { + if !shouldStartTicker(nextApp) { + continue + } + app := nextApp + t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second) + go func() { for { select { case <-ctx.Done(): + return + case <-t.C: + } + // we set the http timeout to the healthcheck interval to prevent getting too backed up. + client := &http.Client{ + Timeout: time.Duration(app.Healthcheck.Interval) * time.Second, + } + err := func() error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil) + if err != nil { + return err + } + res, err := client.Do(req) + if err != nil { + return err + } + // successful healthcheck is a non-5XX status code + res.Body.Close() + if res.StatusCode >= http.StatusInternalServerError { + return xerrors.Errorf("error status code: %d", res.StatusCode) + } + return nil - case <-reportTicker.C: - mu.RLock() - changed := healthChanged(lastHealth, health) - mu.RUnlock() - if changed { - mu.Lock() - lastHealth = copyHealth(health) - mu.Unlock() - err := postWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ - Healths: health, - }) - if err != nil { - logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) - } + }() + if err != nil { + mu.Lock() + if failures[app.Name] < int(app.Healthcheck.Threshold) { + // increment the failure count and keep status the same. + // we will change it when we hit the threshold. + failures[app.Name]++ + } else { + // set to unhealthy if we hit the failure threshold. + // we stop incrementing at the threshold to prevent the failure value from increasing forever. + health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy } + mu.Unlock() + } else { + mu.Lock() + // we only need one successful health check to be considered healthy. + health[app.Name] = codersdk.WorkspaceAppHealthHealthy + failures[app.Name] = 0 + mu.Unlock() } + + t.Reset(time.Duration(app.Healthcheck.Interval)) } }() + } + + mu.Lock() + lastHealth := copyHealth(health) + mu.Unlock() + reportTicker := time.NewTicker(time.Second) + // every second we check if the health values of the apps have changed + // and if there is a change we will report the new values. + for { + select { + case <-ctx.Done(): + return nil + case <-reportTicker.C: + mu.RLock() + changed := healthChanged(lastHealth, health) + mu.RUnlock() + if !changed { + continue + } + + mu.Lock() + lastHealth = copyHealth(health) + mu.Unlock() + err := postWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ + Healths: health, + }) + if err != nil { + logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) + } + } + } + } + + return func(ctx context.Context) { + for r := retry.New(time.Second, 30*time.Second); r.Wait(ctx); { + err := runHealthcheckLoop(ctx) if err != nil { if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { return @@ -167,7 +172,7 @@ func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]co for name, newValue := range new { oldValue, found := old[name] if !found { - panic("workspace app lengths are not equal") + return true } if newValue != oldValue { return true diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index 809fdbd68e5cd..35eaba67a2371 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -146,7 +146,8 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa var mu sync.Mutex workspaceAgentApps := func(context.Context) ([]codersdk.WorkspaceApp, error) { - return apps, nil + var newApps []codersdk.WorkspaceApp + return append(newApps, apps...), nil } postWorkspaceAgentAppHealth := func(_ context.Context, req codersdk.PostWorkspaceAppHealthsRequest) error { for name, health := range req.Healths { diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index fba4ce9953821..d92501ad01fd0 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -437,7 +437,7 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { }, }) require.Error(t, err) - // app.HealthEnabled == false + // healcheck disabled err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ Healths: map[string]codersdk.WorkspaceAppHealth{ "code-server": codersdk.WorkspaceAppHealthInitializing, From 7f3f45aadc42c2a53925aeb719503cd27fbdafd0 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 18:43:37 +0000 Subject: [PATCH 61/64] fix datarace in test --- agent/apphealth_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agent/apphealth_test.go b/agent/apphealth_test.go index 35eaba67a2371..de4a8d52eb27f 100644 --- a/agent/apphealth_test.go +++ b/agent/apphealth_test.go @@ -146,12 +146,14 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa var mu sync.Mutex workspaceAgentApps := func(context.Context) ([]codersdk.WorkspaceApp, error) { + mu.Lock() + defer mu.Unlock() var newApps []codersdk.WorkspaceApp return append(newApps, apps...), nil } postWorkspaceAgentAppHealth := func(_ context.Context, req codersdk.PostWorkspaceAppHealthsRequest) error { + mu.Lock() for name, health := range req.Healths { - mu.Lock() for i, app := range apps { if app.Name != name { continue @@ -159,8 +161,8 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa app.Health = health apps[i] = app } - mu.Unlock() } + mu.Unlock() return nil } From 7caea9af8f6eacab4946bd387b1fd98c9333c708 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 18:46:35 +0000 Subject: [PATCH 62/64] fix another datarace --- agent/apphealth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index cbbd4601301b9..03116141a156c 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -136,7 +136,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp lastHealth = copyHealth(health) mu.Unlock() err := postWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{ - Healths: health, + Healths: lastHealth, }) if err != nil { logger.Error(ctx, "failed to report workspace app stat", slog.Error(err)) From 52ab3dc32f555b57b4782795d8c69513d65273a5 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 18:50:19 +0000 Subject: [PATCH 63/64] dont wait twice --- agent/apphealth.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 03116141a156c..264c4cf59a8a7 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -154,9 +154,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp } logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) // continue loop with backoff on non-nil errors - if r.Wait(ctx) { - continue - } + continue } return From f1ca9c553208d10d2a52788ab75e13a11920b41f Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 23 Sep 2022 18:52:24 +0000 Subject: [PATCH 64/64] cleanup --- agent/apphealth.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/agent/apphealth.go b/agent/apphealth.go index 264c4cf59a8a7..9ddaa1a52f711 100644 --- a/agent/apphealth.go +++ b/agent/apphealth.go @@ -148,16 +148,10 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps Worksp return func(ctx context.Context) { for r := retry.New(time.Second, 30*time.Second); r.Wait(ctx); { err := runHealthcheckLoop(ctx) - if err != nil { - if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { - return - } - logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) - // continue loop with backoff on non-nil errors - continue + if err == nil || xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { + return } - - return + logger.Error(ctx, "failed running workspace app reporter", slog.Error(err)) } } }