mirror of https://github.com/coder/coder.git
feat: Add workspace application support (#1773)
* 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 <name>`. 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. * Compare fields in apps test * Update Terraform provider to use relative path * Add some basic structure for routing * 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`. * Add basic proxy logic * Add proxying based on path * Add app proxying for wildcards * Add wsconncache * fix: Race when writing to a closed pipe This is such an intermittent race it's difficult to track, but regardless this is an improvement to the code. * fix: Race when writing to a closed pipe This is such an intermittent race it's difficult to track, but regardless this is an improvement to the code. * fix: Race when writing to a closed pipe This is such an intermittent race it's difficult to track, but regardless this is an improvement to the code. * fix: Race when writing to a closed pipe This is such an intermittent race it's difficult to track, but regardless this is an improvement to the code. * Add workspace route proxying endpoint - Makes the workspace conn cache concurrency-safe - Reduces unnecessary open checks in `peer.Channel` - Fixes the use of a temporary context when dialing a workspace agent * Add embed errors * chore: Refactor site to improve testing It was difficult to develop this package due to the embed build tag being mandatory on the tests. The logic to test doesn't require any embedded files. * Add test for error handler * Remove unused access url * Add RBAC tests * Fix dial agent syntax * Fix linting errors * Fix gen * Fix icon required * Adjust migration number * Fix proxy error status code * Fix empty db lookup
This commit is contained in:
parent
2c089d5a99
commit
013f028e55
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"apps",
|
||||
"awsidentity",
|
||||
"buildinfo",
|
||||
"buildname",
|
||||
"circbuf",
|
||||
"cliflag",
|
||||
|
@ -16,6 +19,7 @@
|
|||
"Dsts",
|
||||
"fatih",
|
||||
"Formik",
|
||||
"gitsshkey",
|
||||
"goarch",
|
||||
"gographviz",
|
||||
"goleak",
|
||||
|
@ -30,6 +34,7 @@
|
|||
"incpatch",
|
||||
"isatty",
|
||||
"Jobf",
|
||||
"Keygen",
|
||||
"kirsle",
|
||||
"ldflags",
|
||||
"manifoldco",
|
||||
|
@ -54,6 +59,7 @@
|
|||
"retrier",
|
||||
"rpty",
|
||||
"sdkproto",
|
||||
"sdktrace",
|
||||
"Signup",
|
||||
"sourcemapped",
|
||||
"stretchr",
|
||||
|
@ -66,13 +72,18 @@
|
|||
"tfjson",
|
||||
"tfstate",
|
||||
"trimprefix",
|
||||
"turnconn",
|
||||
"typegen",
|
||||
"unconvert",
|
||||
"Untar",
|
||||
"VMID",
|
||||
"weblinks",
|
||||
"webrtc",
|
||||
"workspaceagent",
|
||||
"workspaceapp",
|
||||
"workspaceapps",
|
||||
"workspacebuilds",
|
||||
"wsconncache",
|
||||
"xerrors",
|
||||
"xstate",
|
||||
"yamux"
|
||||
|
|
|
@ -102,7 +102,7 @@ func (c *Conn) DialContext(ctx context.Context, network string, addr string) (ne
|
|||
var res dialResponse
|
||||
err = dec.Decode(&res)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to decode initial packet: %w", err)
|
||||
return nil, xerrors.Errorf("decode agent dial response: %w", err)
|
||||
}
|
||||
if res.Error != "" {
|
||||
_ = channel.Close()
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/tracing"
|
||||
"github.com/coder/coder/coderd/turnconn"
|
||||
"github.com/coder/coder/coderd/wsconncache"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/site"
|
||||
)
|
||||
|
@ -44,6 +45,7 @@ type Options struct {
|
|||
// app. Specific routes may have their own limiters.
|
||||
APIRateLimit int
|
||||
AWSCertificates awsidentity.Certificates
|
||||
Authorizer rbac.Authorizer
|
||||
AzureCertificates x509.VerifyOptions
|
||||
GoogleTokenValidator *idtoken.Validator
|
||||
GithubOAuth2Config *GithubOAuth2Config
|
||||
|
@ -51,7 +53,6 @@ type Options struct {
|
|||
SecureAuthCookie bool
|
||||
SSHKeygenAlgorithm gitsshkey.Algorithm
|
||||
TURNServer *turnconn.Server
|
||||
Authorizer rbac.Authorizer
|
||||
TracerProvider *sdktrace.TracerProvider
|
||||
}
|
||||
|
||||
|
@ -75,9 +76,11 @@ func New(options *Options) *API {
|
|||
|
||||
r := chi.NewRouter()
|
||||
api := &API{
|
||||
Options: options,
|
||||
Handler: r,
|
||||
Options: options,
|
||||
Handler: r,
|
||||
siteHandler: site.Handler(site.FS()),
|
||||
}
|
||||
api.workspaceAgentCache = wsconncache.New(api.dialWorkspaceAgent, 0)
|
||||
|
||||
apiKeyMiddleware := httpmw.ExtractAPIKey(options.Database, &httpmw.OAuth2Configs{
|
||||
Github: options.GithubOAuth2Config,
|
||||
|
@ -93,6 +96,20 @@ func New(options *Options) *API {
|
|||
tracing.HTTPMW(api.TracerProvider, "coderd.http"),
|
||||
)
|
||||
|
||||
apps := func(r chi.Router) {
|
||||
r.Use(
|
||||
httpmw.RateLimitPerMinute(options.APIRateLimit),
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractUserParam(api.Database),
|
||||
)
|
||||
r.Get("/*", api.workspaceAppsProxyPath)
|
||||
}
|
||||
// %40 is the encoded character of the @ symbol. VS Code Web does
|
||||
// not handle character encoding properly, so it's safe to assume
|
||||
// other applications might not as well.
|
||||
r.Route("/%40{user}/{workspacename}/apps/{workspaceapp}", apps)
|
||||
r.Route("/@{user}/{workspacename}/apps/{workspaceapp}", apps)
|
||||
|
||||
r.Route("/api/v2", func(r chi.Router) {
|
||||
r.NotFound(func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
|
||||
|
@ -327,24 +344,27 @@ func New(options *Options) *API {
|
|||
r.Get("/state", api.workspaceBuildState)
|
||||
})
|
||||
})
|
||||
r.NotFound(site.Handler(site.FS()).ServeHTTP)
|
||||
|
||||
r.NotFound(api.siteHandler.ServeHTTP)
|
||||
return api
|
||||
}
|
||||
|
||||
type API struct {
|
||||
*Options
|
||||
|
||||
Handler chi.Router
|
||||
websocketWaitMutex sync.Mutex
|
||||
websocketWaitGroup sync.WaitGroup
|
||||
Handler chi.Router
|
||||
siteHandler http.Handler
|
||||
websocketWaitMutex sync.Mutex
|
||||
websocketWaitGroup sync.WaitGroup
|
||||
workspaceAgentCache *wsconncache.Cache
|
||||
}
|
||||
|
||||
// Close waits for all WebSocket connections to drain before returning.
|
||||
func (api *API) Close() {
|
||||
func (api *API) Close() error {
|
||||
api.websocketWaitMutex.Lock()
|
||||
api.websocketWaitGroup.Wait()
|
||||
api.websocketWaitMutex.Unlock()
|
||||
|
||||
return api.workspaceAgentCache.Close()
|
||||
}
|
||||
|
||||
func debugLogRequest(log slog.Logger) func(http.Handler) http.Handler {
|
||||
|
|
|
@ -74,6 +74,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
|||
Agents: []*proto.Agent{{
|
||||
Id: "something",
|
||||
Auth: &proto.Agent_Token{},
|
||||
Apps: []*proto.App{{
|
||||
Name: "app",
|
||||
Url: "http://localhost:3000",
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
|
@ -128,6 +132,15 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
|||
"GET:/api/v2/users/authmethods": {NoAuthorize: true},
|
||||
"POST:/api/v2/csp/reports": {NoAuthorize: true},
|
||||
|
||||
"GET:/%40{user}/{workspacename}/apps/{application}/*": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/@{user}/{workspacename}/apps/{application}/*": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
|
||||
// Has it's own auth
|
||||
"GET:/api/v2/users/oauth2/github/callback": {NoAuthorize: true},
|
||||
|
||||
|
@ -368,6 +381,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
|||
route = strings.ReplaceAll(route, "{template}", template.ID.String())
|
||||
route = strings.ReplaceAll(route, "{hash}", file.Hash)
|
||||
route = strings.ReplaceAll(route, "{workspaceresource}", workspaceResources[0].ID.String())
|
||||
route = strings.ReplaceAll(route, "{workspaceapp}", workspaceResources[0].Agents[0].Apps[0].Name)
|
||||
route = strings.ReplaceAll(route, "{templateversion}", version.ID.String())
|
||||
route = strings.ReplaceAll(route, "{templateversiondryrun}", templateVersionDryRun.ID.String())
|
||||
route = strings.ReplaceAll(route, "{templatename}", template.Name)
|
||||
|
|
|
@ -173,7 +173,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, *coderd.API)
|
|||
cancelFunc()
|
||||
_ = turnServer.Close()
|
||||
srv.Close()
|
||||
coderAPI.Close()
|
||||
_ = coderAPI.Close()
|
||||
})
|
||||
|
||||
return codersdk.New(serverURL), coderAPI
|
||||
|
|
|
@ -35,6 +35,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),
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +64,7 @@ type fakeQuerier struct {
|
|||
templateVersions []database.TemplateVersion
|
||||
templates []database.Template
|
||||
workspaceBuilds []database.WorkspaceBuild
|
||||
workspaceApps []database.WorkspaceApp
|
||||
workspaces []database.Workspace
|
||||
}
|
||||
|
||||
|
@ -388,6 +390,38 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspacesAutostart(_ context.Context) ([]database.Workspace, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
@ -1031,6 +1065,22 @@ func (q *fakeQuerier) GetWorkspaceAgentsByResourceIDs(_ context.Context, resourc
|
|||
return workspaceAgents, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceAppByAgentIDAndName(_ context.Context, arg database.GetWorkspaceAppByAgentIDAndNameParams) (database.WorkspaceApp, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, app := range q.workspaceApps {
|
||||
if app.AgentID != arg.AgentID {
|
||||
continue
|
||||
}
|
||||
if app.Name != arg.Name {
|
||||
continue
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
return database.WorkspaceApp{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetProvisionerDaemonByID(_ context.Context, id uuid.UUID) (database.ProvisionerDaemon, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
@ -1521,6 +1571,25 @@ 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()
|
||||
|
||||
// nolint:gosimple
|
||||
workspaceApp := database.WorkspaceApp{
|
||||
ID: arg.ID,
|
||||
AgentID: arg.AgentID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Name: arg.Name,
|
||||
Icon: arg.Icon,
|
||||
Command: arg.Command,
|
||||
Url: arg.Url,
|
||||
RelativePath: arg.RelativePath,
|
||||
}
|
||||
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()
|
||||
|
|
|
@ -280,6 +280,17 @@ 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),
|
||||
url character varying(65534),
|
||||
relative_path boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_builds (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
@ -382,6 +393,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);
|
||||
|
||||
|
@ -463,6 +480,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;
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE workspace_apps;
|
|
@ -0,0 +1,12 @@
|
|||
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,
|
||||
command varchar(65534),
|
||||
url varchar(65534),
|
||||
relative_path boolean NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE(agent_id, name)
|
||||
);
|
|
@ -493,6 +493,17 @@ 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"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
RelativePath bool `db:"relative_path" json:"relative_path"`
|
||||
}
|
||||
|
||||
type WorkspaceBuild struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
|
|
|
@ -64,6 +64,9 @@ 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)
|
||||
GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg GetWorkspaceAppByAgentIDAndNameParams) (WorkspaceApp, 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, arg GetWorkspaceBuildByWorkspaceIDParams) ([]WorkspaceBuild, error)
|
||||
|
@ -93,6 +96,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
|
||||
|
|
|
@ -2785,6 +2785,155 @@ func (q *sqlQuerier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg
|
|||
return err
|
||||
}
|
||||
|
||||
const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1 AND name = $2
|
||||
`
|
||||
|
||||
type GetWorkspaceAppByAgentIDAndNameParams struct {
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg GetWorkspaceAppByAgentIDAndNameParams) (WorkspaceApp, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceAppByAgentIDAndName, arg.AgentID, arg.Name)
|
||||
var i WorkspaceApp
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1
|
||||
`
|
||||
|
||||
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.Url,
|
||||
&i.RelativePath,
|
||||
); 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, url, relative_path 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.Url,
|
||||
&i.RelativePath,
|
||||
); 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,
|
||||
url,
|
||||
relative_path
|
||||
)
|
||||
VALUES
|
||||
($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"`
|
||||
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) {
|
||||
row := q.db.QueryRowContext(ctx, insertWorkspaceApp,
|
||||
arg.ID,
|
||||
arg.CreatedAt,
|
||||
arg.AgentID,
|
||||
arg.Name,
|
||||
arg.Icon,
|
||||
arg.Command,
|
||||
arg.Url,
|
||||
arg.RelativePath,
|
||||
)
|
||||
var i WorkspaceApp
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, name, build_number, transition, initiator_id, provisioner_state, job_id, deadline
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
-- 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: GetWorkspaceAppByAgentIDAndName :one
|
||||
SELECT * FROM workspace_apps WHERE agent_id = $1 AND name = $2;
|
||||
|
||||
-- name: InsertWorkspaceApp :one
|
||||
INSERT INTO
|
||||
workspace_apps (
|
||||
id,
|
||||
created_at,
|
||||
agent_id,
|
||||
name,
|
||||
icon,
|
||||
command,
|
||||
url,
|
||||
relative_path
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;
|
|
@ -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{}
|
||||
|
|
|
@ -724,7 +724,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(),
|
||||
|
@ -744,6 +744,28 @@ 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 != "",
|
||||
},
|
||||
Url: sql.NullString{
|
||||
String: app.Url,
|
||||
Valid: app.Url != "",
|
||||
},
|
||||
RelativePath: app.RelativePath,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert app: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -220,6 +220,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 {
|
||||
|
@ -228,7 +242,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: "Internal error reading job agent",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -113,7 +113,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?
|
||||
}
|
||||
|
|
|
@ -31,7 +31,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: "Internal error reading workspace agent",
|
||||
|
@ -50,7 +57,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: "Internal error reading workspace agent",
|
||||
|
@ -97,7 +104,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: "Internal error reading workspace agent",
|
||||
|
@ -358,7 +365,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: "Internal error reading workspace agent",
|
||||
|
@ -403,16 +410,16 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx, wsNetConn := websocketNetConn(r.Context(), conn, websocket.MessageBinary)
|
||||
_, wsNetConn := websocketNetConn(r.Context(), conn, websocket.MessageBinary)
|
||||
defer wsNetConn.Close() // Also closes conn.
|
||||
|
||||
agentConn, err := api.dialWorkspaceAgent(ctx, r, workspaceAgent.ID)
|
||||
agentConn, release, err := api.workspaceAgentCache.Acquire(r, workspaceAgent.ID)
|
||||
if err != nil {
|
||||
_ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("dial workspace agent: %s", err))
|
||||
return
|
||||
}
|
||||
defer agentConn.Close()
|
||||
ptNetConn, err := agentConn.ReconnectingPTY(reconnect.String(), uint16(height), uint16(width), "")
|
||||
defer release()
|
||||
ptNetConn, err := agentConn.ReconnectingPTY(reconnect.String(), uint16(height), uint16(width), r.URL.Query().Get("command"))
|
||||
if err != nil {
|
||||
_ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("dial: %s", err))
|
||||
return
|
||||
|
@ -428,8 +435,9 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
|
|||
// dialWorkspaceAgent connects to a workspace agent by ID. Only rely on
|
||||
// r.Context() for cancellation if it's use is safe or r.Hijack() has
|
||||
// not been performed.
|
||||
func (api *API) dialWorkspaceAgent(ctx context.Context, 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()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
_ = peerbroker.ProxyListen(ctx, server, peerbroker.ProxyOptions{
|
||||
ChannelID: agentID.String(),
|
||||
|
@ -443,9 +451,12 @@ func (api *API) dialWorkspaceAgent(ctx context.Context, r *http.Request, agentID
|
|||
peerClient := proto.NewDRPCPeerBrokerClient(provisionersdk.Conn(client))
|
||||
stream, err := peerClient.NegotiateConnection(ctx)
|
||||
if err != nil {
|
||||
cancelFunc()
|
||||
return nil, xerrors.Errorf("negotiate: %w", err)
|
||||
}
|
||||
options := &peer.ConnOptions{}
|
||||
options := &peer.ConnOptions{
|
||||
Logger: api.Logger.Named("agent-dialer"),
|
||||
}
|
||||
options.SettingEngine.SetSrflxAcceptanceMinWait(0)
|
||||
options.SettingEngine.SetRelayAcceptanceMinWait(0)
|
||||
// Use the ProxyDialer for the TURN server.
|
||||
|
@ -476,15 +487,33 @@ func (api *API) dialWorkspaceAgent(ctx context.Context, r *http.Request, agentID
|
|||
}))
|
||||
peerConn, err := peerbroker.Dial(stream, append(api.ICEServers, turnconn.Proxy), options)
|
||||
if err != nil {
|
||||
cancelFunc()
|
||||
return nil, xerrors.Errorf("dial: %w", err)
|
||||
}
|
||||
go func() {
|
||||
<-peerConn.Closed()
|
||||
cancelFunc()
|
||||
}()
|
||||
return &agent.Conn{
|
||||
Negotiator: peerClient,
|
||||
Conn: peerConn,
|
||||
}, 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,
|
||||
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)
|
||||
|
@ -504,6 +533,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
|
||||
|
|
|
@ -297,7 +297,7 @@ func TestWorkspaceAgentPTY(t *testing.T) {
|
|||
})
|
||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
conn, err := client.WorkspaceAgentReconnectingPTY(context.Background(), resources[0].Agents[0].ID, uuid.New(), 80, 80)
|
||||
conn, err := client.WorkspaceAgentReconnectingPTY(context.Background(), resources[0].Agents[0].ID, uuid.New(), 80, 80, "/bin/bash")
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
package coderd
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"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/coderd/rbac"
|
||||
"github.com/coder/coder/site"
|
||||
)
|
||||
|
||||
// workspaceAppsProxyPath proxies requests to a workspace application
|
||||
// through a relative URL path.
|
||||
func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) {
|
||||
user := httpmw.UserParam(r)
|
||||
// This can be in the form of: "<workspace-name>.[workspace-agent]" or "<workspace-name>"
|
||||
workspaceWithAgent := chi.URLParam(r, "workspacename")
|
||||
workspaceParts := strings.Split(workspaceWithAgent, ".")
|
||||
|
||||
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
|
||||
OwnerID: user.ID,
|
||||
Name: workspaceParts[0],
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
|
||||
Message: "workspace not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspace: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !api.Authorize(rw, r, rbac.ActionRead, workspace) {
|
||||
return
|
||||
}
|
||||
|
||||
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspace build: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resources, err := api.Database.GetWorkspaceResourcesByJobID(r.Context(), build.JobID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspace resources: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
resourceIDs := make([]uuid.UUID, 0)
|
||||
for _, resource := range resources {
|
||||
resourceIDs = append(resourceIDs, resource.ID)
|
||||
}
|
||||
agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(r.Context(), resourceIDs)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspace agents: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(agents) == 0 {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: "no agents exist",
|
||||
})
|
||||
}
|
||||
|
||||
agent := agents[0]
|
||||
if len(workspaceParts) > 1 {
|
||||
for _, otherAgent := range agents {
|
||||
if otherAgent.Name == workspaceParts[1] {
|
||||
agent = otherAgent
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
|
||||
AgentID: agent.ID,
|
||||
Name: chi.URLParam(r, "workspaceapp"),
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
|
||||
Message: "application not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspace app: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !app.Url.Valid {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: fmt.Sprintf("application does not have a url: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
appURL, err := url.Parse(app.Url.String)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("parse app url: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(appURL)
|
||||
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
// This is a browser-facing route so JSON responses are not viable here.
|
||||
// To pass friendly errors to the frontend, special meta tags are overridden
|
||||
// in the index.html with the content passed here.
|
||||
r = r.WithContext(site.WithAPIResponse(r.Context(), site.APIResponse{
|
||||
StatusCode: http.StatusBadGateway,
|
||||
Message: err.Error(),
|
||||
}))
|
||||
api.siteHandler.ServeHTTP(w, r)
|
||||
}
|
||||
path := chi.URLParam(r, "*")
|
||||
if !strings.HasSuffix(r.URL.Path, "/") && path == "" {
|
||||
// Web applications typically request paths relative to the
|
||||
// root URL. This allows for routing behind a proxy or subpath.
|
||||
// See https://github.com/coder/code-server/issues/241 for examples.
|
||||
r.URL.Path += "/"
|
||||
http.Redirect(rw, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
if r.URL.RawQuery == "" && appURL.RawQuery != "" {
|
||||
// If the application defines a default set of query parameters,
|
||||
// we should always respect them. The reverse proxy will merge
|
||||
// query parameters for server-side requests, but sometimes
|
||||
// client-side applications require the query parameters to render
|
||||
// properly. With code-server, this is the "folder" param.
|
||||
r.URL.RawQuery = appURL.RawQuery
|
||||
http.Redirect(rw, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
r.URL.Path = path
|
||||
|
||||
conn, release, err := api.workspaceAgentCache.Acquire(r, agent.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("dial workspace agent: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer release()
|
||||
|
||||
proxy.Transport = conn.HTTPTransport()
|
||||
proxy.ServeHTTP(rw, r)
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package coderd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func TestWorkspaceAppsProxyPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
// #nosec
|
||||
ln, err := net.Listen("tcp", ":0")
|
||||
require.NoError(t, err)
|
||||
server := http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = server.Close()
|
||||
_ = ln.Close()
|
||||
})
|
||||
go server.Serve(ln)
|
||||
tcpAddr, _ := ln.Addr().(*net.TCPAddr)
|
||||
|
||||
client, coderAPI := coderdtest.NewWithAPI(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, coderAPI)
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionDryRun: echo.ProvisionComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*proto.Agent{{
|
||||
Id: uuid.NewString(),
|
||||
Auth: &proto.Agent_Token{
|
||||
Token: authToken,
|
||||
},
|
||||
Apps: []*proto.App{{
|
||||
Name: "example",
|
||||
Url: fmt.Sprintf("http://127.0.0.1:%d?query=true", tcpAddr.Port),
|
||||
}, {
|
||||
Name: "fake",
|
||||
Url: "http://127.0.0.2",
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
agentClient := codersdk.New(client.URL)
|
||||
agentClient.SessionToken = authToken
|
||||
agentCloser := agent.New(agentClient.ListenWorkspaceAgent, &agent.Options{
|
||||
Logger: slogtest.Make(t, nil),
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
_ = agentCloser.Close()
|
||||
})
|
||||
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
t.Run("RedirectsWithSlash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := client.Request(context.Background(), http.MethodGet, "/@me/"+workspace.Name+"/apps/example", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("RedirectsWithQuery", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := client.Request(context.Background(), http.MethodGet, "/@me/"+workspace.Name+"/apps/example/", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
loc, err := resp.Location()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "query=true", loc.RawQuery)
|
||||
})
|
||||
|
||||
t.Run("Proxies", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := client.Request(context.Background(), http.MethodGet, "/@me/"+workspace.Name+"/apps/example/?query=true", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", string(body))
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("ProxyError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := client.Request(context.Background(), http.MethodGet, "/@me/"+workspace.Name+"/apps/fake/", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
}
|
|
@ -3,10 +3,12 @@ package coderd
|
|||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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/coderd/rbac"
|
||||
|
@ -46,9 +48,27 @@ 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 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: "Internal error reading workspace agent",
|
||||
|
|
|
@ -43,4 +43,50 @@ 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, coderd := coderdtest.NewWithAPI(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, coderd)
|
||||
app := &proto.App{
|
||||
Name: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
}
|
||||
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{app},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
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)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
// Package wsconncache caches workspace agent connections by UUID.
|
||||
package wsconncache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/sync/singleflight"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/agent"
|
||||
)
|
||||
|
||||
// New creates a new workspace connection cache that closes
|
||||
// connections after the inactive timeout provided.
|
||||
//
|
||||
// Agent connections are cached due to WebRTC negotiation
|
||||
// taking a few hundred milliseconds.
|
||||
func New(dialer Dialer, inactiveTimeout time.Duration) *Cache {
|
||||
if inactiveTimeout == 0 {
|
||||
inactiveTimeout = 5 * time.Minute
|
||||
}
|
||||
return &Cache{
|
||||
closed: make(chan struct{}),
|
||||
dialer: dialer,
|
||||
inactiveTimeout: inactiveTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Dialer creates a new agent connection by ID.
|
||||
type Dialer func(r *http.Request, id uuid.UUID) (*agent.Conn, error)
|
||||
|
||||
// Conn wraps an agent connection with a reusable HTTP transport.
|
||||
type Conn struct {
|
||||
*agent.Conn
|
||||
|
||||
locks atomic.Uint64
|
||||
timeoutMutex sync.Mutex
|
||||
timeout *time.Timer
|
||||
timeoutCancel context.CancelFunc
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
func (c *Conn) HTTPTransport() *http.Transport {
|
||||
return c.transport
|
||||
}
|
||||
|
||||
// CloseWithError ends the HTTP transport if exists, and closes the agent.
|
||||
func (c *Conn) CloseWithError(err error) error {
|
||||
if c.transport != nil {
|
||||
c.transport.CloseIdleConnections()
|
||||
}
|
||||
c.timeoutMutex.Lock()
|
||||
defer c.timeoutMutex.Unlock()
|
||||
if c.timeout != nil {
|
||||
c.timeout.Stop()
|
||||
}
|
||||
return c.Conn.CloseWithError(err)
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
closed chan struct{}
|
||||
closeMutex sync.Mutex
|
||||
closeGroup sync.WaitGroup
|
||||
connGroup singleflight.Group
|
||||
connMap sync.Map
|
||||
dialer Dialer
|
||||
inactiveTimeout time.Duration
|
||||
}
|
||||
|
||||
// Acquire gets or establishes a connection with the dialer using the ID provided.
|
||||
// If a connection is in-progress, that connection or error will be returned.
|
||||
//
|
||||
// The returned function is used to release a lock on the connection. Once zero
|
||||
// locks exist on a connection, the inactive timeout will begin to tick down.
|
||||
// After the time expires, the connection will be cleared from the cache.
|
||||
func (c *Cache) Acquire(r *http.Request, id uuid.UUID) (*Conn, func(), error) {
|
||||
rawConn, found := c.connMap.Load(id.String())
|
||||
// If the connection isn't found, establish a new one!
|
||||
if !found {
|
||||
var err error
|
||||
// A singleflight group is used to allow for concurrent requests to the
|
||||
// same identifier to resolve.
|
||||
rawConn, err, _ = c.connGroup.Do(id.String(), func() (interface{}, error) {
|
||||
agentConn, err := c.dialer(r, id)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("dial: %w", err)
|
||||
}
|
||||
timeoutCtx, timeoutCancelFunc := context.WithCancel(context.Background())
|
||||
defaultTransport, valid := http.DefaultTransport.(*http.Transport)
|
||||
if !valid {
|
||||
panic("dev error: default transport is the wrong type")
|
||||
}
|
||||
transport := defaultTransport.Clone()
|
||||
transport.DialContext = agentConn.DialContext
|
||||
conn := &Conn{
|
||||
Conn: agentConn,
|
||||
timeoutCancel: timeoutCancelFunc,
|
||||
transport: transport,
|
||||
}
|
||||
c.closeMutex.Lock()
|
||||
c.closeGroup.Add(1)
|
||||
c.closeMutex.Unlock()
|
||||
go func() {
|
||||
defer c.closeGroup.Done()
|
||||
var err error
|
||||
select {
|
||||
case <-timeoutCtx.Done():
|
||||
err = xerrors.New("cache timeout")
|
||||
case <-c.closed:
|
||||
err = xerrors.New("cache closed")
|
||||
case <-conn.Closed():
|
||||
}
|
||||
|
||||
c.connMap.Delete(id.String())
|
||||
c.connGroup.Forget(id.String())
|
||||
_ = conn.CloseWithError(err)
|
||||
}()
|
||||
return conn, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.connMap.Store(id.String(), rawConn)
|
||||
}
|
||||
|
||||
conn, _ := rawConn.(*Conn)
|
||||
conn.timeoutMutex.Lock()
|
||||
defer conn.timeoutMutex.Unlock()
|
||||
if conn.timeout != nil {
|
||||
conn.timeout.Stop()
|
||||
}
|
||||
conn.locks.Inc()
|
||||
return conn, func() {
|
||||
conn.timeoutMutex.Lock()
|
||||
defer conn.timeoutMutex.Unlock()
|
||||
if conn.timeout != nil {
|
||||
conn.timeout.Stop()
|
||||
}
|
||||
conn.locks.Dec()
|
||||
if conn.locks.Load() == 0 {
|
||||
conn.timeout = time.AfterFunc(c.inactiveTimeout, conn.timeoutCancel)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Close() error {
|
||||
c.closeMutex.Lock()
|
||||
defer c.closeMutex.Unlock()
|
||||
select {
|
||||
case <-c.closed:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
close(c.closed)
|
||||
c.closeGroup.Wait()
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
package wsconncache_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/coderd/wsconncache"
|
||||
"github.com/coder/coder/peer"
|
||||
"github.com/coder/coder/peerbroker"
|
||||
"github.com/coder/coder/peerbroker/proto"
|
||||
"github.com/coder/coder/provisionersdk"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Same", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
}, 0)
|
||||
t.Cleanup(func() {
|
||||
_ = cache.Close()
|
||||
})
|
||||
conn1, _, err := cache.Acquire(httptest.NewRequest(http.MethodGet, "/", nil), uuid.Nil)
|
||||
require.NoError(t, err)
|
||||
conn2, _, err := cache.Acquire(httptest.NewRequest(http.MethodGet, "/", nil), uuid.Nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, conn1 == conn2)
|
||||
})
|
||||
t.Run("Expire", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
called := atomic.NewInt32(0)
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
called.Add(1)
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
}, time.Microsecond)
|
||||
t.Cleanup(func() {
|
||||
_ = cache.Close()
|
||||
})
|
||||
conn, release, err := cache.Acquire(httptest.NewRequest(http.MethodGet, "/", nil), uuid.Nil)
|
||||
require.NoError(t, err)
|
||||
release()
|
||||
<-conn.Closed()
|
||||
conn, release, err = cache.Acquire(httptest.NewRequest(http.MethodGet, "/", nil), uuid.Nil)
|
||||
require.NoError(t, err)
|
||||
release()
|
||||
<-conn.Closed()
|
||||
require.Equal(t, int32(2), called.Load())
|
||||
})
|
||||
t.Run("NoExpireWhenLocked", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
}, time.Microsecond)
|
||||
t.Cleanup(func() {
|
||||
_ = cache.Close()
|
||||
})
|
||||
conn, release, err := cache.Acquire(httptest.NewRequest(http.MethodGet, "/", nil), uuid.Nil)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Millisecond)
|
||||
release()
|
||||
<-conn.Closed()
|
||||
})
|
||||
t.Run("HTTPTransport", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
random, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = random.Close()
|
||||
})
|
||||
tcpAddr, valid := random.Addr().(*net.TCPAddr)
|
||||
require.True(t, valid)
|
||||
|
||||
server := &http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}),
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = server.Close()
|
||||
})
|
||||
go server.Serve(random)
|
||||
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
}, time.Microsecond)
|
||||
t.Cleanup(func() {
|
||||
_ = cache.Close()
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
// Perform many requests in parallel to simulate
|
||||
// simultaneous HTTP requests.
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
||||
Scheme: "http",
|
||||
Host: fmt.Sprintf("127.0.0.1:%d", tcpAddr.Port),
|
||||
Path: "/",
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
conn, release, err := cache.Acquire(req, uuid.Nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
defer release()
|
||||
proxy.Transport = conn.HTTPTransport()
|
||||
res := httptest.NewRecorder()
|
||||
proxy.ServeHTTP(res, req)
|
||||
res.Result().Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.Result().StatusCode)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) *agent.Conn {
|
||||
client, server := provisionersdk.TransportPipe()
|
||||
closer := agent.New(func(ctx context.Context, logger slog.Logger) (agent.Metadata, *peerbroker.Listener, error) {
|
||||
listener, err := peerbroker.Listen(server, func(ctx context.Context) ([]webrtc.ICEServer, *peer.ConnOptions, error) {
|
||||
return nil, &peer.ConnOptions{
|
||||
Logger: slogtest.Make(t, nil).Named("server").Leveled(slog.LevelDebug),
|
||||
}, nil
|
||||
})
|
||||
return metadata, listener, err
|
||||
}, &agent.Options{
|
||||
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
|
||||
ReconnectingPTYTimeout: ptyTimeout,
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
_ = client.Close()
|
||||
_ = server.Close()
|
||||
_ = closer.Close()
|
||||
})
|
||||
api := proto.NewDRPCPeerBrokerClient(provisionersdk.Conn(client))
|
||||
stream, err := api.NegotiateConnection(context.Background())
|
||||
assert.NoError(t, err)
|
||||
conn, err := peerbroker.Dial(stream, []webrtc.ICEServer{}, &peer.ConnOptions{
|
||||
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = conn.Close()
|
||||
})
|
||||
|
||||
return &agent.Conn{
|
||||
Negotiator: api,
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
|
@ -341,8 +341,8 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge
|
|||
// WorkspaceAgentReconnectingPTY spawns a PTY that reconnects using the token provided.
|
||||
// It communicates using `agent.ReconnectingPTYRequest` marshaled as JSON.
|
||||
// Responses are PTY output that can be rendered.
|
||||
func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, agentID, reconnect uuid.UUID, height, width int) (net.Conn, error) {
|
||||
serverURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/pty?reconnect=%s&height=%d&width=%d", agentID, reconnect, height, width))
|
||||
func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, agentID, reconnect uuid.UUID, height, width int, command string) (net.Conn, error) {
|
||||
serverURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/pty?reconnect=%s&height=%d&width=%d&command=%s", agentID, reconnect, height, width, command))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse url: %w", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package codersdk
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type WorkspaceApp struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
// Name is a unique identifier attached to an agent.
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command,omitempty"`
|
||||
// Icon is a relative path or external URL that specifies
|
||||
// an icon to be displayed in the dashboard.
|
||||
Icon string `json:"icon,omitempty"`
|
||||
}
|
|
@ -44,6 +44,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 {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
name: Develop code-server in Docker
|
||||
description: Run code-server in a Docker development environment
|
||||
tags: [local, docker]
|
||||
---
|
||||
|
||||
# code-server in Docker
|
||||
|
||||
## Getting started
|
||||
|
||||
Run `coder templates init` and select this template. Follow the instructions that appear.
|
|
@ -0,0 +1,45 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.2"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
version = "~> 2.16.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "coder" {
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {
|
||||
}
|
||||
|
||||
resource "coder_agent" "dev" {
|
||||
arch = "amd64"
|
||||
os = "linux"
|
||||
startup_script = "code-server --auth none"
|
||||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.dev.id
|
||||
url = "http://localhost:8080/?folder=/home/coder"
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = "codercom/code-server:latest"
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}"
|
||||
hostname = lower(data.coder_workspace.me.name)
|
||||
dns = ["1.1.1.1"]
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
entrypoint = ["sh", "-c", replace(coder_agent.dev.init_script, "127.0.0.1", "host.docker.internal")]
|
||||
env = ["CODER_AGENT_TOKEN=${coder_agent.dev.token}"]
|
||||
host {
|
||||
host = "host.docker.internal"
|
||||
ip = "host-gateway"
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
@ -136,12 +135,12 @@ func (c *Channel) init() {
|
|||
// is triggerred with a buffer size less than the chunks written.
|
||||
//
|
||||
// This makes sense when considering UDP connections, because
|
||||
// bufferring of data that has no transmit guarantees is likely
|
||||
// buffering of data that has no transmit guarantees is likely
|
||||
// to cause unexpected behavior.
|
||||
//
|
||||
// When ordered, this adds a bufio.Reader. This ensures additional
|
||||
// data on TCP-like connections can be read in parts, while still
|
||||
// being bufferred.
|
||||
// being buffered.
|
||||
if c.opts.Unordered {
|
||||
c.reader = c.rwc
|
||||
} else {
|
||||
|
@ -193,11 +192,9 @@ func (c *Channel) Read(bytes []byte) (int, error) {
|
|||
if c.isClosed() {
|
||||
return 0, c.closeError
|
||||
}
|
||||
if !c.isOpened() {
|
||||
err := c.waitOpened()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err := c.waitOpened()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
bytesRead, err := c.reader.Read(bytes)
|
||||
|
@ -211,7 +208,6 @@ func (c *Channel) Read(bytes []byte) (int, error) {
|
|||
if xerrors.Is(err, io.EOF) {
|
||||
err = c.closeWithError(ErrClosed)
|
||||
}
|
||||
return bytesRead, err
|
||||
}
|
||||
return bytesRead, err
|
||||
}
|
||||
|
@ -235,24 +231,14 @@ func (c *Channel) Write(bytes []byte) (n int, err error) {
|
|||
if c.isClosed() {
|
||||
return 0, c.closeWithError(nil)
|
||||
}
|
||||
if !c.isOpened() {
|
||||
err := c.waitOpened()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = c.waitOpened()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if c.dc.BufferedAmount()+uint64(len(bytes)) >= maxBufferedAmount {
|
||||
<-c.sendMore
|
||||
}
|
||||
|
||||
// There's an obvious race-condition here. This is an edge-case, as
|
||||
// most-frequently data won't be pooled so synchronously, but is
|
||||
// definitely possible.
|
||||
//
|
||||
// See: https://github.com/pion/sctp/issues/181
|
||||
time.Sleep(time.Microsecond)
|
||||
|
||||
return c.rwc.Write(bytes)
|
||||
}
|
||||
|
||||
|
@ -319,15 +305,6 @@ func (c *Channel) isClosed() bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Channel) isOpened() bool {
|
||||
select {
|
||||
case <-c.opened:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Channel) waitOpened() error {
|
||||
select {
|
||||
case <-c.opened:
|
||||
|
|
|
@ -114,6 +114,7 @@ type Conn struct {
|
|||
closeMutex sync.Mutex
|
||||
closeError error
|
||||
|
||||
dcCreateMutex sync.Mutex
|
||||
dcOpenChannel chan *webrtc.DataChannel
|
||||
dcDisconnectChannel chan struct{}
|
||||
dcDisconnectListeners atomic.Uint32
|
||||
|
@ -483,6 +484,11 @@ func (c *Conn) CreateChannel(ctx context.Context, label string, opts *ChannelOpt
|
|||
}
|
||||
|
||||
func (c *Conn) dialChannel(ctx context.Context, label string, opts *ChannelOptions) (*Channel, error) {
|
||||
// pion/webrtc is slower when opening multiple channels
|
||||
// in parallel than it is sequentially.
|
||||
c.dcCreateMutex.Lock()
|
||||
defer c.dcCreateMutex.Unlock()
|
||||
|
||||
c.logger().Debug(ctx, "creating data channel", slog.F("label", label), slog.F("opts", opts))
|
||||
var id *uint16
|
||||
if opts.ID != 0 {
|
||||
|
|
|
@ -397,7 +397,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
|
||||
}
|
||||
resources = append(resources, &proto.Resource{
|
||||
|
@ -520,11 +520,47 @@ 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"`
|
||||
URL string `mapstructure:"url"`
|
||||
Command string `mapstructure:"command"`
|
||||
RelativePath bool `mapstructure:"relative_path"`
|
||||
}
|
||||
// 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,
|
||||
Url: attrs.URL,
|
||||
Icon: attrs.Icon,
|
||||
RelativePath: attrs.RelativePath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, resource := range tfResources {
|
||||
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
|
||||
}
|
||||
resourceAgents := findAgents(resourceDependencies, agents, convertAddressToLabel(resource.Address))
|
||||
|
|
|
@ -30,7 +30,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.3.4"
|
||||
version = "0.4.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -438,6 +438,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) {
|
||||
|
|
|
@ -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,97 @@ 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"`
|
||||
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() {
|
||||
*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) GetUrl() string {
|
||||
if x != nil {
|
||||
return x.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *App) GetIcon() string {
|
||||
if x != nil {
|
||||
return x.Icon
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *App) GetRelativePath() bool {
|
||||
if x != nil {
|
||||
return x.RelativePath
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Resource represents created infrastructure.
|
||||
type Resource struct {
|
||||
state protoimpl.MessageState
|
||||
|
@ -849,7 +937,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 +950,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 +963,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 +997,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 +1010,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 +1023,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 +1036,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 +1049,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 +1062,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 +1076,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 +1089,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 +1102,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 +1123,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 +1136,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 +1149,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 +1173,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 +1186,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 +1199,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 +1255,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 +1268,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 +1281,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 +1341,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 +1354,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 +1367,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 +1414,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 +1427,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 +1440,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 +1457,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 +1470,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 +1483,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 +1536,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 +1549,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 +1562,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 +1600,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 +1613,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 +1626,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 +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, 0xe9, 0x02, 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,
|
||||
|
@ -1675,114 +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, 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,
|
||||
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,
|
||||
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, 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,
|
||||
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,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -1798,7 +1897,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 +1912,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 +1935,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 +2063,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 +2075,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 +2087,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 +2110,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 +2122,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 +2134,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 +2146,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 +2158,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 +2170,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 +2182,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 +2194,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 +2206,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 +2223,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 +2241,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,
|
||||
},
|
||||
|
|
|
@ -80,12 +80,22 @@ 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 url = 3;
|
||||
string icon = 4;
|
||||
bool relative_path = 5;
|
||||
}
|
||||
|
||||
// Resource represents created infrastructure.
|
||||
message Resource {
|
||||
string name = 1;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<meta property="og:type" content="website" />
|
||||
<meta property="csp-nonce" content="{{ .CSP.Nonce }}" />
|
||||
<meta property="csrf-token" content="{{ .CSRF.Token }}" />
|
||||
<meta id="api-response" data-statuscode="{{ .APIResponse.StatusCode }}" data-message="{{ .APIResponse.Message }}" />
|
||||
<link rel="mask-icon" href="/static/favicon.svg" color="#000000" crossorigin="use-credentials" />
|
||||
<link rel="alternate icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
|
|
30
site/site.go
30
site/site.go
|
@ -2,6 +2,8 @@ package site
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
|
@ -17,6 +19,15 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type apiResponseContextKey struct{}
|
||||
|
||||
// WithAPIResponse returns a context with the APIResponse value attached.
|
||||
// This is used to inject API response data to the index.html for additional
|
||||
// metadata in error pages.
|
||||
func WithAPIResponse(ctx context.Context, apiResponse APIResponse) context.Context {
|
||||
return context.WithValue(ctx, apiResponseContextKey{}, apiResponse)
|
||||
}
|
||||
|
||||
// Handler returns an HTTP handler for serving the static site.
|
||||
func Handler(fileSystem fs.FS) http.Handler {
|
||||
// html files are handled by a text/template. Non-html files
|
||||
|
@ -66,8 +77,14 @@ func (h *handler) exists(filePath string) bool {
|
|||
}
|
||||
|
||||
type htmlState struct {
|
||||
CSP cspState
|
||||
CSRF csrfState
|
||||
APIResponse APIResponse
|
||||
CSP cspState
|
||||
CSRF csrfState
|
||||
}
|
||||
|
||||
type APIResponse struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
}
|
||||
|
||||
type cspState struct {
|
||||
|
@ -115,6 +132,15 @@ func (h *handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
|||
CSRF: csrfState{Token: nosurf.Token(req)},
|
||||
}
|
||||
|
||||
apiResponseRaw := req.Context().Value(apiResponseContextKey{})
|
||||
if apiResponseRaw != nil {
|
||||
apiResponse, ok := apiResponseRaw.(APIResponse)
|
||||
if !ok {
|
||||
panic("dev error: api response in context isn't the correct type")
|
||||
}
|
||||
state.APIResponse = apiResponse
|
||||
}
|
||||
|
||||
// First check if it's a file we have in our templates
|
||||
if h.serveHTML(resp, req, reqFile, state) {
|
||||
return
|
||||
|
|
|
@ -2,6 +2,7 @@ package site_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -169,3 +170,40 @@ func TestShouldCacheFile(t *testing.T) {
|
|||
require.Equal(t, testCase.expected, got, fmt.Sprintf("Expected ShouldCacheFile(%s) to be %t", testCase.reqFile, testCase.expected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeAPIResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test server
|
||||
rootFS := fstest.MapFS{
|
||||
"index.html": &fstest.MapFile{
|
||||
Data: []byte(`{"code":{{ .APIResponse.StatusCode }},"message":"{{ .APIResponse.Message }}"}`),
|
||||
},
|
||||
}
|
||||
|
||||
apiResponse := site.APIResponse{
|
||||
StatusCode: http.StatusBadGateway,
|
||||
Message: "This could be an error message!",
|
||||
}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(site.WithAPIResponse(r.Context(), apiResponse))
|
||||
site.Handler(rootFS).ServeHTTP(w, r)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), "GET", srv.URL, nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
var body struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
t.Logf("resp: %q", data)
|
||||
err = json.Unmarshal(data, &body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, apiResponse.StatusCode, body.Code)
|
||||
require.Equal(t, apiResponse.Message, body.Message)
|
||||
}
|
||||
|
|
|
@ -391,6 +391,7 @@ export interface WorkspaceAgent {
|
|||
readonly operating_system: string
|
||||
readonly startup_script?: string
|
||||
readonly directory?: string
|
||||
readonly apps: WorkspaceApp[]
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:47:6
|
||||
|
@ -398,7 +399,7 @@ export interface WorkspaceAgentAuthenticateResponse {
|
|||
readonly session_token: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:57:6
|
||||
// From codersdk/workspaceresources.go:58:6
|
||||
export interface WorkspaceAgentInstanceMetadata {
|
||||
readonly jail_orchestrator: string
|
||||
readonly operating_system: string
|
||||
|
@ -411,7 +412,7 @@ export interface WorkspaceAgentInstanceMetadata {
|
|||
readonly vnc: boolean
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:49:6
|
||||
// From codersdk/workspaceresources.go:50:6
|
||||
export interface WorkspaceAgentResourceMetadata {
|
||||
readonly memory_total: number
|
||||
readonly disk_total: number
|
||||
|
@ -420,6 +421,14 @@ export interface WorkspaceAgentResourceMetadata {
|
|||
readonly cpu_mhz: number
|
||||
}
|
||||
|
||||
// From codersdk/workspaceapps.go:7:6
|
||||
export interface WorkspaceApp {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly command?: string
|
||||
readonly icon?: string
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go:24:6
|
||||
export interface WorkspaceBuild {
|
||||
readonly id: string
|
||||
|
|
|
@ -210,7 +210,14 @@ export const MockDeletedWorkspace: TypesGen.Workspace = { ...MockWorkspace, late
|
|||
|
||||
export const MockOutdatedWorkspace: TypesGen.Workspace = { ...MockFailedWorkspace, outdated: true }
|
||||
|
||||
export const MockWorkspaceApp: TypesGen.WorkspaceApp = {
|
||||
id: "test-app",
|
||||
name: "test-app",
|
||||
icon: "",
|
||||
}
|
||||
|
||||
export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = {
|
||||
apps: [MockWorkspaceApp],
|
||||
architecture: "amd64",
|
||||
created_at: "",
|
||||
environment_variables: {},
|
||||
|
|
Loading…
Reference in New Issue