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)) } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy