From bdfa61aa0df44a9725cf13ee8c47b42619248f49 Mon Sep 17 00:00:00 2001 From: kylecarbs Date: Fri, 6 May 2022 03:12:32 +0000 Subject: [PATCH 1/5] feat: Add app support This adds apps as a property to a workspace agent. The resource is added to the Terraform provider here: https://github.com/coder/terraform-provider-coder/pull/17 Apps will be opened in the dashboard or via the CLI with `coder open `. If `command` is specified, a terminal will appear locally and in the web. If `target` is specified, the browser will open to an exposed instance of that target. --- .vscode/settings.json | 1 + coderd/database/databasefake/databasefake.go | 54 ++ coderd/database/dump.sql | 19 + .../migrations/000011_workspace_apps.down.sql | 1 + .../migrations/000011_workspace_apps.up.sql | 13 + coderd/database/models.go | 10 + coderd/database/querier.go | 3 + coderd/database/queries.sql.go | 118 +++++ coderd/database/queries/workspaceapps.sql | 19 + coderd/provisionerdaemons.go | 23 +- coderd/provisionerjobs.go | 23 +- coderd/rbac/README.md | 2 +- coderd/rbac/object.go | 2 +- coderd/workspaceagents.go | 32 +- coderd/workspaceresources.go | 24 +- coderd/workspaceresources_test.go | 39 ++ codersdk/workspaceapps.go | 11 + codersdk/workspaceresources.go | 1 + provisioner/terraform/provision.go | 38 +- provisioner/terraform/provision_test.go | 48 +- provisionersdk/proto/provisioner.pb.go | 497 +++++++++++------- provisionersdk/proto/provisioner.proto | 13 +- site/src/api/typesGenerated.ts | 14 +- 23 files changed, 791 insertions(+), 214 deletions(-) create mode 100644 coderd/database/migrations/000011_workspace_apps.down.sql create mode 100644 coderd/database/migrations/000011_workspace_apps.up.sql create mode 100644 coderd/database/queries/workspaceapps.sql create mode 100644 codersdk/workspaceapps.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f7ea5c69fce3..ccc535fd1ae8a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "coderdtest", "codersdk", "devel", + "apps", "drpc", "drpcconn", "drpcmux", diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 5726c9f7befe5..1a2b1c7cdc346 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -34,6 +34,7 @@ func New() database.Store { templateVersions: make([]database.TemplateVersion, 0), templates: make([]database.Template, 0), workspaceBuilds: make([]database.WorkspaceBuild, 0), + workspaceApps: make([]database.WorkspaceApp, 0), workspaces: make([]database.Workspace, 0), } } @@ -62,6 +63,7 @@ type fakeQuerier struct { templateVersions []database.TemplateVersion templates []database.Template workspaceBuilds []database.WorkspaceBuild + workspaceApps []database.WorkspaceApp workspaces []database.Workspace } @@ -328,6 +330,41 @@ func (q *fakeQuerier) GetWorkspaceByOwnerIDAndName(_ context.Context, arg databa return database.Workspace{}, sql.ErrNoRows } +func (q *fakeQuerier) GetWorkspaceAppsByAgentID(_ context.Context, id uuid.UUID) ([]database.WorkspaceApp, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + apps := make([]database.WorkspaceApp, 0) + for _, app := range q.workspaceApps { + if app.AgentID == id { + apps = append(apps, app) + } + } + if len(apps) == 0 { + return nil, sql.ErrNoRows + } + return apps, nil +} + +func (q *fakeQuerier) GetWorkspaceAppsByAgentIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceApp, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + apps := make([]database.WorkspaceApp, 0) + for _, app := range q.workspaceApps { + for _, id := range ids { + if app.AgentID.String() == id.String() { + apps = append(apps, app) + break + } + } + } + if len(apps) == 0 { + return nil, sql.ErrNoRows + } + return apps, nil +} + func (q *fakeQuerier) GetWorkspaceOwnerCountsByTemplateIDs(_ context.Context, templateIDs []uuid.UUID) ([]database.GetWorkspaceOwnerCountsByTemplateIDsRow, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -1353,6 +1390,23 @@ func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser return workspaceBuild, nil } +func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertWorkspaceAppParams) (database.WorkspaceApp, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + + workspaceApp := database.WorkspaceApp{ + ID: arg.ID, + AgentID: arg.AgentID, + CreatedAt: arg.CreatedAt, + Name: arg.Name, + Icon: arg.Icon, + Command: arg.Command, + Target: arg.Target, + } + q.workspaceApps = append(q.workspaceApps, workspaceApp) + return workspaceApp, nil +} + 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 dd18186ea761b..c59e70db04675 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -280,6 +280,16 @@ CREATE TABLE workspace_agents ( directory character varying(4096) DEFAULT ''::character varying NOT NULL ); +CREATE TABLE workspace_apps ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + agent_id uuid NOT NULL, + name character varying(64) NOT NULL, + icon character varying(256) NOT NULL, + command character varying(65534), + target character varying(65534) +); + CREATE TABLE workspace_builds ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -382,6 +392,12 @@ ALTER TABLE ONLY users ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); +ALTER TABLE ONLY workspace_apps + ADD CONSTRAINT workspace_apps_agent_id_name_key UNIQUE (agent_id, name); + +ALTER TABLE ONLY workspace_apps + ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id); + ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); @@ -460,6 +476,9 @@ ALTER TABLE ONLY templates ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; +ALTER TABLE ONLY workspace_apps + ADD CONSTRAINT workspace_apps_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000011_workspace_apps.down.sql b/coderd/database/migrations/000011_workspace_apps.down.sql new file mode 100644 index 0000000000000..c1c0dabd478da --- /dev/null +++ b/coderd/database/migrations/000011_workspace_apps.down.sql @@ -0,0 +1 @@ +DROP TABLE workspace_apps; diff --git a/coderd/database/migrations/000011_workspace_apps.up.sql b/coderd/database/migrations/000011_workspace_apps.up.sql new file mode 100644 index 0000000000000..bea1278a2c5fa --- /dev/null +++ b/coderd/database/migrations/000011_workspace_apps.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE workspace_apps ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + agent_id uuid NOT NULL REFERENCES workspace_agents (id) ON DELETE CASCADE, + name varchar(64) NOT NULL, + icon varchar(256) NOT NULL, + -- A command to run when opened. + command varchar(65534), + -- A URL or port to target. + target varchar(65534), + PRIMARY KEY (id), + UNIQUE(agent_id, name) +); diff --git a/coderd/database/models.go b/coderd/database/models.go index 0fd6e4ca28270..26928c83937f7 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -493,6 +493,16 @@ type WorkspaceAgent struct { Directory string `db:"directory" json:"directory"` } +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"` + Target sql.NullString `db:"target" json:"target"` +} + type WorkspaceBuild struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index db2f3f0d49987..73057cc01085f 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -58,6 +58,8 @@ type querier interface { GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error) + GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) + GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error) GetWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceBuild, error) @@ -88,6 +90,7 @@ type querier interface { InsertUser(ctx context.Context, arg InsertUserParams) (User, error) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) + InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuild, error) InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error) UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index f91070915c097..7ff9b54102898 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2630,6 +2630,124 @@ func (q *sqlQuerier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg return err } +const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many +SELECT id, created_at, agent_id, name, icon, command, target FROM workspace_apps WHERE agent_id = $1 +` + +func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAppsByAgentID, agentID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceApp + for rows.Next() { + var i WorkspaceApp + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.AgentID, + &i.Name, + &i.Icon, + &i.Command, + &i.Target, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many +SELECT id, created_at, agent_id, name, icon, command, target FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) +` + +func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAppsByAgentIDs, pq.Array(ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceApp + for rows.Next() { + var i WorkspaceApp + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.AgentID, + &i.Name, + &i.Icon, + &i.Command, + &i.Target, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertWorkspaceApp = `-- name: InsertWorkspaceApp :one +INSERT INTO + workspace_apps ( + id, + created_at, + agent_id, + name, + icon, + command, + target + ) +VALUES + ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created_at, agent_id, name, icon, command, target +` + +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"` + Target sql.NullString `db:"target" json:"target"` +} + +func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) { + row := q.db.QueryRowContext(ctx, insertWorkspaceApp, + arg.ID, + arg.CreatedAt, + arg.AgentID, + arg.Name, + arg.Icon, + arg.Command, + arg.Target, + ) + var i WorkspaceApp + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.AgentID, + &i.Name, + &i.Icon, + &i.Command, + &i.Target, + ) + return i, err +} + const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one SELECT id, created_at, updated_at, workspace_id, template_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql new file mode 100644 index 0000000000000..1364fd3b59c93 --- /dev/null +++ b/coderd/database/queries/workspaceapps.sql @@ -0,0 +1,19 @@ +-- name: GetWorkspaceAppsByAgentID :many +SELECT * FROM workspace_apps WHERE agent_id = $1; + +-- name: GetWorkspaceAppsByAgentIDs :many +SELECT * FROM workspace_apps WHERE agent_id = ANY(@ids :: uuid [ ]); + +-- name: InsertWorkspaceApp :one +INSERT INTO + workspace_apps ( + id, + created_at, + agent_id, + name, + icon, + command, + target + ) +VALUES + ($1, $2, $3, $4, $5, $6, $7) RETURNING *; diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index a7639e6b76c3c..e7128833985de 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -625,7 +625,7 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. } } - _, err := db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{ + dbAgent, err := db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{ ID: uuid.New(), CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -645,6 +645,27 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. if err != nil { return xerrors.Errorf("insert agent: %w", err) } + + for _, app := range agent.Apps { + _, err := db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{ + ID: uuid.New(), + CreatedAt: database.Now(), + AgentID: dbAgent.ID, + Name: app.Name, + Icon: app.Icon, + Command: sql.NullString{ + String: app.Command, + Valid: app.Command != "", + }, + Target: sql.NullString{ + String: app.Target, + Valid: app.Target != "", + }, + }) + if err != nil { + return xerrors.Errorf("insert app: %w", err) + } + } } return nil } diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 5dd57211cbfe2..53d947116a4d2 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -209,6 +209,20 @@ func (api *api) provisionerJobResources(rw http.ResponseWriter, r *http.Request, }) return } + resourceAgentIDs := make([]uuid.UUID, 0) + for _, agent := range resourceAgents { + resourceAgentIDs = append(resourceAgentIDs, agent.ID) + } + apps, err := api.Database.GetWorkspaceAppsByAgentIDs(r.Context(), resourceAgentIDs) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get workspace apps: %s", err), + }) + return + } apiResources := make([]codersdk.WorkspaceResource, 0) for _, resource := range resources { @@ -217,7 +231,14 @@ func (api *api) provisionerJobResources(rw http.ResponseWriter, r *http.Request, if agent.ResourceID != resource.ID { continue } - apiAgent, err := convertWorkspaceAgent(agent, api.AgentConnectionUpdateFrequency) + dbApps := make([]database.WorkspaceApp, 0) + for _, app := range apps { + if app.AgentID == agent.ID { + dbApps = append(dbApps, app) + } + } + + apiAgent, err := convertWorkspaceAgent(agent, convertApps(dbApps), api.AgentConnectionUpdateFrequency) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("convert provisioner job agent: %s", err), diff --git a/coderd/rbac/README.md b/coderd/rbac/README.md index 69ba5e9f925aa..f2e1283206822 100644 --- a/coderd/rbac/README.md +++ b/coderd/rbac/README.md @@ -51,7 +51,7 @@ This can be represented by the following truth table, where Y represents *positi ## Example Permissions -- `+site.*.*.read`: allowed to perform the `read` action against all objects of type `devurl` in a given Coder deployment. +- `+site.*.*.read`: allowed to perform the `read` action against all objects of type `app` in a given Coder deployment. - `-user.workspace.*.create`: user is not allowed to create workspaces. ## Roles diff --git a/coderd/rbac/object.go b/coderd/rbac/object.go index a4be9b1edab5b..8f994a5dd3b58 100644 --- a/coderd/rbac/object.go +++ b/coderd/rbac/object.go @@ -41,7 +41,7 @@ type Object struct { // OrgID specifies which org the object is a part of. OrgID string `json:"org_owner"` - // Type is "workspace", "project", "devurl", etc + // Type is "workspace", "project", "app", etc Type string `json:"type"` // TODO: SharedUsers? } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index d2a7baeab560d..608fcd80f1c06 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -30,7 +30,14 @@ import ( func (api *api) workspaceAgent(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgentParam(r) - apiAgent, err := convertWorkspaceAgent(workspaceAgent, api.AgentConnectionUpdateFrequency) + dbApps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get workspace agent apps: %s", err), + }) + return + } + apiAgent, err := convertWorkspaceAgent(workspaceAgent, convertApps(dbApps), api.AgentConnectionUpdateFrequency) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("convert workspace agent: %s", err), @@ -48,7 +55,7 @@ func (api *api) workspaceAgentDial(rw http.ResponseWriter, r *http.Request) { defer api.websocketWaitGroup.Done() workspaceAgent := httpmw.WorkspaceAgentParam(r) - apiAgent, err := convertWorkspaceAgent(workspaceAgent, api.AgentConnectionUpdateFrequency) + apiAgent, err := convertWorkspaceAgent(workspaceAgent, nil, api.AgentConnectionUpdateFrequency) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("convert workspace agent: %s", err), @@ -92,7 +99,7 @@ func (api *api) workspaceAgentDial(rw http.ResponseWriter, r *http.Request) { func (api *api) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent(workspaceAgent, api.AgentConnectionUpdateFrequency) + apiAgent, err := convertWorkspaceAgent(workspaceAgent, nil, api.AgentConnectionUpdateFrequency) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("convert workspace agent: %s", err), @@ -331,7 +338,7 @@ func (api *api) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { defer api.websocketWaitGroup.Done() workspaceAgent := httpmw.WorkspaceAgentParam(r) - apiAgent, err := convertWorkspaceAgent(workspaceAgent, api.AgentConnectionUpdateFrequency) + apiAgent, err := convertWorkspaceAgent(workspaceAgent, nil, api.AgentConnectionUpdateFrequency) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("convert workspace agent: %s", err), @@ -451,7 +458,21 @@ func (api *api) dialWorkspaceAgent(r *http.Request, agentID uuid.UUID) (*agent.C }, nil } -func convertWorkspaceAgent(dbAgent database.WorkspaceAgent, agentUpdateFrequency time.Duration) (codersdk.WorkspaceAgent, error) { +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, + Target: dbApp.Target.String, + Icon: dbApp.Icon, + }) + } + return apps +} + +func convertWorkspaceAgent(dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, agentUpdateFrequency time.Duration) (codersdk.WorkspaceAgent, error) { var envs map[string]string if dbAgent.EnvironmentVariables.Valid { err := json.Unmarshal(dbAgent.EnvironmentVariables.RawMessage, &envs) @@ -471,6 +492,7 @@ func convertWorkspaceAgent(dbAgent database.WorkspaceAgent, agentUpdateFrequency StartupScript: dbAgent.StartupScript.String, EnvironmentVariables: envs, Directory: dbAgent.Directory, + Apps: apps, } if dbAgent.FirstConnectedAt.Valid { workspaceAgent.FirstConnectedAt = &dbAgent.FirstConnectedAt.Time diff --git a/coderd/workspaceresources.go b/coderd/workspaceresources.go index a337b3828f7ee..4595a95491d19 100644 --- a/coderd/workspaceresources.go +++ b/coderd/workspaceresources.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" + "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/coderd/httpmw" "github.com/coder/coder/codersdk" @@ -39,9 +40,30 @@ func (api *api) workspaceResource(rw http.ResponseWriter, r *http.Request) { }) return } + agentIDs := make([]uuid.UUID, 0) + for _, agent := range agents { + agentIDs = append(agentIDs, agent.ID) + } + apps, err := api.Database.GetWorkspaceAppsByAgentIDs(r.Context(), agentIDs) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get workspace apps: %s", err), + }) + return + } apiAgents := make([]codersdk.WorkspaceAgent, 0) for _, agent := range agents { - convertedAgent, err := convertWorkspaceAgent(agent, api.AgentConnectionUpdateFrequency) + dbApps := make([]database.WorkspaceApp, 0) + for _, app := range apps { + if app.AgentID == agent.ID { + dbApps = append(dbApps, app) + } + } + + convertedAgent, err := convertWorkspaceAgent(agent, convertApps(dbApps), api.AgentConnectionUpdateFrequency) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("convert provisioner job agent: %s", err), diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 8b4a846cfa258..2ba772c61a9ac 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -44,4 +44,43 @@ func TestWorkspaceResource(t *testing.T) { _, err = client.WorkspaceResource(context.Background(), resources[0].ID) require.NoError(t, err) }) + + t.Run("Apps", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + 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: "some", + Type: "example", + Agents: []*proto.Agent{{ + Id: "something", + Auth: &proto.Agent_Token{}, + Apps: []*proto.App{{ + Name: "code-server", + Command: "code-server", + }}, + }}, + }}, + }, + }, + }}, + }) + 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) + resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID) + require.NoError(t, err) + resource, err := client.WorkspaceResource(context.Background(), resources[0].ID) + require.NoError(t, err) + require.Len(t, resource.Agents, 1) + agent := resource.Agents[0] + require.Len(t, agent.Apps, 1) + }) } diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go new file mode 100644 index 0000000000000..faee514dba349 --- /dev/null +++ b/codersdk/workspaceapps.go @@ -0,0 +1,11 @@ +package codersdk + +import "github.com/google/uuid" + +type WorkspaceApp struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Command string `json:"command,omitempty"` + Target string `json:"target,omitempty"` + Icon string `json:"icon"` +} diff --git a/codersdk/workspaceresources.go b/codersdk/workspaceresources.go index b21451bbc63ea..1d11788857dd1 100644 --- a/codersdk/workspaceresources.go +++ b/codersdk/workspaceresources.go @@ -46,6 +46,7 @@ type WorkspaceAgent struct { OperatingSystem string `json:"operating_system"` StartupScript string `json:"startup_script,omitempty"` Directory string `json:"directory,omitempty"` + Apps []WorkspaceApp `json:"apps"` } type WorkspaceAgentResourceMetadata struct { diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 4e9865e829344..8a9c64d8b2497 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -344,7 +344,7 @@ func parseTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, planfi if resource.Mode == tfjson.DataResourceMode { continue } - if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" { + if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" { continue } resourceKey := strings.Join([]string{resource.Type, resource.Name}, ".") @@ -471,11 +471,45 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state } } + type appAttributes struct { + AgentID string `mapstructure:"agent_id"` + Name string `mapstructure:"name"` + Icon string `mapstructure:"icon"` + Target string `mapstructure:"target"` + Command string `mapstructure:"command"` + } + // Associate Apps with agents. + for _, resource := range state.Values.RootModule.Resources { + if resource.Type != "coder_app" { + continue + } + var attrs appAttributes + err = mapstructure.Decode(resource.AttributeValues, &attrs) + if err != nil { + return nil, xerrors.Errorf("decode app attributes: %w", err) + } + if attrs.Name == "" { + // Default to the resource name if none is set! + attrs.Name = resource.Name + } + for _, agent := range agents { + if agent.Id != attrs.AgentID { + continue + } + agent.Apps = append(agent.Apps, &proto.App{ + Name: attrs.Name, + Command: attrs.Command, + Target: attrs.Target, + Icon: attrs.Icon, + }) + } + } + for _, resource := range state.Values.RootModule.Resources { if resource.Mode == tfjson.DataResourceMode { continue } - if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" { + if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" { continue } resourceKey := strings.Join([]string{resource.Type, resource.Name}, ".") diff --git a/provisioner/terraform/provision_test.go b/provisioner/terraform/provision_test.go index 6169f8e5f0bc4..ff6e6a1493dab 100644 --- a/provisioner/terraform/provision_test.go +++ b/provisioner/terraform/provision_test.go @@ -28,7 +28,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.3.4" + version = "0.4.0" } } } @@ -432,6 +432,52 @@ provider "coder" { }, }, }, + }, { + Name: "agent-with-app", + Files: map[string]string{ + "main.tf": provider + ` + resource "coder_agent" "A" { + os = "darwin" + arch = "amd64" + } + resource "null_resource" "A" { + depends_on = [ + coder_agent.A + ] + } + resource "coder_app" "A" { + agent_id = coder_agent.A.id + command = "vim" + } + `, + }, + Request: &proto.Provision_Request{ + Type: &proto.Provision_Request_Start{ + Start: &proto.Provision_Start{ + Metadata: &proto.Provision_Metadata{}, + }, + }, + }, + Response: &proto.Provision_Response{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "A", + Type: "null_resource", + Agents: []*proto.Agent{{ + Name: "A", + OperatingSystem: "darwin", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + Apps: []*proto.App{{ + Name: "A", + Command: "vim", + }}, + }}, + }}, + }, + }, + }, }} { testCase := testCase t.Run(testCase.Name, func(t *testing.T) { diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 01d68d25a8619..2dc5c9d16f60c 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -711,6 +711,7 @@ type Agent struct { OperatingSystem string `protobuf:"bytes,5,opt,name=operating_system,json=operatingSystem,proto3" json:"operating_system,omitempty"` Architecture string `protobuf:"bytes,6,opt,name=architecture,proto3" json:"architecture,omitempty"` Directory string `protobuf:"bytes,7,opt,name=directory,proto3" json:"directory,omitempty"` + Apps []*App `protobuf:"bytes,8,rep,name=apps,proto3" json:"apps,omitempty"` // Types that are assignable to Auth: // *Agent_Token // *Agent_InstanceId @@ -798,6 +799,13 @@ func (x *Agent) GetDirectory() string { return "" } +func (x *Agent) GetApps() []*App { + if x != nil { + return x.Apps + } + return nil +} + func (m *Agent) GetAuth() isAgent_Auth { if m != nil { return m.Auth @@ -824,17 +832,89 @@ type isAgent_Auth interface { } type Agent_Token struct { - Token string `protobuf:"bytes,8,opt,name=token,proto3,oneof"` + Token string `protobuf:"bytes,9,opt,name=token,proto3,oneof"` } type Agent_InstanceId struct { - InstanceId string `protobuf:"bytes,9,opt,name=instance_id,json=instanceId,proto3,oneof"` + InstanceId string `protobuf:"bytes,10,opt,name=instance_id,json=instanceId,proto3,oneof"` } func (*Agent_Token) isAgent_Auth() {} func (*Agent_InstanceId) isAgent_Auth() {} +// App represents a dev-accessible application on the workspace. +type App struct { + state protoimpl.MessageState + 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"` + Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` + Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"` +} + +func (x *App) Reset() { + *x = App{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *App) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*App) ProtoMessage() {} + +func (x *App) ProtoReflect() protoreflect.Message { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[8] + 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 App.ProtoReflect.Descriptor instead. +func (*App) Descriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{8} +} + +func (x *App) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *App) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +func (x *App) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *App) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + // Resource represents created infrastructure. type Resource struct { state protoimpl.MessageState @@ -849,7 +929,7 @@ type Resource struct { func (x *Resource) Reset() { *x = Resource{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[8] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -862,7 +942,7 @@ func (x *Resource) String() string { func (*Resource) ProtoMessage() {} func (x *Resource) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[8] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -875,7 +955,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{8} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9} } func (x *Resource) GetName() string { @@ -909,7 +989,7 @@ type Parse struct { func (x *Parse) Reset() { *x = Parse{} 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) } @@ -922,7 +1002,7 @@ func (x *Parse) String() string { func (*Parse) ProtoMessage() {} func (x *Parse) 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 { @@ -935,7 +1015,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{9} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10} } // Provision consumes source-code from a directory to produce resources. @@ -948,7 +1028,7 @@ type Provision struct { func (x *Provision) Reset() { *x = Provision{} 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) } @@ -961,7 +1041,7 @@ func (x *Provision) String() string { func (*Provision) ProtoMessage() {} func (x *Provision) 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 { @@ -974,7 +1054,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{10} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11} } type Parse_Request struct { @@ -988,7 +1068,7 @@ type Parse_Request struct { func (x *Parse_Request) Reset() { *x = Parse_Request{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1001,7 +1081,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[12] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1014,7 +1094,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{9, 0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 0} } func (x *Parse_Request) GetDirectory() string { @@ -1035,7 +1115,7 @@ type Parse_Complete struct { func (x *Parse_Complete) Reset() { *x = Parse_Complete{} 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) } @@ -1048,7 +1128,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[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 { @@ -1061,7 +1141,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{9, 1} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 1} } func (x *Parse_Complete) GetParameterSchemas() []*ParameterSchema { @@ -1085,7 +1165,7 @@ type Parse_Response struct { func (x *Parse_Response) Reset() { *x = Parse_Response{} 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) } @@ -1098,7 +1178,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[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 { @@ -1111,7 +1191,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{9, 2} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 2} } func (m *Parse_Response) GetType() isParse_Response_Type { @@ -1167,7 +1247,7 @@ type Provision_Metadata struct { func (x *Provision_Metadata) Reset() { *x = Provision_Metadata{} 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) } @@ -1180,7 +1260,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[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 { @@ -1193,7 +1273,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{10, 0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 0} } func (x *Provision_Metadata) GetCoderUrl() string { @@ -1253,7 +1333,7 @@ type Provision_Start struct { func (x *Provision_Start) Reset() { *x = Provision_Start{} 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) } @@ -1266,7 +1346,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[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 { @@ -1279,7 +1359,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{10, 1} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 1} } func (x *Provision_Start) GetDirectory() string { @@ -1326,7 +1406,7 @@ type Provision_Cancel struct { func (x *Provision_Cancel) Reset() { *x = Provision_Cancel{} 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) } @@ -1339,7 +1419,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[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 { @@ -1352,7 +1432,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{10, 2} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 2} } type Provision_Request struct { @@ -1369,7 +1449,7 @@ type Provision_Request struct { func (x *Provision_Request) Reset() { *x = Provision_Request{} 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) } @@ -1382,7 +1462,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[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 { @@ -1395,7 +1475,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{10, 3} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 3} } func (m *Provision_Request) GetType() isProvision_Request_Type { @@ -1448,7 +1528,7 @@ type Provision_Complete struct { func (x *Provision_Complete) Reset() { *x = Provision_Complete{} 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) } @@ -1461,7 +1541,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[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 { @@ -1474,7 +1554,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{10, 4} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 4} } func (x *Provision_Complete) GetState() []byte { @@ -1512,7 +1592,7 @@ type Provision_Response struct { func (x *Provision_Response) Reset() { *x = Provision_Response{} 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) } @@ -1525,7 +1605,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[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 { @@ -1538,7 +1618,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{10, 5} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 5} } func (m *Provision_Response) GetType() isProvision_Response_Type { @@ -1660,7 +1740,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x70, 0x75, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0xe9, 0x02, 0x0a, + 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x98, 0x03, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, @@ -1675,114 +1755,123 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, - 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x5e, 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, 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, + 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x2d, 0x0a, + 0x07, 0x64, 0x65, 0x76, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x76, + 0x75, 0x72, 0x6c, 0x52, 0x07, 0x64, 0x65, 0x76, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, + 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x62, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x75, 0x72, + 0x6c, 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, + 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x22, 0x5e, 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, 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, 0xfa, 0x06, 0x0a, 0x09, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x9d, 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, 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, 0xfa, 0x06, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x9d, 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, 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, + 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 ( @@ -1798,7 +1887,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, 21) +var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: provisioner.LogLevel (WorkspaceTransition)(0), // 1: provisioner.WorkspaceTransition @@ -1813,19 +1902,20 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (*Log)(nil), // 10: provisioner.Log (*InstanceIdentityAuth)(nil), // 11: provisioner.InstanceIdentityAuth (*Agent)(nil), // 12: provisioner.Agent - (*Resource)(nil), // 13: provisioner.Resource - (*Parse)(nil), // 14: provisioner.Parse - (*Provision)(nil), // 15: provisioner.Provision - nil, // 16: provisioner.Agent.EnvEntry - (*Parse_Request)(nil), // 17: provisioner.Parse.Request - (*Parse_Complete)(nil), // 18: provisioner.Parse.Complete - (*Parse_Response)(nil), // 19: provisioner.Parse.Response - (*Provision_Metadata)(nil), // 20: provisioner.Provision.Metadata - (*Provision_Start)(nil), // 21: provisioner.Provision.Start - (*Provision_Cancel)(nil), // 22: provisioner.Provision.Cancel - (*Provision_Request)(nil), // 23: provisioner.Provision.Request - (*Provision_Complete)(nil), // 24: provisioner.Provision.Complete - (*Provision_Response)(nil), // 25: provisioner.Provision.Response + (*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 + (*Parse_Request)(nil), // 18: provisioner.Parse.Request + (*Parse_Complete)(nil), // 19: provisioner.Parse.Complete + (*Parse_Response)(nil), // 20: provisioner.Parse.Response + (*Provision_Metadata)(nil), // 21: provisioner.Provision.Metadata + (*Provision_Start)(nil), // 22: provisioner.Provision.Start + (*Provision_Cancel)(nil), // 23: provisioner.Provision.Cancel + (*Provision_Request)(nil), // 24: provisioner.Provision.Request + (*Provision_Complete)(nil), // 25: provisioner.Provision.Complete + (*Provision_Response)(nil), // 26: provisioner.Provision.Response } var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 2, // 0: provisioner.ParameterSource.scheme:type_name -> provisioner.ParameterSource.Scheme @@ -1835,28 +1925,29 @@ 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 - 16, // 7: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry - 12, // 8: provisioner.Resource.agents:type_name -> provisioner.Agent - 9, // 9: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema - 10, // 10: provisioner.Parse.Response.log:type_name -> provisioner.Log - 18, // 11: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete - 1, // 12: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 8, // 13: provisioner.Provision.Start.parameter_values:type_name -> provisioner.ParameterValue - 20, // 14: provisioner.Provision.Start.metadata:type_name -> provisioner.Provision.Metadata - 21, // 15: provisioner.Provision.Request.start:type_name -> provisioner.Provision.Start - 22, // 16: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel - 13, // 17: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource - 10, // 18: provisioner.Provision.Response.log:type_name -> provisioner.Log - 24, // 19: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete - 17, // 20: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request - 23, // 21: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request - 19, // 22: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response - 25, // 23: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response - 22, // [22:24] is the sub-list for method output_type - 20, // [20:22] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 17, // 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 + 9, // 10: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema + 10, // 11: provisioner.Parse.Response.log:type_name -> provisioner.Log + 19, // 12: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete + 1, // 13: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition + 8, // 14: provisioner.Provision.Start.parameter_values:type_name -> provisioner.ParameterValue + 21, // 15: provisioner.Provision.Start.metadata:type_name -> provisioner.Provision.Metadata + 22, // 16: provisioner.Provision.Request.start:type_name -> provisioner.Provision.Start + 23, // 17: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel + 14, // 18: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource + 10, // 19: provisioner.Provision.Response.log:type_name -> provisioner.Log + 25, // 20: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete + 18, // 21: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request + 24, // 22: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request + 20, // 23: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response + 26, // 24: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response + 23, // [23:25] is the sub-list for method output_type + 21, // [21:23] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_provisionersdk_proto_provisioner_proto_init() } @@ -1962,7 +2053,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Resource); i { + switch v := v.(*App); i { case 0: return &v.state case 1: @@ -1974,7 +2065,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.(*Parse); i { + switch v := v.(*Resource); i { case 0: return &v.state case 1: @@ -1986,6 +2077,18 @@ 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 { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Provision); i { case 0: return &v.state @@ -1997,7 +2100,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Parse_Request); i { case 0: return &v.state @@ -2009,7 +2112,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.(*Parse_Complete); i { case 0: return &v.state @@ -2021,7 +2124,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_Response); i { case 0: return &v.state @@ -2033,7 +2136,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.(*Provision_Metadata); i { case 0: return &v.state @@ -2045,7 +2148,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.(*Provision_Start); i { case 0: return &v.state @@ -2057,7 +2160,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_Cancel); i { case 0: return &v.state @@ -2069,7 +2172,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_Request); i { case 0: return &v.state @@ -2081,7 +2184,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_Complete); i { case 0: return &v.state @@ -2093,7 +2196,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_Response); i { case 0: return &v.state @@ -2110,15 +2213,15 @@ func file_provisionersdk_proto_provisioner_proto_init() { (*Agent_Token)(nil), (*Agent_InstanceId)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[14].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[15].OneofWrappers = []interface{}{ (*Parse_Response_Log)(nil), (*Parse_Response_Complete)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[18].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[19].OneofWrappers = []interface{}{ (*Provision_Request_Start)(nil), (*Provision_Request_Cancel)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[20].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[21].OneofWrappers = []interface{}{ (*Provision_Response_Log)(nil), (*Provision_Response_Complete)(nil), } @@ -2128,7 +2231,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc, NumEnums: 5, - NumMessages: 21, + NumMessages: 22, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 66bfee737a00a..738875d66c7d8 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -80,12 +80,21 @@ message Agent { string operating_system = 5; string architecture = 6; string directory = 7; + repeated App apps = 8; oneof auth { - string token = 8; - string instance_id = 9; + string token = 9; + string instance_id = 10; } } +// App represents a dev-accessible application on the workspace. +message App { + string name = 1; + string command = 2; + string target = 3; + string icon = 4; +} + // Resource represents created infrastructure. message Resource { string name = 1; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index ee4add928bb11..1cac7fa9e783f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -338,6 +338,7 @@ export interface WorkspaceAgent { readonly operating_system: string readonly startup_script: string readonly directory: string + readonly apps: WorkspaceApp[] } // From codersdk/workspaceagents.go:47:6 @@ -345,7 +346,7 @@ export interface WorkspaceAgentAuthenticateResponse { readonly session_token: string } -// From codersdk/workspaceresources.go:59:6 +// From codersdk/workspaceresources.go:60:6 export interface WorkspaceAgentInstanceMetadata { readonly jail_orchestrator: string readonly operating_system: string @@ -358,7 +359,7 @@ export interface WorkspaceAgentInstanceMetadata { readonly vnc: boolean } -// From codersdk/workspaceresources.go:51:6 +// From codersdk/workspaceresources.go:52:6 export interface WorkspaceAgentResourceMetadata { readonly memory_total: number readonly disk_total: number @@ -367,6 +368,15 @@ export interface WorkspaceAgentResourceMetadata { readonly cpu_mhz: number } +// From codersdk/workspacedevurls.go:5:6 +export interface WorkspaceApp { + readonly id: string + readonly name: string + readonly command: string + readonly target: string + readonly icon: string +} + // From codersdk/workspacebuilds.go:18:6 export interface WorkspaceBuild { readonly id: string From e3ff8adbfebe20512cfd3cfe7a87e2e511afefff Mon Sep 17 00:00:00 2001 From: kylecarbs Date: Tue, 24 May 2022 15:35:11 +0000 Subject: [PATCH 2/5] Compare fields in apps test --- coderd/rbac/authz.go | 1 + coderd/workspaceresources_test.go | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/coderd/rbac/authz.go b/coderd/rbac/authz.go index 5e566496093b2..b9f9e1f825e9e 100644 --- a/coderd/rbac/authz.go +++ b/coderd/rbac/authz.go @@ -3,6 +3,7 @@ package rbac import ( "context" _ "embed" + "golang.org/x/xerrors" "github.com/open-policy-agent/opa/rego" diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index c579ab25eab80..1759a36a55e93 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -49,6 +49,12 @@ func TestWorkspaceResource(t *testing.T) { _, client, coderd := coderdtest.NewWithServer(t, nil) user := coderdtest.CreateFirstUser(t, client) coderdtest.NewProvisionerDaemon(t, coderd) + app := &proto.App{ + Name: "code-server", + Command: "some-command", + Target: "http://localhost:3000", + Icon: "/code.svg", + } version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, Provision: []*proto.Provision_Response{{ @@ -60,10 +66,7 @@ func TestWorkspaceResource(t *testing.T) { Agents: []*proto.Agent{{ Id: "something", Auth: &proto.Agent_Token{}, - Apps: []*proto.App{{ - Name: "code-server", - Command: "code-server", - }}, + Apps: []*proto.App{app}, }}, }}, }, @@ -81,5 +84,10 @@ func TestWorkspaceResource(t *testing.T) { require.Len(t, resource.Agents, 1) agent := resource.Agents[0] require.Len(t, agent.Apps, 1) + 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) + require.Equal(t, app.Target, got.Target) }) } From b6e1ea6b39ee2c4d91096d66605f085419deea9d Mon Sep 17 00:00:00 2001 From: kylecarbs Date: Wed, 25 May 2022 14:27:53 +0000 Subject: [PATCH 3/5] Update Terraform provider to use relative path --- coderd/coderd.go | 9 +- coderd/database/databasefake/databasefake.go | 15 +- coderd/database/dump.sql | 3 +- ...own.sql => 000014_workspace_apps.down.sql} | 0 ...ps.up.sql => 000014_workspace_apps.up.sql} | 5 +- coderd/database/models.go | 15 +- coderd/database/queries.sql.go | 36 ++- coderd/database/queries/workspaceapps.sql | 5 +- coderd/provisionerdaemons.go | 7 +- coderd/workspaceagents.go | 10 +- coderd/workspaceresources_test.go | 4 +- codersdk/workspaceapps.go | 15 +- provisioner/terraform/provision.go | 20 +- provisioner/terraform/provision_test.go | 2 +- provisionersdk/proto/provisioner.pb.go | 258 +++++++++--------- provisionersdk/proto/provisioner.proto | 3 +- 16 files changed, 218 insertions(+), 189 deletions(-) rename coderd/database/migrations/{000011_workspace_apps.down.sql => 000014_workspace_apps.down.sql} (100%) rename coderd/database/migrations/{000011_workspace_apps.up.sql => 000014_workspace_apps.up.sql} (77%) diff --git a/coderd/coderd.go b/coderd/coderd.go index cde85eec1c978..e10cd62828d0b 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -34,10 +34,11 @@ import ( // Options are requires parameters for Coder to start. type Options struct { - AccessURL *url.URL - Logger slog.Logger - Database database.Store - Pubsub database.Pubsub + AccessURL *url.URL + WildcardURL *url.URL + Logger slog.Logger + Database database.Store + Pubsub database.Pubsub AgentConnectionUpdateFrequency time.Duration // APIRateLimit is the minutely throughput rate limit per user or ip. diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index d8d3302a110c1..8ebbb9eaf62f0 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -1533,13 +1533,14 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW defer q.mutex.Unlock() workspaceApp := database.WorkspaceApp{ - ID: arg.ID, - AgentID: arg.AgentID, - CreatedAt: arg.CreatedAt, - Name: arg.Name, - Icon: arg.Icon, - Command: arg.Command, - Target: arg.Target, + ID: arg.ID, + AgentID: arg.AgentID, + CreatedAt: arg.CreatedAt, + Name: arg.Name, + Icon: arg.Icon, + Command: arg.Command, + Url: arg.Url, + RelativePath: arg.RelativePath, } q.workspaceApps = append(q.workspaceApps, workspaceApp) return workspaceApp, nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index c53f66e8f4fe3..4bf028644535f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -288,7 +288,8 @@ CREATE TABLE workspace_apps ( name character varying(64) NOT NULL, icon character varying(256) NOT NULL, command character varying(65534), - target character varying(65534) + url character varying(65534), + relative_path boolean DEFAULT false NOT NULL ); CREATE TABLE workspace_builds ( diff --git a/coderd/database/migrations/000011_workspace_apps.down.sql b/coderd/database/migrations/000014_workspace_apps.down.sql similarity index 100% rename from coderd/database/migrations/000011_workspace_apps.down.sql rename to coderd/database/migrations/000014_workspace_apps.down.sql diff --git a/coderd/database/migrations/000011_workspace_apps.up.sql b/coderd/database/migrations/000014_workspace_apps.up.sql similarity index 77% rename from coderd/database/migrations/000011_workspace_apps.up.sql rename to coderd/database/migrations/000014_workspace_apps.up.sql index bea1278a2c5fa..7a991c179ee13 100644 --- a/coderd/database/migrations/000011_workspace_apps.up.sql +++ b/coderd/database/migrations/000014_workspace_apps.up.sql @@ -4,10 +4,9 @@ CREATE TABLE workspace_apps ( agent_id uuid NOT NULL REFERENCES workspace_agents (id) ON DELETE CASCADE, name varchar(64) NOT NULL, icon varchar(256) NOT NULL, - -- A command to run when opened. command varchar(65534), - -- A URL or port to target. - target varchar(65534), + url varchar(65534), + relative_path boolean NOT NULL DEFAULT false, PRIMARY KEY (id), UNIQUE(agent_id, name) ); diff --git a/coderd/database/models.go b/coderd/database/models.go index ec5a815e73ce9..4253103011acf 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -495,13 +495,14 @@ 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"` - Target sql.NullString `db:"target" json:"target"` + 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"` } type WorkspaceBuild struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 207fd39e2cd62..26d6a3f78ffc6 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2750,7 +2750,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg } const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many -SELECT id, created_at, agent_id, name, icon, command, target FROM workspace_apps WHERE agent_id = $1 +SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1 ` func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) { @@ -2769,7 +2769,8 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid &i.Name, &i.Icon, &i.Command, - &i.Target, + &i.Url, + &i.RelativePath, ); err != nil { return nil, err } @@ -2785,7 +2786,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid } const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many -SELECT id, created_at, agent_id, name, icon, command, target FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) +SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ` func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) { @@ -2804,7 +2805,8 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid. &i.Name, &i.Icon, &i.Command, - &i.Target, + &i.Url, + &i.RelativePath, ); err != nil { return nil, err } @@ -2828,20 +2830,22 @@ INSERT INTO name, icon, command, - target + url, + relative_path ) VALUES - ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created_at, agent_id, name, icon, command, target + ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path ` 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"` - Target sql.NullString `db:"target" json:"target"` + 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"` } func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) { @@ -2852,7 +2856,8 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace arg.Name, arg.Icon, arg.Command, - arg.Target, + arg.Url, + arg.RelativePath, ) var i WorkspaceApp err := row.Scan( @@ -2862,7 +2867,8 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace &i.Name, &i.Icon, &i.Command, - &i.Target, + &i.Url, + &i.RelativePath, ) return i, err } diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql index 1364fd3b59c93..4b63ba1be5580 100644 --- a/coderd/database/queries/workspaceapps.sql +++ b/coderd/database/queries/workspaceapps.sql @@ -13,7 +13,8 @@ INSERT INTO name, icon, command, - target + url, + relative_path ) VALUES - ($1, $2, $3, $4, $5, $6, $7) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index aac427599d30a..891c7bc51c036 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -665,10 +665,11 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. String: app.Command, Valid: app.Command != "", }, - Target: sql.NullString{ - String: app.Target, - Valid: app.Target != "", + Url: sql.NullString{ + String: app.Url, + Valid: app.Url != "", }, + RelativePath: app.RelativePath, }) if err != nil { return xerrors.Errorf("insert app: %w", err) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 875d5a7314c22..765714b6f510d 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -462,11 +462,11 @@ 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, - Target: dbApp.Target.String, - Icon: dbApp.Icon, + ID: dbApp.ID, + Name: dbApp.Name, + Command: dbApp.Command.String, + AccessURL: dbApp.Url.String, + Icon: dbApp.Icon, }) } return apps diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 1759a36a55e93..f448c99e51fbd 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -52,7 +52,7 @@ func TestWorkspaceResource(t *testing.T) { app := &proto.App{ Name: "code-server", Command: "some-command", - Target: "http://localhost:3000", + Url: "http://localhost:3000", Icon: "/code.svg", } version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ @@ -88,6 +88,6 @@ 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) - require.Equal(t, app.Target, got.Target) + require.Equal(t, app.Url, got.AccessURL) }) } diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index faee514dba349..ca46e83a33e64 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -3,9 +3,14 @@ package codersdk import "github.com/google/uuid" type WorkspaceApp struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Command string `json:"command,omitempty"` - Target string `json:"target,omitempty"` - Icon string `json:"icon"` + ID uuid.UUID `json:"id"` + // Name is a unique identifier attached to an agent. + Name string `json:"name"` + Command string `json:"command,omitempty"` + // AccessURL is an address used to access the application. + // If command is specified, this will be omitted. + AccessURL string `json:"access_url,omitempty"` + // Icon is a relative path or external URL that specifies + // an icon to be displayed in the dashboard. + Icon string `json:"icon"` } diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 3839e1f1f9e79..b08b46725eea0 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -523,11 +523,12 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state } type appAttributes struct { - AgentID string `mapstructure:"agent_id"` - Name string `mapstructure:"name"` - Icon string `mapstructure:"icon"` - Target string `mapstructure:"target"` - Command string `mapstructure:"command"` + 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"` } // Associate Apps with agents. for _, resource := range state.Values.RootModule.Resources { @@ -548,10 +549,11 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state continue } agent.Apps = append(agent.Apps, &proto.App{ - Name: attrs.Name, - Command: attrs.Command, - Target: attrs.Target, - Icon: attrs.Icon, + Name: attrs.Name, + Command: attrs.Command, + Url: attrs.URL, + Icon: attrs.Icon, + RelativePath: attrs.RelativePath, }) } } diff --git a/provisioner/terraform/provision_test.go b/provisioner/terraform/provision_test.go index c32b37cfee17f..77f1e8023c63b 100644 --- a/provisioner/terraform/provision_test.go +++ b/provisioner/terraform/provision_test.go @@ -30,7 +30,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.4.0" + version = "0.4.2" } } } diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 2dc5c9d16f60c..eee976e770aa9 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -711,7 +711,7 @@ type Agent struct { OperatingSystem string `protobuf:"bytes,5,opt,name=operating_system,json=operatingSystem,proto3" json:"operating_system,omitempty"` Architecture string `protobuf:"bytes,6,opt,name=architecture,proto3" json:"architecture,omitempty"` Directory string `protobuf:"bytes,7,opt,name=directory,proto3" json:"directory,omitempty"` - Apps []*App `protobuf:"bytes,8,rep,name=apps,proto3" json:"apps,omitempty"` + Apps []*App `protobuf:"bytes,8,rep,name=apps,proto3" json:"apps,omitempty"` // Types that are assignable to Auth: // *Agent_Token // *Agent_InstanceId @@ -849,10 +849,11 @@ 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"` - Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` - Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,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"` } func (x *App) Reset() { @@ -901,9 +902,9 @@ func (x *App) GetCommand() string { return "" } -func (x *App) GetTarget() string { +func (x *App) GetUrl() string { if x != nil { - return x.Target + return x.Url } return "" } @@ -915,6 +916,13 @@ func (x *App) GetIcon() string { return "" } +func (x *App) GetRelativePath() bool { + if x != nil { + return x.RelativePath + } + return false +} + // Resource represents created infrastructure. type Resource struct { state protoimpl.MessageState @@ -1740,7 +1748,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x70, 0x75, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x98, 0x03, 0x0a, + 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x8f, 0x03, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, @@ -1755,123 +1763,125 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x2d, 0x0a, - 0x07, 0x64, 0x65, 0x76, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x76, - 0x75, 0x72, 0x6c, 0x52, 0x07, 0x64, 0x65, 0x76, 0x75, 0x72, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x05, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, - 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x62, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x75, 0x72, - 0x6c, 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, - 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x22, 0x5e, 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, 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, + 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, + 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, + 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x36, + 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 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, 0x5e, + 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, 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, 0xfa, 0x06, + 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x9d, 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, 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, 0xfa, 0x06, 0x0a, 0x09, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x9d, 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, + 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, 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, 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, + 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 ( @@ -1902,7 +1912,7 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (*Log)(nil), // 10: provisioner.Log (*InstanceIdentityAuth)(nil), // 11: provisioner.InstanceIdentityAuth (*Agent)(nil), // 12: provisioner.Agent - (*App)(nil), // 13: provisioner.App + (*App)(nil), // 13: provisioner.App (*Resource)(nil), // 14: provisioner.Resource (*Parse)(nil), // 15: provisioner.Parse (*Provision)(nil), // 16: provisioner.Provision diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 738875d66c7d8..86e61ca8d9884 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -91,8 +91,9 @@ message Agent { message App { string name = 1; string command = 2; - string target = 3; + string url = 3; string icon = 4; + bool relative_path = 5; } // Resource represents created infrastructure. From 430cfe73f610b29011416569e936452f4a460d0d Mon Sep 17 00:00:00 2001 From: kylecarbs Date: Thu, 26 May 2022 02:14:40 +0000 Subject: [PATCH 4/5] Add some basic structure for routing --- coderd/coderd.go | 14 +++++++++++++ coderd/httpmw/apikey.go | 2 +- coderd/httpmw/wildcard.go | 24 +++++++++++++++++++++ coderd/httpmw/wildcard_test.go | 38 ++++++++++++++++++++++++++++++++++ coderd/workspaceapps.go | 9 ++++++++ coderd/workspaceapps_test.go | 1 + go.mod | 2 ++ go.sum | 2 ++ 8 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 coderd/httpmw/wildcard.go create mode 100644 coderd/httpmw/wildcard_test.go create mode 100644 coderd/workspaceapps.go create mode 100644 coderd/workspaceapps_test.go diff --git a/coderd/coderd.go b/coderd/coderd.go index e10cd62828d0b..b97cbc2965127 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -15,6 +15,8 @@ import ( "golang.org/x/xerrors" "google.golang.org/api/idtoken" + "github.com/go-chi/cors" + sdktrace "go.opentelemetry.io/otel/sdk/trace" "cdr.dev/slog" @@ -107,6 +109,15 @@ func newRouter(options *Options, a *api) chi.Router { tracing.HTTPMW(a.TracerProvider, "coderd.http"), ) + r.Route("/{user}/{workspaceagent}/{application}", func(r chi.Router) { + r.Use( + httpmw.RateLimitPerMinute(options.APIRateLimit), + apiKeyMiddleware, + httpmw.ExtractUserParam(a.Database), + authRolesMiddleware, + ) + }) + r.Route("/api/v2", func(r chi.Router) { r.NotFound(func(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ @@ -320,6 +331,9 @@ func newRouter(options *Options, a *api) chi.Router { r.Get("/watch", a.watchWorkspace) }) }) + r.Route("/wildcardauth", func(r chi.Router) { + r.Use(cors.Handler(cors.Options{})) + }) r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) { r.Use( apiKeyMiddleware, diff --git a/coderd/httpmw/apikey.go b/coderd/httpmw/apikey.go index abf55128d55d7..77a4b00af01be 100644 --- a/coderd/httpmw/apikey.go +++ b/coderd/httpmw/apikey.go @@ -17,7 +17,7 @@ import ( "github.com/coder/coder/coderd/httpapi" ) -// SessionTokenKey represents the name of the cookie or query paramater the API key is stored in. +// SessionTokenKey represents the name of the cookie or query parameter the API key is stored in. const SessionTokenKey = "session_token" type apiKeyContextKey struct{} diff --git a/coderd/httpmw/wildcard.go b/coderd/httpmw/wildcard.go new file mode 100644 index 0000000000000..c2cc5be1f451c --- /dev/null +++ b/coderd/httpmw/wildcard.go @@ -0,0 +1,24 @@ +package httpmw + +import ( + "net/http" + "strings" +) + +// Wildcard routes to the provided handler if the request host has the suffix of hostname. +func Wildcard(hostname string, handler http.HandlerFunc) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + ) + + if !strings.HasSuffix(r.Host, hostname) { + next.ServeHTTP(w, r) + return + } + + handler(w, r.WithContext(ctx)) + }) + } +} diff --git a/coderd/httpmw/wildcard_test.go b/coderd/httpmw/wildcard_test.go new file mode 100644 index 0000000000000..ba0a7ebd607ca --- /dev/null +++ b/coderd/httpmw/wildcard_test.go @@ -0,0 +1,38 @@ +package httpmw_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/coderd/httpmw" +) + +func TestWildcard(t *testing.T) { + t.Parallel() + t.Run("Match", func(t *testing.T) { + t.Parallel() + req := httptest.NewRequest("GET", "http://frogs.bananas.org", nil) + res := httptest.NewRecorder() + httpmw.Wildcard("bananas.org", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + })).ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Result().StatusCode) + }) + + t.Run("Passthrough", func(t *testing.T) { + t.Parallel() + req := httptest.NewRequest("GET", "http://frogs.apples.org", nil) + res := httptest.NewRecorder() + httpmw.Wildcard("bananas.org", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + })).ServeHTTP(res, req) + require.Equal(t, http.StatusForbidden, res.Result().StatusCode) + }) +} diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go new file mode 100644 index 0000000000000..280710a2123de --- /dev/null +++ b/coderd/workspaceapps.go @@ -0,0 +1,9 @@ +package coderd + +import ( + "net/http" +) + +func (api *api) proxyPath(rw http.ResponseWriter, r *http.Request) { + +} diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go new file mode 100644 index 0000000000000..a1bbc0bfabd3f --- /dev/null +++ b/coderd/workspaceapps_test.go @@ -0,0 +1 @@ +package coderd_test diff --git a/go.mod b/go.mod index 1271d4bdc0e0b..38ea6b634112d 100644 --- a/go.mod +++ b/go.mod @@ -126,6 +126,8 @@ require ( storj.io/drpc v0.0.30 ) +require github.com/go-chi/cors v1.2.1 + require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect diff --git a/go.sum b/go.sum index 5c8ba9c18ffb4..d21ccb4729924 100644 --- a/go.sum +++ b/go.sum @@ -571,6 +571,8 @@ github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/httprate v0.5.3 h1:5HPWb0N6ymIiuotMtCfOGpQKiKeqXVzMexHh1W1yXPc= github.com/go-chi/httprate v0.5.3/go.mod h1:kYR4lorHX3It9tTh4eTdHhcF2bzrYnCrRNlv5+IBm2M= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= From 6ef781cca36030834c64d94a0bc8af0692625113 Mon Sep 17 00:00:00 2001 From: kylecarbs Date: Thu, 26 May 2022 02:29:06 +0000 Subject: [PATCH 5/5] chore: Remove interface from coderd and lift API surface Abstracting coderd into an interface added misdirection because the interface was never intended to be fulfilled outside of a single implementation. This lifts the abstraction, and attaches all handlers to a root struct named `*coderd.API`. --- cli/server.go | 10 +- coderd/authorize.go | 4 +- coderd/coderd.go | 227 +++++++++++++++----------------- coderd/coderdtest/coderdtest.go | 8 +- coderd/files.go | 4 +- coderd/gitsshkey.go | 6 +- coderd/members.go | 4 +- coderd/organizations.go | 2 +- coderd/parameters.go | 6 +- coderd/provisionerdaemons.go | 18 +-- coderd/provisionerjobs.go | 4 +- coderd/roles.go | 6 +- coderd/templates.go | 10 +- coderd/templateversions.go | 20 +-- coderd/userauth.go | 4 +- coderd/users.go | 40 +++--- coderd/workspaceagents.go | 16 +-- coderd/workspaceapps.go | 9 ++ coderd/workspacebuilds.go | 16 +-- coderd/workspaceresourceauth.go | 8 +- coderd/workspaceresources.go | 2 +- coderd/workspaces.go | 18 +-- 22 files changed, 215 insertions(+), 227 deletions(-) create mode 100644 coderd/workspaceapps.go diff --git a/cli/server.go b/cli/server.go index c778e0a220235..4ccec67810a63 100644 --- a/cli/server.go +++ b/cli/server.go @@ -319,7 +319,7 @@ func server() *cobra.Command { // These errors are typically noise like "TLS: EOF". Vault does similar: // https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714 ErrorLog: log.New(io.Discard, "", 0), - Handler: coderDaemon.Handler(), + Handler: coderDaemon, BaseContext: func(_ net.Listener) context.Context { return shutdownConnsCtx }, @@ -387,7 +387,7 @@ func server() *cobra.Command { signal.Notify(stopChan, os.Interrupt) select { case <-cmd.Context().Done(): - coderDaemon.CloseWait() + coderDaemon.Close() return cmd.Context().Err() case err := <-tunnelErrChan: if err != nil { @@ -395,7 +395,7 @@ func server() *cobra.Command { } case err := <-errCh: shutdownConns() - coderDaemon.CloseWait() + coderDaemon.Close() return err case <-stopChan: } @@ -459,7 +459,7 @@ func server() *cobra.Command { _, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Waiting for WebSocket connections to close...\n") shutdownConns() - coderDaemon.CloseWait() + coderDaemon.Close() return nil }, } @@ -555,7 +555,7 @@ func createFirstUser(cmd *cobra.Command, client *codersdk.Client, cfg config.Roo } // nolint:revive -func newProvisionerDaemon(ctx context.Context, coderDaemon coderd.CoderD, +func newProvisionerDaemon(ctx context.Context, coderDaemon *coderd.API, logger slog.Logger, cacheDir string, errChan chan error, dev bool) (*provisionerd.Server, error) { err := os.MkdirAll(cacheDir, 0700) if err != nil { diff --git a/coderd/authorize.go b/coderd/authorize.go index 13374344fae1f..b98ad5e20f83c 100644 --- a/coderd/authorize.go +++ b/coderd/authorize.go @@ -12,12 +12,12 @@ import ( "github.com/coder/coder/coderd/rbac" ) -func AuthorizeFilter[O rbac.Objecter](api *api, r *http.Request, action rbac.Action, objects []O) []O { +func AuthorizeFilter[O rbac.Objecter](api *API, r *http.Request, action rbac.Action, objects []O) []O { roles := httpmw.UserRoles(r) return rbac.Filter(r.Context(), api.Authorizer, roles.ID.String(), roles.Roles, action, objects) } -func (api *api) Authorize(rw http.ResponseWriter, r *http.Request, action rbac.Action, object rbac.Objecter) bool { +func (api *API) Authorize(rw http.ResponseWriter, r *http.Request, action rbac.Action, object rbac.Objecter) bool { roles := httpmw.UserRoles(r) err := api.Authorizer.ByRoleName(r.Context(), roles.ID.String(), roles.Roles, action, object.RBACObject()) if err != nil { diff --git a/coderd/coderd.go b/coderd/coderd.go index c474aeb785215..fb042d8b65045 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -28,7 +28,6 @@ import ( "github.com/coder/coder/coderd/tracing" "github.com/coder/coder/coderd/turnconn" "github.com/coder/coder/codersdk" - "github.com/coder/coder/provisionerd/proto" "github.com/coder/coder/site" ) @@ -56,22 +55,14 @@ type Options struct { TracerProvider *sdktrace.TracerProvider } -type CoderD interface { - Handler() http.Handler - CloseWait() - - // An in-process provisionerd connection. - ListenProvisionerDaemon(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) -} - -type coderD struct { - api *api - router chi.Router - options *Options -} +// New constructs a Coder API handler. +func New(options *Options) *API { + r := chi.NewRouter() + api := &API{ + Options: options, + router: r, + } -// newRouter constructs the Chi Router for the given API. -func newRouter(options *Options, a *api) chi.Router { if options.AgentConnectionUpdateFrequency == 0 { options.AgentConnectionUpdateFrequency = 3 * time.Second } @@ -90,12 +81,9 @@ func newRouter(options *Options, a *api) chi.Router { apiKeyMiddleware := httpmw.ExtractAPIKey(options.Database, &httpmw.OAuth2Configs{ Github: options.GithubOAuth2Config, }) - // TODO: @emyrk we should just move this into 'ExtractAPIKey'. authRolesMiddleware := httpmw.ExtractUserRoles(options.Database) - r := chi.NewRouter() - r.Use( func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -103,7 +91,7 @@ func newRouter(options *Options, a *api) chi.Router { }) }, httpmw.Prometheus, - tracing.HTTPMW(a.TracerProvider, "coderd.http"), + tracing.HTTPMW(api.TracerProvider, "coderd.http"), ) r.Route("/api/v2", func(r chi.Router) { @@ -116,7 +104,7 @@ func newRouter(options *Options, a *api) chi.Router { r.Use( // Specific routes can specify smaller limits. httpmw.RateLimitPerMinute(options.APIRateLimit), - debugLogRequest(a.Logger), + debugLogRequest(api.Logger), ) r.Get("/", func(w http.ResponseWriter, r *http.Request) { httpapi.Write(w, http.StatusOK, httpapi.Response{ @@ -139,8 +127,8 @@ func newRouter(options *Options, a *api) chi.Router { // file content is expensive so it should be small. httpmw.RateLimitPerMinute(12), ) - r.Get("/{hash}", a.fileByHash) - r.Post("/", a.postFile) + r.Get("/{hash}", api.fileByHash) + r.Post("/", api.postFile) }) r.Route("/organizations/{organization}", func(r chi.Router) { r.Use( @@ -148,40 +136,40 @@ func newRouter(options *Options, a *api) chi.Router { httpmw.ExtractOrganizationParam(options.Database), authRolesMiddleware, ) - r.Get("/", a.organization) - r.Get("/provisionerdaemons", a.provisionerDaemonsByOrganization) - r.Post("/templateversions", a.postTemplateVersionsByOrganization) + r.Get("/", api.organization) + r.Get("/provisionerdaemons", api.provisionerDaemonsByOrganization) + r.Post("/templateversions", api.postTemplateVersionsByOrganization) r.Route("/templates", func(r chi.Router) { - r.Post("/", a.postTemplateByOrganization) - r.Get("/", a.templatesByOrganization) - r.Get("/{templatename}", a.templateByOrganizationAndName) + r.Post("/", api.postTemplateByOrganization) + r.Get("/", api.templatesByOrganization) + r.Get("/{templatename}", api.templateByOrganizationAndName) }) r.Route("/workspaces", func(r chi.Router) { - r.Post("/", a.postWorkspacesByOrganization) - r.Get("/", a.workspacesByOrganization) + r.Post("/", api.postWorkspacesByOrganization) + r.Get("/", api.workspacesByOrganization) r.Route("/{user}", func(r chi.Router) { r.Use(httpmw.ExtractUserParam(options.Database)) - r.Get("/{workspacename}", a.workspaceByOwnerAndName) - r.Get("/", a.workspacesByOwner) + r.Get("/{workspacename}", api.workspaceByOwnerAndName) + r.Get("/", api.workspacesByOwner) }) }) r.Route("/members", func(r chi.Router) { - r.Get("/roles", a.assignableOrgRoles) + r.Get("/roles", api.assignableOrgRoles) r.Route("/{user}", func(r chi.Router) { r.Use( httpmw.ExtractUserParam(options.Database), httpmw.ExtractOrganizationMemberParam(options.Database), ) - r.Put("/roles", a.putMemberRoles) + r.Put("/roles", api.putMemberRoles) }) }) }) r.Route("/parameters/{scope}/{id}", func(r chi.Router) { r.Use(apiKeyMiddleware) - r.Post("/", a.postParameter) - r.Get("/", a.parameters) + r.Post("/", api.postParameter) + r.Get("/", api.parameters) r.Route("/{name}", func(r chi.Router) { - r.Delete("/", a.deleteParameter) + r.Delete("/", api.deleteParameter) }) }) r.Route("/templates/{template}", func(r chi.Router) { @@ -191,12 +179,12 @@ func newRouter(options *Options, a *api) chi.Router { httpmw.ExtractTemplateParam(options.Database), ) - r.Get("/", a.template) - r.Delete("/", a.deleteTemplate) + r.Get("/", api.template) + r.Delete("/", api.deleteTemplate) r.Route("/versions", func(r chi.Router) { - r.Get("/", a.templateVersionsByTemplate) - r.Patch("/", a.patchActiveTemplateVersion) - r.Get("/{templateversionname}", a.templateVersionByName) + r.Get("/", api.templateVersionsByTemplate) + r.Patch("/", api.patchActiveTemplateVersion) + r.Get("/{templateversionname}", api.templateVersionByName) }) }) r.Route("/templateversions/{templateversion}", func(r chi.Router) { @@ -206,23 +194,23 @@ func newRouter(options *Options, a *api) chi.Router { httpmw.ExtractTemplateVersionParam(options.Database), ) - r.Get("/", a.templateVersion) - r.Patch("/cancel", a.patchCancelTemplateVersion) - r.Get("/schema", a.templateVersionSchema) - r.Get("/parameters", a.templateVersionParameters) - r.Get("/resources", a.templateVersionResources) - r.Get("/logs", a.templateVersionLogs) + r.Get("/", api.templateVersion) + r.Patch("/cancel", api.patchCancelTemplateVersion) + r.Get("/schema", api.templateVersionSchema) + r.Get("/parameters", api.templateVersionParameters) + r.Get("/resources", api.templateVersionResources) + r.Get("/logs", api.templateVersionLogs) }) r.Route("/users", func(r chi.Router) { - r.Get("/first", a.firstUser) - r.Post("/first", a.postFirstUser) - r.Post("/login", a.postLogin) - r.Post("/logout", a.postLogout) - r.Get("/authmethods", a.userAuthMethods) + r.Get("/first", api.firstUser) + r.Post("/first", api.postFirstUser) + r.Post("/login", api.postLogin) + r.Post("/logout", api.postLogout) + r.Get("/authmethods", api.userAuthMethods) r.Route("/oauth2", func(r chi.Router) { r.Route("/github", func(r chi.Router) { r.Use(httpmw.ExtractOAuth2(options.GithubOAuth2Config)) - r.Get("/callback", a.userOAuth2Github) + r.Get("/callback", api.userOAuth2Github) }) }) r.Group(func(r chi.Router) { @@ -230,62 +218,62 @@ func newRouter(options *Options, a *api) chi.Router { apiKeyMiddleware, authRolesMiddleware, ) - r.Post("/", a.postUser) - r.Get("/", a.users) + r.Post("/", api.postUser) + r.Get("/", api.users) // These routes query information about site wide roles. r.Route("/roles", func(r chi.Router) { - r.Get("/", a.assignableSiteRoles) + r.Get("/", api.assignableSiteRoles) }) r.Route("/{user}", func(r chi.Router) { r.Use(httpmw.ExtractUserParam(options.Database)) - r.Get("/", a.userByName) - r.Put("/profile", a.putUserProfile) + r.Get("/", api.userByName) + r.Put("/profile", api.putUserProfile) r.Route("/status", func(r chi.Router) { - r.Put("/suspend", a.putUserStatus(database.UserStatusSuspended)) - r.Put("/active", a.putUserStatus(database.UserStatusActive)) + r.Put("/suspend", api.putUserStatus(database.UserStatusSuspended)) + r.Put("/active", api.putUserStatus(database.UserStatusActive)) }) r.Route("/password", func(r chi.Router) { - r.Put("/", a.putUserPassword) + r.Put("/", api.putUserPassword) }) // These roles apply to the site wide permissions. - r.Put("/roles", a.putUserRoles) - r.Get("/roles", a.userRoles) + r.Put("/roles", api.putUserRoles) + r.Get("/roles", api.userRoles) - r.Post("/authorization", a.checkPermissions) + r.Post("/authorization", api.checkPermissions) - r.Post("/keys", a.postAPIKey) + r.Post("/keys", api.postAPIKey) r.Route("/organizations", func(r chi.Router) { - r.Post("/", a.postOrganizationsByUser) - r.Get("/", a.organizationsByUser) - r.Get("/{organizationname}", a.organizationByUserAndName) + r.Post("/", api.postOrganizationsByUser) + r.Get("/", api.organizationsByUser) + r.Get("/{organizationname}", api.organizationByUserAndName) }) - r.Get("/gitsshkey", a.gitSSHKey) - r.Put("/gitsshkey", a.regenerateGitSSHKey) + r.Get("/gitsshkey", api.gitSSHKey) + r.Put("/gitsshkey", api.regenerateGitSSHKey) }) }) }) r.Route("/workspaceagents", func(r chi.Router) { - r.Post("/azure-instance-identity", a.postWorkspaceAuthAzureInstanceIdentity) - r.Post("/aws-instance-identity", a.postWorkspaceAuthAWSInstanceIdentity) - r.Post("/google-instance-identity", a.postWorkspaceAuthGoogleInstanceIdentity) + r.Post("/azure-instance-identity", api.postWorkspaceAuthAzureInstanceIdentity) + r.Post("/aws-instance-identity", api.postWorkspaceAuthAWSInstanceIdentity) + r.Post("/google-instance-identity", api.postWorkspaceAuthGoogleInstanceIdentity) r.Route("/me", func(r chi.Router) { r.Use(httpmw.ExtractWorkspaceAgent(options.Database)) - r.Get("/metadata", a.workspaceAgentMetadata) - r.Get("/listen", a.workspaceAgentListen) - r.Get("/gitsshkey", a.agentGitSSHKey) - r.Get("/turn", a.workspaceAgentTurn) - r.Get("/iceservers", a.workspaceAgentICEServers) + r.Get("/metadata", api.workspaceAgentMetadata) + r.Get("/listen", api.workspaceAgentListen) + r.Get("/gitsshkey", api.agentGitSSHKey) + r.Get("/turn", api.workspaceAgentTurn) + r.Get("/iceservers", api.workspaceAgentICEServers) }) r.Route("/{workspaceagent}", func(r chi.Router) { r.Use( apiKeyMiddleware, httpmw.ExtractWorkspaceAgentParam(options.Database), ) - r.Get("/", a.workspaceAgent) - r.Get("/dial", a.workspaceAgentDial) - r.Get("/turn", a.workspaceAgentTurn) - r.Get("/pty", a.workspaceAgentPTY) - r.Get("/iceservers", a.workspaceAgentICEServers) + r.Get("/", api.workspaceAgent) + r.Get("/dial", api.workspaceAgentDial) + r.Get("/turn", api.workspaceAgentTurn) + r.Get("/pty", api.workspaceAgentPTY) + r.Get("/iceservers", api.workspaceAgentICEServers) }) }) r.Route("/workspaceresources/{workspaceresource}", func(r chi.Router) { @@ -295,31 +283,31 @@ func newRouter(options *Options, a *api) chi.Router { httpmw.ExtractWorkspaceResourceParam(options.Database), httpmw.ExtractWorkspaceParam(options.Database), ) - r.Get("/", a.workspaceResource) + r.Get("/", api.workspaceResource) }) r.Route("/workspaces", func(r chi.Router) { r.Use( apiKeyMiddleware, authRolesMiddleware, ) - r.Get("/", a.workspaces) + r.Get("/", api.workspaces) r.Route("/{workspace}", func(r chi.Router) { r.Use( httpmw.ExtractWorkspaceParam(options.Database), ) - r.Get("/", a.workspace) + r.Get("/", api.workspace) r.Route("/builds", func(r chi.Router) { - r.Get("/", a.workspaceBuilds) - r.Post("/", a.postWorkspaceBuilds) - r.Get("/{workspacebuildname}", a.workspaceBuildByName) + r.Get("/", api.workspaceBuilds) + r.Post("/", api.postWorkspaceBuilds) + r.Get("/{workspacebuildname}", api.workspaceBuildByName) }) r.Route("/autostart", func(r chi.Router) { - r.Put("/", a.putWorkspaceAutostart) + r.Put("/", api.putWorkspaceAutostart) }) r.Route("/ttl", func(r chi.Router) { - r.Put("/", a.putWorkspaceTTL) + r.Put("/", api.putWorkspaceTTL) }) - r.Get("/watch", a.watchWorkspace) + r.Get("/watch", api.watchWorkspace) }) }) r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) { @@ -329,48 +317,39 @@ func newRouter(options *Options, a *api) chi.Router { httpmw.ExtractWorkspaceBuildParam(options.Database), httpmw.ExtractWorkspaceParam(options.Database), ) - r.Get("/", a.workspaceBuild) - r.Patch("/cancel", a.patchCancelWorkspaceBuild) - r.Get("/logs", a.workspaceBuildLogs) - r.Get("/resources", a.workspaceBuildResources) - r.Get("/state", a.workspaceBuildState) + r.Get("/", api.workspaceBuild) + r.Patch("/cancel", api.patchCancelWorkspaceBuild) + r.Get("/logs", api.workspaceBuildLogs) + r.Get("/resources", api.workspaceBuildResources) + r.Get("/state", api.workspaceBuildState) }) }) - - var _ = xerrors.New("test") - r.NotFound(site.DefaultHandler().ServeHTTP) - return r -} - -func New(options *Options) CoderD { - a := &api{Options: options} - return &coderD{ - api: a, - router: newRouter(options, a), - options: options, - } -} - -func (c *coderD) CloseWait() { - c.api.websocketWaitMutex.Lock() - c.api.websocketWaitGroup.Wait() - c.api.websocketWaitMutex.Unlock() -} -func (c *coderD) Handler() http.Handler { - return c.router + return api } -// API contains all route handlers. Only HTTP handlers should -// be added to this struct for code clarity. -type api struct { +type API struct { *Options + router chi.Router websocketWaitMutex sync.Mutex websocketWaitGroup sync.WaitGroup } +// ServeHTTP fulfills the http.Handler interface to allow for the API +// to be mounted without exposing the router. +func (api *API) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + api.router.ServeHTTP(rw, r) +} + +// Close waits for all WebSocket connections to drain before returning. +func (api *API) Close() { + api.websocketWaitMutex.Lock() + api.websocketWaitGroup.Wait() + api.websocketWaitMutex.Unlock() +} + func debugLogRequest(log slog.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 7ee5a2bc4dc9d..244c3640a9f28 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -78,7 +78,7 @@ func New(t *testing.T, options *Options) *codersdk.Client { // NewWithServer returns an in-memory coderd instance and // the HTTP server it started with. -func NewWithServer(t *testing.T, options *Options) (*httptest.Server, *codersdk.Client, coderd.CoderD) { +func NewWithServer(t *testing.T, options *Options) (*httptest.Server, *codersdk.Client, *coderd.API) { if options == nil { options = &Options{} } @@ -160,7 +160,7 @@ func NewWithServer(t *testing.T, options *Options) (*httptest.Server, *codersdk. APIRateLimit: options.APIRateLimit, Authorizer: options.Authorizer, }) - srv.Config.Handler = coderDaemon.Handler() + srv.Config.Handler = coderDaemon if options.IncludeProvisionerD { _ = NewProvisionerDaemon(t, coderDaemon) } @@ -168,7 +168,7 @@ func NewWithServer(t *testing.T, options *Options) (*httptest.Server, *codersdk. cancelFunc() _ = turnServer.Close() srv.Close() - coderDaemon.CloseWait() + coderDaemon.Close() }) return srv, codersdk.New(serverURL), coderDaemon @@ -177,7 +177,7 @@ func NewWithServer(t *testing.T, options *Options) (*httptest.Server, *codersdk. // NewProvisionerDaemon launches a provisionerd instance configured to work // well with coderd testing. It registers the "echo" provisioner for // quick testing. -func NewProvisionerDaemon(t *testing.T, coderDaemon coderd.CoderD) io.Closer { +func NewProvisionerDaemon(t *testing.T, coderDaemon *coderd.API) io.Closer { echoClient, echoServer := provisionersdk.TransportPipe() ctx, cancelFunc := context.WithCancel(context.Background()) t.Cleanup(func() { diff --git a/coderd/files.go b/coderd/files.go index 74b4b56f26f02..5e63889496ae8 100644 --- a/coderd/files.go +++ b/coderd/files.go @@ -18,7 +18,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) postFile(rw http.ResponseWriter, r *http.Request) { +func (api *API) postFile(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) // This requires the site wide action to create files. // Once created, a user can read their own files uploaded @@ -74,7 +74,7 @@ func (api *api) postFile(rw http.ResponseWriter, r *http.Request) { }) } -func (api *api) fileByHash(rw http.ResponseWriter, r *http.Request) { +func (api *API) fileByHash(rw http.ResponseWriter, r *http.Request) { hash := chi.URLParam(r, "hash") if hash == "" { httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ diff --git a/coderd/gitsshkey.go b/coderd/gitsshkey.go index d5b2b049f892c..a80144fee2683 100644 --- a/coderd/gitsshkey.go +++ b/coderd/gitsshkey.go @@ -12,7 +12,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) { +func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) if !api.Authorize(rw, r, rbac.ActionUpdate, rbac.ResourceUserData.WithOwner(user.ID.String())) { @@ -57,7 +57,7 @@ func (api *api) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) { }) } -func (api *api) gitSSHKey(rw http.ResponseWriter, r *http.Request) { +func (api *API) gitSSHKey(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceUserData.WithOwner(user.ID.String())) { @@ -81,7 +81,7 @@ func (api *api) gitSSHKey(rw http.ResponseWriter, r *http.Request) { }) } -func (api *api) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) { +func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) { agent := httpmw.WorkspaceAgent(r) resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), agent.ResourceID) if err != nil { diff --git a/coderd/members.go b/coderd/members.go index 4c716c537af50..78f44294ae87f 100644 --- a/coderd/members.go +++ b/coderd/members.go @@ -16,7 +16,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) putMemberRoles(rw http.ResponseWriter, r *http.Request) { +func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) organization := httpmw.OrganizationParam(r) member := httpmw.OrganizationMemberParam(r) @@ -55,7 +55,7 @@ func (api *api) putMemberRoles(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertOrganizationMember(updatedUser)) } -func (api *api) updateOrganizationMemberRoles(ctx context.Context, args database.UpdateMemberRolesParams) (database.OrganizationMember, error) { +func (api *API) updateOrganizationMemberRoles(ctx context.Context, args database.UpdateMemberRolesParams) (database.OrganizationMember, error) { // Enforce only site wide roles for _, r := range args.GrantedRoles { // Must be an org role for the org in the args diff --git a/coderd/organizations.go b/coderd/organizations.go index b0b57f748ccd6..a5e3a958817dc 100644 --- a/coderd/organizations.go +++ b/coderd/organizations.go @@ -10,7 +10,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) organization(rw http.ResponseWriter, r *http.Request) { +func (api *API) organization(rw http.ResponseWriter, r *http.Request) { organization := httpmw.OrganizationParam(r) if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceOrganization. diff --git a/coderd/parameters.go b/coderd/parameters.go index 0f8afcbb2da60..9e719798083f2 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -16,7 +16,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) postParameter(rw http.ResponseWriter, r *http.Request) { +func (api *API) postParameter(rw http.ResponseWriter, r *http.Request) { var createRequest codersdk.CreateParameterRequest if !httpapi.Read(rw, r, &createRequest) { return @@ -63,7 +63,7 @@ func (api *api) postParameter(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusCreated, convertParameterValue(parameterValue)) } -func (api *api) parameters(rw http.ResponseWriter, r *http.Request) { +func (api *API) parameters(rw http.ResponseWriter, r *http.Request) { scope, scopeID, valid := readScopeAndID(rw, r) if !valid { return @@ -89,7 +89,7 @@ func (api *api) parameters(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, apiParameterValues) } -func (api *api) deleteParameter(rw http.ResponseWriter, r *http.Request) { +func (api *API) deleteParameter(rw http.ResponseWriter, r *http.Request) { scope, scopeID, valid := readScopeAndID(rw, r) if !valid { return diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index bb3fa21361bdd..590f3e56264f4 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -30,7 +30,7 @@ import ( sdkproto "github.com/coder/coder/provisionersdk/proto" ) -func (api *api) provisionerDaemonsByOrganization(rw http.ResponseWriter, r *http.Request) { +func (api *API) provisionerDaemonsByOrganization(rw http.ResponseWriter, r *http.Request) { daemons, err := api.Database.GetProvisionerDaemons(r.Context()) if errors.Is(err, sql.ErrNoRows) { err = nil @@ -49,7 +49,7 @@ func (api *api) provisionerDaemonsByOrganization(rw http.ResponseWriter, r *http // ListenProvisionerDaemon is an in-memory connection to a provisionerd. Useful when starting coderd and provisionerd // in the same process. -func (c *coderD) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPCProvisionerDaemonClient, err error) { +func (api *API) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPCProvisionerDaemonClient, err error) { clientSession, serverSession := provisionersdk.TransportPipe() defer func() { if err != nil { @@ -58,7 +58,7 @@ func (c *coderD) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPC } }() - daemon, err := c.api.Database.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{ + daemon, err := api.Database.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{ ID: uuid.New(), CreatedAt: database.Now(), Name: namesgenerator.GetRandomName(1), @@ -70,12 +70,12 @@ func (c *coderD) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPC mux := drpcmux.New() err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdServer{ - AccessURL: c.options.AccessURL, + AccessURL: api.AccessURL, ID: daemon.ID, - Database: c.options.Database, - Pubsub: c.options.Pubsub, + Database: api.Database, + Pubsub: api.Pubsub, Provisioners: daemon.Provisioners, - Logger: c.options.Logger.Named(fmt.Sprintf("provisionerd-%s", daemon.Name)), + Logger: api.Logger.Named(fmt.Sprintf("provisionerd-%s", daemon.Name)), }) if err != nil { return nil, err @@ -85,13 +85,13 @@ func (c *coderD) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPC if xerrors.Is(err, io.EOF) { return } - c.options.Logger.Debug(ctx, "drpc server error", slog.Error(err)) + api.Logger.Debug(ctx, "drpc server error", slog.Error(err)) }, }) go func() { err = server.Serve(ctx, serverSession) if err != nil && !xerrors.Is(err, io.EOF) { - c.options.Logger.Debug(ctx, "provisioner daemon disconnected", slog.Error(err)) + api.Logger.Debug(ctx, "provisioner daemon disconnected", slog.Error(err)) } // close the sessions so we don't leak goroutines serving them. _ = clientSession.Close() diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 8c8ded3620f2f..a47cc15541994 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -26,7 +26,7 @@ import ( // 2. GET /logs?after=&follow // The combination of these responses should provide all current logs // to the consumer, and future logs are streamed in the follow request. -func (api *api) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job database.ProvisionerJob) { +func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job database.ProvisionerJob) { follow := r.URL.Query().Has("follow") afterRaw := r.URL.Query().Get("after") beforeRaw := r.URL.Query().Get("before") @@ -178,7 +178,7 @@ func (api *api) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job } } -func (api *api) provisionerJobResources(rw http.ResponseWriter, r *http.Request, job database.ProvisionerJob) { +func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, job database.ProvisionerJob) { if !job.CompletedAt.Valid { httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ Message: "Job hasn't completed!", diff --git a/coderd/roles.go b/coderd/roles.go index 58d7ea96c6d14..3843124fbc45d 100644 --- a/coderd/roles.go +++ b/coderd/roles.go @@ -11,7 +11,7 @@ import ( ) // assignableSiteRoles returns all site wide roles that can be assigned. -func (api *api) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) { +func (api *API) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) { // TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the // role of the user. @@ -24,7 +24,7 @@ func (api *api) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) { } // assignableSiteRoles returns all site wide roles that can be assigned. -func (api *api) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { +func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { // TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the // role of the user. organization := httpmw.OrganizationParam(r) @@ -37,7 +37,7 @@ func (api *api) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertRoles(roles)) } -func (api *api) checkPermissions(rw http.ResponseWriter, r *http.Request) { +func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceUser.WithOwner(user.ID.String())) { diff --git a/coderd/templates.go b/coderd/templates.go index 44850f817f623..185836e293335 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -18,7 +18,7 @@ import ( ) // Returns a single template. -func (api *api) template(rw http.ResponseWriter, r *http.Request) { +func (api *API) template(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID}) @@ -44,7 +44,7 @@ func (api *api) template(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertTemplate(template, count)) } -func (api *api) deleteTemplate(rw http.ResponseWriter, r *http.Request) { +func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) if !api.Authorize(rw, r, rbac.ActionDelete, template) { return @@ -84,7 +84,7 @@ func (api *api) deleteTemplate(rw http.ResponseWriter, r *http.Request) { } // Create a new template in an organization. -func (api *api) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) { +func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) { var createTemplate codersdk.CreateTemplateRequest organization := httpmw.OrganizationParam(r) if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(organization.ID)) { @@ -193,7 +193,7 @@ func (api *api) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque httpapi.Write(rw, http.StatusCreated, template) } -func (api *api) templatesByOrganization(rw http.ResponseWriter, r *http.Request) { +func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request) { organization := httpmw.OrganizationParam(r) templates, err := api.Database.GetTemplatesByOrganization(r.Context(), database.GetTemplatesByOrganizationParams{ OrganizationID: organization.ID, @@ -230,7 +230,7 @@ func (api *api) templatesByOrganization(rw http.ResponseWriter, r *http.Request) httpapi.Write(rw, http.StatusOK, convertTemplates(templates, workspaceCounts)) } -func (api *api) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) { organization := httpmw.OrganizationParam(r) templateName := chi.URLParam(r, "templatename") template, err := api.Database.GetTemplateByOrganizationAndName(r.Context(), database.GetTemplateByOrganizationAndNameParams{ diff --git a/coderd/templateversions.go b/coderd/templateversions.go index bf8438d32238b..0c1c1d3f7de83 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -19,7 +19,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) templateVersion(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { return @@ -36,7 +36,7 @@ func (api *api) templateVersion(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job))) } -func (api *api) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) { +func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) { templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(rw, r, rbac.ActionUpdate, templateVersion) { return @@ -79,7 +79,7 @@ func (api *api) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Reque }) } -func (api *api) templateVersionSchema(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersionSchema(rw http.ResponseWriter, r *http.Request) { templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { return @@ -122,7 +122,7 @@ func (api *api) templateVersionSchema(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, apiSchemas) } -func (api *api) templateVersionParameters(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersionParameters(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { @@ -163,7 +163,7 @@ func (api *api) templateVersionParameters(rw http.ResponseWriter, r *http.Reques httpapi.Write(rw, http.StatusOK, values) } -func (api *api) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) if !api.Authorize(rw, r, rbac.ActionRead, template) { return @@ -221,7 +221,7 @@ func (api *api) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque httpapi.Write(rw, http.StatusOK, apiVersion) } -func (api *api) templateVersionByName(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) if !api.Authorize(rw, r, rbac.ActionRead, template) { return @@ -258,7 +258,7 @@ func (api *api) templateVersionByName(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job))) } -func (api *api) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) { +func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) if !api.Authorize(rw, r, rbac.ActionUpdate, template) { return @@ -303,7 +303,7 @@ func (api *api) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque } // Creates a new version of a template. An import job is queued to parse the storage method provided. -func (api *api) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *http.Request) { +func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) organization := httpmw.OrganizationParam(r) var req codersdk.CreateTemplateVersionRequest @@ -415,7 +415,7 @@ func (api *api) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht // provisioned, each resource can have an agent that dials back to coderd. // The agents returned are informative of the template version, and do not // return agents associated with any particular workspace. -func (api *api) templateVersionResources(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request) { templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { return @@ -435,7 +435,7 @@ func (api *api) templateVersionResources(rw http.ResponseWriter, r *http.Request // template version. These logs are only associated with the template version, // and not any build logs for a workspace. // Eg: Logs returned from 'terraform plan' when uploading a new terraform file. -func (api *api) templateVersionLogs(rw http.ResponseWriter, r *http.Request) { +func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) { templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { return diff --git a/coderd/userauth.go b/coderd/userauth.go index e2b52329b37a7..5838f933e2571 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -28,14 +28,14 @@ type GithubOAuth2Config struct { AllowOrganizations []string } -func (api *api) userAuthMethods(rw http.ResponseWriter, _ *http.Request) { +func (api *API) userAuthMethods(rw http.ResponseWriter, _ *http.Request) { httpapi.Write(rw, http.StatusOK, codersdk.AuthMethods{ Password: true, Github: api.GithubOAuth2Config != nil, }) } -func (api *api) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { +func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { state := httpmw.OAuth2(r) oauthClient := oauth2.NewClient(r.Context(), oauth2.StaticTokenSource(state.Token)) diff --git a/coderd/users.go b/coderd/users.go index 0e0ce9bda22b8..8e927a9466d7b 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -25,7 +25,7 @@ import ( ) // Returns whether the initial user has been created or not. -func (api *api) firstUser(rw http.ResponseWriter, r *http.Request) { +func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) { userCount, err := api.Database.GetUserCount(r.Context()) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ @@ -47,7 +47,7 @@ func (api *api) firstUser(rw http.ResponseWriter, r *http.Request) { } // Creates the initial user for a Coder deployment. -func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { +func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { var createUser codersdk.CreateFirstUserRequest if !httpapi.Read(rw, r, &createUser) { return @@ -103,7 +103,7 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { }) } -func (api *api) users(rw http.ResponseWriter, r *http.Request) { +func (api *API) users(rw http.ResponseWriter, r *http.Request) { var ( searchName = r.URL.Query().Get("search") statusFilter = r.URL.Query().Get("status") @@ -161,7 +161,7 @@ func (api *api) users(rw http.ResponseWriter, r *http.Request) { } // Creates a new user. -func (api *api) postUser(rw http.ResponseWriter, r *http.Request) { +func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { // Create the user on the site if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceUser) { return @@ -224,7 +224,7 @@ func (api *api) postUser(rw http.ResponseWriter, r *http.Request) { // Returns the parameterized user requested. All validation // is completed in the middleware for this route. -func (api *api) userByName(rw http.ResponseWriter, r *http.Request) { +func (api *API) userByName(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) organizationIDs, err := userOrganizationIDs(r.Context(), api, user) @@ -242,7 +242,7 @@ func (api *api) userByName(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertUser(user, organizationIDs)) } -func (api *api) putUserProfile(rw http.ResponseWriter, r *http.Request) { +func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) if !api.Authorize(rw, r, rbac.ActionUpdate, rbac.ResourceUser.WithID(user.ID.String())) { @@ -311,7 +311,7 @@ func (api *api) putUserProfile(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs)) } -func (api *api) putUserStatus(status database.UserStatus) func(rw http.ResponseWriter, r *http.Request) { +func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseWriter, r *http.Request) { return func(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) apiKey := httpmw.APIKey(r) @@ -352,7 +352,7 @@ func (api *api) putUserStatus(status database.UserStatus) func(rw http.ResponseW } } -func (api *api) putUserPassword(rw http.ResponseWriter, r *http.Request) { +func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) { var ( user = httpmw.UserParam(r) params codersdk.UpdateUserPasswordRequest @@ -387,7 +387,7 @@ func (api *api) putUserPassword(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusNoContent, nil) } -func (api *api) userRoles(rw http.ResponseWriter, r *http.Request) { +func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceUserData. @@ -421,7 +421,7 @@ func (api *api) userRoles(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, resp) } -func (api *api) putUserRoles(rw http.ResponseWriter, r *http.Request) { +func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) { // User is the user to modify user := httpmw.UserParam(r) roles := httpmw.UserRoles(r) @@ -469,7 +469,7 @@ func (api *api) putUserRoles(rw http.ResponseWriter, r *http.Request) { // updateSiteUserRoles will ensure only site wide roles are passed in as arguments. // If an organization role is included, an error is returned. -func (api *api) updateSiteUserRoles(ctx context.Context, args database.UpdateUserRolesParams) (database.User, error) { +func (api *API) updateSiteUserRoles(ctx context.Context, args database.UpdateUserRolesParams) (database.User, error) { // Enforce only site wide roles for _, r := range args.GrantedRoles { if _, ok := rbac.IsOrgRole(r); ok { @@ -489,7 +489,7 @@ func (api *api) updateSiteUserRoles(ctx context.Context, args database.UpdateUse } // Returns organizations the parameterized user has access to. -func (api *api) organizationsByUser(rw http.ResponseWriter, r *http.Request) { +func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) organizations, err := api.Database.GetOrganizationsByUserID(r.Context(), user.ID) @@ -515,7 +515,7 @@ func (api *api) organizationsByUser(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, publicOrganizations) } -func (api *api) organizationByUserAndName(rw http.ResponseWriter, r *http.Request) { +func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Request) { organizationName := chi.URLParam(r, "organizationname") organization, err := api.Database.GetOrganizationByName(r.Context(), organizationName) if errors.Is(err, sql.ErrNoRows) { @@ -539,7 +539,7 @@ func (api *api) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques httpapi.Write(rw, http.StatusOK, convertOrganization(organization)) } -func (api *api) postOrganizationsByUser(rw http.ResponseWriter, r *http.Request) { +func (api *API) postOrganizationsByUser(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) var req codersdk.CreateOrganizationRequest if !httpapi.Read(rw, r, &req) { @@ -605,7 +605,7 @@ func (api *api) postOrganizationsByUser(rw http.ResponseWriter, r *http.Request) } // Authenticates the user with an email and password. -func (api *api) postLogin(rw http.ResponseWriter, r *http.Request) { +func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { var loginWithPassword codersdk.LoginWithPasswordRequest if !httpapi.Read(rw, r, &loginWithPassword) { return @@ -651,7 +651,7 @@ func (api *api) postLogin(rw http.ResponseWriter, r *http.Request) { } // Creates a new session key, used for logging in via the CLI -func (api *api) postAPIKey(rw http.ResponseWriter, r *http.Request) { +func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { user := httpmw.UserParam(r) if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceAPIKey.WithOwner(user.ID.String())) { @@ -670,7 +670,7 @@ func (api *api) postAPIKey(rw http.ResponseWriter, r *http.Request) { } // Clear the user's session cookie -func (*api) postLogout(rw http.ResponseWriter, _ *http.Request) { +func (*API) postLogout(rw http.ResponseWriter, _ *http.Request) { // Get a blank token cookie cookie := &http.Cookie{ // MaxAge < 0 means to delete the cookie now @@ -700,7 +700,7 @@ func generateAPIKeyIDSecret() (id string, secret string, err error) { return id, secret, nil } -func (api *api) createAPIKey(rw http.ResponseWriter, r *http.Request, params database.InsertAPIKeyParams) (string, bool) { +func (api *API) createAPIKey(rw http.ResponseWriter, r *http.Request, params database.InsertAPIKeyParams) (string, bool) { keyID, keySecret, err := generateAPIKeyIDSecret() if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ @@ -743,7 +743,7 @@ func (api *api) createAPIKey(rw http.ResponseWriter, r *http.Request, params dat return sessionToken, true } -func (api *api) createUser(ctx context.Context, req codersdk.CreateUserRequest) (database.User, uuid.UUID, error) { +func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest) (database.User, uuid.UUID, error) { var user database.User return user, req.OrganizationID, api.Database.InTx(func(db database.Store) error { var orgRoles []string @@ -845,7 +845,7 @@ func convertUsers(users []database.User, organizationIDsByUserID map[uuid.UUID][ return converted } -func userOrganizationIDs(ctx context.Context, api *api, user database.User) ([]uuid.UUID, error) { +func userOrganizationIDs(ctx context.Context, api *API, user database.User) ([]uuid.UUID, error) { organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{user.ID}) if errors.Is(err, sql.ErrNoRows) || len(organizationIDsByMemberIDsRows) == 0 { return []uuid.UUID{}, nil diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index ec26001b99ff1..bbda257eb01d5 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -28,7 +28,7 @@ import ( "github.com/coder/coder/provisionersdk" ) -func (api *api) workspaceAgent(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgentParam(r) apiAgent, err := convertWorkspaceAgent(workspaceAgent, api.AgentConnectionUpdateFrequency) if err != nil { @@ -41,7 +41,7 @@ func (api *api) workspaceAgent(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, apiAgent) } -func (api *api) workspaceAgentDial(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgentDial(rw http.ResponseWriter, r *http.Request) { api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() @@ -90,7 +90,7 @@ func (api *api) workspaceAgentDial(rw http.ResponseWriter, r *http.Request) { } } -func (api *api) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { workspaceAgent := httpmw.WorkspaceAgent(r) apiAgent, err := convertWorkspaceAgent(workspaceAgent, api.AgentConnectionUpdateFrequency) if err != nil { @@ -136,7 +136,7 @@ func (api *api) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) }) } -func (api *api) workspaceAgentListen(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgentListen(rw http.ResponseWriter, r *http.Request) { api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() @@ -269,12 +269,12 @@ func (api *api) workspaceAgentListen(rw http.ResponseWriter, r *http.Request) { } } -func (api *api) workspaceAgentICEServers(rw http.ResponseWriter, _ *http.Request) { +func (api *API) workspaceAgentICEServers(rw http.ResponseWriter, _ *http.Request) { httpapi.Write(rw, http.StatusOK, api.ICEServers) } // workspaceAgentTurn proxies a WebSocket connection to the TURN server. -func (api *api) workspaceAgentTurn(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgentTurn(rw http.ResponseWriter, r *http.Request) { api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() @@ -324,7 +324,7 @@ func (api *api) workspaceAgentTurn(rw http.ResponseWriter, r *http.Request) { // workspaceAgentPTY spawns a PTY and pipes it over a WebSocket. // This is used for the web terminal. -func (api *api) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() @@ -395,7 +395,7 @@ func (api *api) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { } // dialWorkspaceAgent connects to a workspace agent by ID. -func (api *api) dialWorkspaceAgent(r *http.Request, agentID uuid.UUID) (*agent.Conn, error) { +func (api *API) dialWorkspaceAgent(r *http.Request, agentID uuid.UUID) (*agent.Conn, error) { client, server := provisionersdk.TransportPipe() go func() { _ = peerbroker.ProxyListen(r.Context(), server, peerbroker.ProxyOptions{ diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go new file mode 100644 index 0000000000000..0c2aa8f342bf7 --- /dev/null +++ b/coderd/workspaceapps.go @@ -0,0 +1,9 @@ +package coderd + +import ( + "net/http" +) + +func (api *API) proxyPath(rw http.ResponseWriter, r *http.Request) { + +} diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index d142ea4221c8c..b07f38b7f068e 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -19,7 +19,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) workspaceBuild(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) if err != nil { @@ -45,7 +45,7 @@ func (api *api) workspaceBuild(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(job))) } -func (api *api) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { workspace := httpmw.WorkspaceParam(r) if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceWorkspace. @@ -107,7 +107,7 @@ func (api *api) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, apiBuilds) } -func (api *api) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) { workspace := httpmw.WorkspaceParam(r) if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceWorkspace. InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) { @@ -142,7 +142,7 @@ func (api *api) workspaceBuildByName(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(job))) } -func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) workspace := httpmw.WorkspaceParam(r) var createBuild codersdk.CreateWorkspaceBuildRequest @@ -310,7 +310,7 @@ func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusCreated, convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(provisionerJob))) } -func (api *api) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) { +func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) if err != nil { @@ -362,7 +362,7 @@ func (api *api) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques }) } -func (api *api) workspaceBuildResources(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceBuildResources(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) if err != nil { @@ -387,7 +387,7 @@ func (api *api) workspaceBuildResources(rw http.ResponseWriter, r *http.Request) api.provisionerJobResources(rw, r, job) } -func (api *api) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) if err != nil { @@ -412,7 +412,7 @@ func (api *api) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) { api.provisionerJobLogs(rw, r, job) } -func (api *api) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) if err != nil { diff --git a/coderd/workspaceresourceauth.go b/coderd/workspaceresourceauth.go index 17a713b651be5..e403271e01645 100644 --- a/coderd/workspaceresourceauth.go +++ b/coderd/workspaceresourceauth.go @@ -18,7 +18,7 @@ import ( // Azure supports instance identity verification: // https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux#tabgroup_14 -func (api *api) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r *http.Request) { var req codersdk.AzureInstanceIdentityToken if !httpapi.Read(rw, r, &req) { return @@ -36,7 +36,7 @@ func (api *api) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r // AWS supports instance identity verification: // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html // Using this, we can exchange a signed instance payload for an agent token. -func (api *api) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r *http.Request) { var req codersdk.AWSInstanceIdentityToken if !httpapi.Read(rw, r, &req) { return @@ -54,7 +54,7 @@ func (api *api) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r * // Google Compute Engine supports instance identity verification: // https://cloud.google.com/compute/docs/instances/verifying-instance-identity // Using this, we can exchange a signed instance payload for an agent token. -func (api *api) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, r *http.Request) { var req codersdk.GoogleInstanceIdentityToken if !httpapi.Read(rw, r, &req) { return @@ -85,7 +85,7 @@ func (api *api) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, api.handleAuthInstanceID(rw, r, claims.Google.ComputeEngine.InstanceID) } -func (api *api) handleAuthInstanceID(rw http.ResponseWriter, r *http.Request, instanceID string) { +func (api *API) handleAuthInstanceID(rw http.ResponseWriter, r *http.Request, instanceID string) { agent, err := api.Database.GetWorkspaceAgentByInstanceID(r.Context(), instanceID) if errors.Is(err, sql.ErrNoRows) { httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ diff --git a/coderd/workspaceresources.go b/coderd/workspaceresources.go index 1bdf1f83e94b9..a08861971e60e 100644 --- a/coderd/workspaceresources.go +++ b/coderd/workspaceresources.go @@ -14,7 +14,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) workspaceResource(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspaceResource := httpmw.WorkspaceResourceParam(r) workspace := httpmw.WorkspaceParam(r) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index a30534a03e24d..340697a436baa 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -28,7 +28,7 @@ import ( "github.com/coder/coder/codersdk" ) -func (api *api) workspace(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { workspace := httpmw.WorkspaceParam(r) if !api.Authorize(rw, r, rbac.ActionRead, workspace) { return @@ -100,7 +100,7 @@ func (api *api) workspace(rw http.ResponseWriter, r *http.Request) { convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template, owner)) } -func (api *api) workspacesByOrganization(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspacesByOrganization(rw http.ResponseWriter, r *http.Request) { organization := httpmw.OrganizationParam(r) workspaces, err := api.Database.GetWorkspacesWithFilter(r.Context(), database.GetWorkspacesWithFilterParams{ OrganizationID: organization.ID, @@ -131,7 +131,7 @@ func (api *api) workspacesByOrganization(rw http.ResponseWriter, r *http.Request // workspaces returns all workspaces a user can read. // Optional filters with query params -func (api *api) workspaces(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) // Empty strings mean no filter @@ -192,7 +192,7 @@ func (api *api) workspaces(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, apiWorkspaces) } -func (api *api) workspacesByOwner(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspacesByOwner(rw http.ResponseWriter, r *http.Request) { owner := httpmw.UserParam(r) workspaces, err := api.Database.GetWorkspacesWithFilter(r.Context(), database.GetWorkspacesWithFilterParams{ OwnerID: owner.ID, @@ -221,7 +221,7 @@ func (api *api) workspacesByOwner(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, apiWorkspaces) } -func (api *api) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) { +func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) { owner := httpmw.UserParam(r) organization := httpmw.OrganizationParam(r) workspaceName := chi.URLParam(r, "workspacename") @@ -280,7 +280,7 @@ func (api *api) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) } // Create a new workspace for the currently authenticated user. -func (api *api) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Request) { var createWorkspace codersdk.CreateWorkspaceRequest if !httpapi.Read(rw, r, &createWorkspace) { return @@ -502,7 +502,7 @@ func (api *api) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req convertWorkspaceBuild(workspaceBuild, convertProvisionerJob(templateVersionJob)), template, user)) } -func (api *api) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { +func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { workspace := httpmw.WorkspaceParam(r) if !api.Authorize(rw, r, rbac.ActionUpdate, rbac.ResourceWorkspace. InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) { @@ -539,7 +539,7 @@ func (api *api) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { } } -func (api *api) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { +func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { workspace := httpmw.WorkspaceParam(r) if !api.Authorize(rw, r, rbac.ActionUpdate, rbac.ResourceWorkspace. InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) { @@ -570,7 +570,7 @@ func (api *api) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { } } -func (api *api) watchWorkspace(rw http.ResponseWriter, r *http.Request) { +func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { workspace := httpmw.WorkspaceParam(r) c, err := websocket.Accept(rw, r, &websocket.AcceptOptions{