mirror of https://github.com/coder/coder.git
feat: add new required slug property to coder_app, use in URLs (#4573)
This commit is contained in:
parent
90f77a3415
commit
10df2fd4fb
|
@ -33,7 +33,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
|
|||
hasHealthchecksEnabled := false
|
||||
health := make(map[string]codersdk.WorkspaceAppHealth, 0)
|
||||
for _, app := range apps {
|
||||
health[app.Name] = app.Health
|
||||
health[app.DisplayName] = app.Health
|
||||
if !hasHealthchecksEnabled && app.Health != codersdk.WorkspaceAppHealthDisabled {
|
||||
hasHealthchecksEnabled = true
|
||||
}
|
||||
|
@ -85,21 +85,21 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
|
|||
}()
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
if failures[app.Name] < int(app.Healthcheck.Threshold) {
|
||||
if failures[app.DisplayName] < int(app.Healthcheck.Threshold) {
|
||||
// increment the failure count and keep status the same.
|
||||
// we will change it when we hit the threshold.
|
||||
failures[app.Name]++
|
||||
failures[app.DisplayName]++
|
||||
} else {
|
||||
// set to unhealthy if we hit the failure threshold.
|
||||
// we stop incrementing at the threshold to prevent the failure value from increasing forever.
|
||||
health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy
|
||||
health[app.DisplayName] = codersdk.WorkspaceAppHealthUnhealthy
|
||||
}
|
||||
mu.Unlock()
|
||||
} else {
|
||||
mu.Lock()
|
||||
// we only need one successful health check to be considered healthy.
|
||||
health[app.Name] = codersdk.WorkspaceAppHealthHealthy
|
||||
failures[app.Name] = 0
|
||||
health[app.DisplayName] = codersdk.WorkspaceAppHealthHealthy
|
||||
failures[app.DisplayName] = 0
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
|
|
|
@ -27,12 +27,12 @@ func TestAppHealth(t *testing.T) {
|
|||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app1",
|
||||
DisplayName: "app1",
|
||||
Healthcheck: codersdk.Healthcheck{},
|
||||
Health: codersdk.WorkspaceAppHealthDisabled,
|
||||
},
|
||||
{
|
||||
Name: "app2",
|
||||
DisplayName: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
|
@ -69,7 +69,7 @@ func TestAppHealth(t *testing.T) {
|
|||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app2",
|
||||
DisplayName: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
|
@ -102,7 +102,7 @@ func TestAppHealth(t *testing.T) {
|
|||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app2",
|
||||
DisplayName: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
|
@ -137,7 +137,7 @@ func TestAppHealth(t *testing.T) {
|
|||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app2",
|
||||
DisplayName: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
|
@ -187,7 +187,7 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa
|
|||
mu.Lock()
|
||||
for name, health := range req.Healths {
|
||||
for i, app := range apps {
|
||||
if app.Name != name {
|
||||
if app.DisplayName != name {
|
||||
continue
|
||||
}
|
||||
app.Health = health
|
||||
|
|
|
@ -51,7 +51,11 @@ func TestScheduleShow(t *testing.T) {
|
|||
lines := strings.Split(strings.TrimSpace(stdoutBuf.String()), "\n")
|
||||
if assert.Len(t, lines, 4) {
|
||||
assert.Contains(t, lines[0], "Starts at 7:30AM Mon-Fri (Europe/Dublin)")
|
||||
assert.Contains(t, lines[1], "Starts next 7:30AM IST on ")
|
||||
assert.Contains(t, lines[1], "Starts next 7:30AM")
|
||||
// it should have either IST or GMT
|
||||
if !strings.Contains(lines[1], "IST") && !strings.Contains(lines[1], "GMT") {
|
||||
t.Error("expected either IST or GMT")
|
||||
}
|
||||
assert.Contains(t, lines[2], "Stops at 8h after start")
|
||||
assert.NotContains(t, lines[3], "Stops next -")
|
||||
}
|
||||
|
@ -137,7 +141,11 @@ func TestScheduleStart(t *testing.T) {
|
|||
lines := strings.Split(strings.TrimSpace(stdoutBuf.String()), "\n")
|
||||
if assert.Len(t, lines, 4) {
|
||||
assert.Contains(t, lines[0], "Starts at 9:30AM Mon-Fri (Europe/Dublin)")
|
||||
assert.Contains(t, lines[1], "Starts next 9:30AM IST on")
|
||||
assert.Contains(t, lines[1], "Starts next 9:30AM")
|
||||
// it should have either IST or GMT
|
||||
if !strings.Contains(lines[1], "IST") && !strings.Contains(lines[1], "GMT") {
|
||||
t.Error("expected either IST or GMT")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure autostart schedule updated
|
||||
|
|
|
@ -331,8 +331,9 @@ func NewAuthTester(ctx context.Context, t *testing.T, client *codersdk.Client, a
|
|||
Id: "something",
|
||||
Auth: &proto.Agent_Token{},
|
||||
Apps: []*proto.App{{
|
||||
Name: "testapp",
|
||||
Url: "http://localhost:3000",
|
||||
Slug: "testapp",
|
||||
DisplayName: "testapp",
|
||||
Url: "http://localhost:3000",
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
|
@ -372,7 +373,7 @@ func NewAuthTester(ctx context.Context, t *testing.T, client *codersdk.Client, a
|
|||
"{template}": template.ID.String(),
|
||||
"{fileID}": file.ID.String(),
|
||||
"{workspaceresource}": workspace.LatestBuild.Resources[0].ID.String(),
|
||||
"{workspaceapp}": workspace.LatestBuild.Resources[0].Agents[0].Apps[0].Name,
|
||||
"{workspaceapp}": workspace.LatestBuild.Resources[0].Agents[0].Apps[0].Slug,
|
||||
"{templateversion}": version.ID.String(),
|
||||
"{jobID}": templateVersionDryRun.ID.String(),
|
||||
"{templatename}": template.Name,
|
||||
|
|
|
@ -1861,7 +1861,7 @@ func (q *fakeQuerier) GetWorkspaceAgentsCreatedAfter(_ context.Context, after ti
|
|||
return workspaceAgents, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceAppByAgentIDAndName(_ context.Context, arg database.GetWorkspaceAppByAgentIDAndNameParams) (database.WorkspaceApp, error) {
|
||||
func (q *fakeQuerier) GetWorkspaceAppByAgentIDAndSlug(_ context.Context, arg database.GetWorkspaceAppByAgentIDAndSlugParams) (database.WorkspaceApp, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
|
@ -1869,7 +1869,7 @@ func (q *fakeQuerier) GetWorkspaceAppByAgentIDAndName(_ context.Context, arg dat
|
|||
if app.AgentID != arg.AgentID {
|
||||
continue
|
||||
}
|
||||
if app.Name != arg.Name {
|
||||
if app.Slug != arg.Slug {
|
||||
continue
|
||||
}
|
||||
return app, nil
|
||||
|
@ -2522,7 +2522,8 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW
|
|||
ID: arg.ID,
|
||||
AgentID: arg.AgentID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Name: arg.Name,
|
||||
Slug: arg.Slug,
|
||||
DisplayName: arg.DisplayName,
|
||||
Icon: arg.Icon,
|
||||
Command: arg.Command,
|
||||
Url: arg.Url,
|
||||
|
|
|
@ -399,7 +399,7 @@ 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,
|
||||
display_name character varying(64) NOT NULL,
|
||||
icon character varying(256) NOT NULL,
|
||||
command character varying(65534),
|
||||
url character varying(65534),
|
||||
|
@ -408,7 +408,8 @@ CREATE TABLE workspace_apps (
|
|||
healthcheck_threshold integer DEFAULT 0 NOT NULL,
|
||||
health workspace_app_health DEFAULT 'disabled'::public.workspace_app_health NOT NULL,
|
||||
subdomain boolean DEFAULT false NOT NULL,
|
||||
sharing_level app_sharing_level DEFAULT 'owner'::public.app_sharing_level NOT NULL
|
||||
sharing_level app_sharing_level DEFAULT 'owner'::public.app_sharing_level NOT NULL,
|
||||
slug text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_builds (
|
||||
|
@ -548,7 +549,7 @@ 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);
|
||||
ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug);
|
||||
|
||||
ALTER TABLE ONLY workspace_apps
|
||||
ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
-- drop unique index on "slug" column
|
||||
ALTER TABLE "workspace_apps" DROP CONSTRAINT IF EXISTS "workspace_apps_agent_id_slug_idx";
|
||||
|
||||
-- drop "slug" column from "workspace_apps" table
|
||||
ALTER TABLE "workspace_apps" DROP COLUMN "slug";
|
|
@ -0,0 +1,16 @@
|
|||
BEGIN;
|
||||
|
||||
-- add "slug" column to "workspace_apps" table
|
||||
ALTER TABLE "workspace_apps" ADD COLUMN "slug" text DEFAULT '';
|
||||
|
||||
-- copy the "name" column for each workspace app to the "slug" column
|
||||
UPDATE "workspace_apps" SET "slug" = "name";
|
||||
|
||||
-- make "slug" column not nullable and remove default
|
||||
ALTER TABLE "workspace_apps" ALTER COLUMN "slug" SET NOT NULL;
|
||||
ALTER TABLE "workspace_apps" ALTER COLUMN "slug" DROP DEFAULT;
|
||||
|
||||
-- add unique index on "slug" column
|
||||
ALTER TABLE "workspace_apps" ADD CONSTRAINT "workspace_apps_agent_id_slug_idx" UNIQUE ("agent_id", "slug");
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,34 @@
|
|||
BEGIN;
|
||||
|
||||
-- Select all apps with an extra "row_number" column that determines the "rank"
|
||||
-- of the display name against other display names in the same agent.
|
||||
WITH row_numbers AS (
|
||||
SELECT
|
||||
*,
|
||||
row_number() OVER (PARTITION BY agent_id, display_name ORDER BY display_name ASC) AS row_number
|
||||
FROM
|
||||
workspace_apps
|
||||
)
|
||||
|
||||
-- Update any app with a "row_number" greater than 1 to have the row number
|
||||
-- appended to the display name. This effectively means that all lowercase
|
||||
-- display names remain untouched, while non-unique mixed case usernames are
|
||||
-- appended with a unique number. If you had three apps called all called asdf,
|
||||
-- they would then be renamed to e.g. asdf, asdf1234, and asdf5678.
|
||||
UPDATE
|
||||
workspace_apps
|
||||
SET
|
||||
display_name = workspace_apps.display_name || floor(random() * 10000)::text
|
||||
FROM
|
||||
row_numbers
|
||||
WHERE
|
||||
workspace_apps.id = row_numbers.id AND
|
||||
row_numbers.row_number > 1;
|
||||
|
||||
-- rename column "display_name" to "name" on "workspace_apps"
|
||||
ALTER TABLE "workspace_apps" RENAME COLUMN "display_name" TO "name";
|
||||
|
||||
-- restore unique index on "workspace_apps" table
|
||||
ALTER TABLE workspace_apps ADD CONSTRAINT workspace_apps_agent_id_name_key UNIQUE ("agent_id", "name");
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,9 @@
|
|||
BEGIN;
|
||||
|
||||
-- rename column "name" to "display_name" on "workspace_apps"
|
||||
ALTER TABLE "workspace_apps" RENAME COLUMN "name" TO "display_name";
|
||||
|
||||
-- drop constraint "workspace_apps_agent_id_name_key" on "workspace_apps".
|
||||
ALTER TABLE ONLY workspace_apps DROP CONSTRAINT IF EXISTS workspace_apps_agent_id_name_key;
|
||||
|
||||
COMMIT;
|
|
@ -667,7 +667,7 @@ 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"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Command sql.NullString `db:"command" json:"command"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
|
@ -677,6 +677,7 @@ type WorkspaceApp struct {
|
|||
Health WorkspaceAppHealth `db:"health" json:"health"`
|
||||
Subdomain bool `db:"subdomain" json:"subdomain"`
|
||||
SharingLevel AppSharingLevel `db:"sharing_level" json:"sharing_level"`
|
||||
Slug string `db:"slug" json:"slug"`
|
||||
}
|
||||
|
||||
type WorkspaceBuild struct {
|
||||
|
|
|
@ -100,7 +100,7 @@ type sqlcQuerier interface {
|
|||
GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error)
|
||||
GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error)
|
||||
GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error)
|
||||
GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg GetWorkspaceAppByAgentIDAndNameParams) (WorkspaceApp, error)
|
||||
GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg GetWorkspaceAppByAgentIDAndSlugParams) (WorkspaceApp, error)
|
||||
GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error)
|
||||
GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error)
|
||||
GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error)
|
||||
|
|
|
@ -973,8 +973,8 @@ func (q *sqlQuerier) UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyPar
|
|||
}
|
||||
|
||||
const deleteGroupByID = `-- name: DeleteGroupByID :exec
|
||||
DELETE FROM
|
||||
groups
|
||||
DELETE FROM
|
||||
groups
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
|
@ -985,8 +985,8 @@ func (q *sqlQuerier) DeleteGroupByID(ctx context.Context, id uuid.UUID) error {
|
|||
}
|
||||
|
||||
const deleteGroupMember = `-- name: DeleteGroupMember :exec
|
||||
DELETE FROM
|
||||
group_members
|
||||
DELETE FROM
|
||||
group_members
|
||||
WHERE
|
||||
user_id = $1
|
||||
`
|
||||
|
@ -4773,23 +4773,23 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up
|
|||
return err
|
||||
}
|
||||
|
||||
const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level FROM workspace_apps WHERE agent_id = $1 AND name = $2
|
||||
const getWorkspaceAppByAgentIDAndSlug = `-- name: GetWorkspaceAppByAgentIDAndSlug :one
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug FROM workspace_apps WHERE agent_id = $1 AND slug = $2
|
||||
`
|
||||
|
||||
type GetWorkspaceAppByAgentIDAndNameParams struct {
|
||||
type GetWorkspaceAppByAgentIDAndSlugParams struct {
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Slug string `db:"slug" json:"slug"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg GetWorkspaceAppByAgentIDAndNameParams) (WorkspaceApp, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceAppByAgentIDAndName, arg.AgentID, arg.Name)
|
||||
func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg GetWorkspaceAppByAgentIDAndSlugParams) (WorkspaceApp, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceAppByAgentIDAndSlug, arg.AgentID, arg.Slug)
|
||||
var i WorkspaceApp
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
|
@ -4799,12 +4799,13 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge
|
|||
&i.Health,
|
||||
&i.Subdomain,
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) {
|
||||
|
@ -4820,7 +4821,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
|||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
|
@ -4830,6 +4831,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
|||
&i.Health,
|
||||
&i.Subdomain,
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4845,7 +4847,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
|||
}
|
||||
|
||||
const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) {
|
||||
|
@ -4861,7 +4863,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
|||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
|
@ -4871,6 +4873,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
|||
&i.Health,
|
||||
&i.Subdomain,
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4886,7 +4889,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
|||
}
|
||||
|
||||
const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC
|
||||
SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) {
|
||||
|
@ -4902,7 +4905,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
|
|||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
|
@ -4912,6 +4915,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
|
|||
&i.Health,
|
||||
&i.Subdomain,
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4932,7 +4936,8 @@ INSERT INTO
|
|||
id,
|
||||
created_at,
|
||||
agent_id,
|
||||
name,
|
||||
slug,
|
||||
display_name,
|
||||
icon,
|
||||
command,
|
||||
url,
|
||||
|
@ -4944,14 +4949,15 @@ INSERT INTO
|
|||
health
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, created_at, agent_id, name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug
|
||||
`
|
||||
|
||||
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"`
|
||||
Slug string `db:"slug" json:"slug"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Command sql.NullString `db:"command" json:"command"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
|
@ -4968,7 +4974,8 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
|||
arg.ID,
|
||||
arg.CreatedAt,
|
||||
arg.AgentID,
|
||||
arg.Name,
|
||||
arg.Slug,
|
||||
arg.DisplayName,
|
||||
arg.Icon,
|
||||
arg.Command,
|
||||
arg.Url,
|
||||
|
@ -4984,7 +4991,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
|||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.AgentID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Command,
|
||||
&i.Url,
|
||||
|
@ -4994,6 +5001,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
|||
&i.Health,
|
||||
&i.Subdomain,
|
||||
&i.SharingLevel,
|
||||
&i.Slug,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ VALUES
|
|||
( $1, $2, $3, $4) RETURNING *;
|
||||
|
||||
-- We use the organization_id as the id
|
||||
-- for simplicity since all users is
|
||||
-- for simplicity since all users is
|
||||
-- every member of the org.
|
||||
-- name: InsertAllUsersGroup :one
|
||||
INSERT INTO groups (
|
||||
|
@ -110,14 +110,14 @@ INSERT INTO group_members (
|
|||
VALUES ( $1, $2);
|
||||
|
||||
-- name: DeleteGroupMember :exec
|
||||
DELETE FROM
|
||||
group_members
|
||||
DELETE FROM
|
||||
group_members
|
||||
WHERE
|
||||
user_id = $1;
|
||||
|
||||
-- name: DeleteGroupByID :exec
|
||||
DELETE FROM
|
||||
groups
|
||||
DELETE FROM
|
||||
groups
|
||||
WHERE
|
||||
id = $1;
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
-- name: GetWorkspaceAppsByAgentID :many
|
||||
SELECT * FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC;
|
||||
SELECT * FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC;
|
||||
|
||||
-- name: GetWorkspaceAppsByAgentIDs :many
|
||||
SELECT * FROM workspace_apps WHERE agent_id = ANY(@ids :: uuid [ ]) ORDER BY name ASC;
|
||||
SELECT * FROM workspace_apps WHERE agent_id = ANY(@ids :: uuid [ ]) ORDER BY slug ASC;
|
||||
|
||||
-- name: GetWorkspaceAppByAgentIDAndName :one
|
||||
SELECT * FROM workspace_apps WHERE agent_id = $1 AND name = $2;
|
||||
-- name: GetWorkspaceAppByAgentIDAndSlug :one
|
||||
SELECT * FROM workspace_apps WHERE agent_id = $1 AND slug = $2;
|
||||
|
||||
-- name: GetWorkspaceAppsCreatedAfter :many
|
||||
SELECT * FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC;
|
||||
SELECT * FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC;
|
||||
|
||||
-- name: InsertWorkspaceApp :one
|
||||
INSERT INTO
|
||||
|
@ -16,7 +16,8 @@ INSERT INTO
|
|||
id,
|
||||
created_at,
|
||||
agent_id,
|
||||
name,
|
||||
slug,
|
||||
display_name,
|
||||
icon,
|
||||
command,
|
||||
url,
|
||||
|
@ -28,7 +29,7 @@ INSERT INTO
|
|||
health
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *;
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *;
|
||||
|
||||
-- name: UpdateWorkspaceAppHealthByID :exec
|
||||
UPDATE
|
||||
|
|
|
@ -16,7 +16,7 @@ const (
|
|||
UniqueProvisionerDaemonsNameKey UniqueConstraint = "provisioner_daemons_name_key" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name);
|
||||
UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key);
|
||||
UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name);
|
||||
UniqueWorkspaceAppsAgentIDNameKey UniqueConstraint = "workspace_apps_agent_id_name_key" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_name_key UNIQUE (agent_id, name);
|
||||
UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug);
|
||||
UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id);
|
||||
UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number);
|
||||
UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name);
|
||||
|
|
|
@ -14,8 +14,8 @@ var (
|
|||
// Remove the "starts with" and "ends with" regex components.
|
||||
nameRegex = strings.Trim(UsernameValidRegex.String(), "^$")
|
||||
appURL = regexp.MustCompile(fmt.Sprintf(
|
||||
// {PORT/APP_NAME}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME}
|
||||
`^(?P<AppName>%[1]s)--(?P<AgentName>%[1]s)--(?P<WorkspaceName>%[1]s)--(?P<Username>%[1]s)$`,
|
||||
// {PORT/APP_SLUG}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME}
|
||||
`^(?P<AppSlug>%[1]s)--(?P<AgentName>%[1]s)--(?P<WorkspaceName>%[1]s)--(?P<Username>%[1]s)$`,
|
||||
nameRegex))
|
||||
|
||||
validHostnameLabelRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
|
||||
|
@ -23,8 +23,8 @@ var (
|
|||
|
||||
// ApplicationURL is a parsed application URL hostname.
|
||||
type ApplicationURL struct {
|
||||
// Only one of AppName or Port will be set.
|
||||
AppName string
|
||||
// Only one of AppSlug or Port will be set.
|
||||
AppSlug string
|
||||
Port uint16
|
||||
AgentName string
|
||||
WorkspaceName string
|
||||
|
@ -34,12 +34,12 @@ type ApplicationURL struct {
|
|||
// String returns the application URL hostname without scheme. You will likely
|
||||
// want to append a period and the base hostname.
|
||||
func (a ApplicationURL) String() string {
|
||||
appNameOrPort := a.AppName
|
||||
appSlugOrPort := a.AppSlug
|
||||
if a.Port != 0 {
|
||||
appNameOrPort = strconv.Itoa(int(a.Port))
|
||||
appSlugOrPort = strconv.Itoa(int(a.Port))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s--%s--%s--%s", appNameOrPort, a.AgentName, a.WorkspaceName, a.Username)
|
||||
return fmt.Sprintf("%s--%s--%s--%s", appSlugOrPort, a.AgentName, a.WorkspaceName, a.Username)
|
||||
}
|
||||
|
||||
// ParseSubdomainAppURL parses an ApplicationURL from the given subdomain. If
|
||||
|
@ -51,7 +51,7 @@ func (a ApplicationURL) String() string {
|
|||
//
|
||||
// Subdomains should be in the form:
|
||||
//
|
||||
// {PORT/APP_NAME}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME}
|
||||
// {PORT/APP_SLUG}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME}
|
||||
// (eg. https://8080--main--dev--dean.hi.c8s.io)
|
||||
func ParseSubdomainAppURL(subdomain string) (ApplicationURL, error) {
|
||||
matches := appURL.FindAllStringSubmatch(subdomain, -1)
|
||||
|
@ -60,9 +60,9 @@ func ParseSubdomainAppURL(subdomain string) (ApplicationURL, error) {
|
|||
}
|
||||
matchGroup := matches[0]
|
||||
|
||||
appName, port := AppNameOrPort(matchGroup[appURL.SubexpIndex("AppName")])
|
||||
appSlug, port := AppSlugOrPort(matchGroup[appURL.SubexpIndex("AppSlug")])
|
||||
return ApplicationURL{
|
||||
AppName: appName,
|
||||
AppSlug: appSlug,
|
||||
Port: port,
|
||||
AgentName: matchGroup[appURL.SubexpIndex("AgentName")],
|
||||
WorkspaceName: matchGroup[appURL.SubexpIndex("WorkspaceName")],
|
||||
|
@ -70,9 +70,9 @@ func ParseSubdomainAppURL(subdomain string) (ApplicationURL, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// AppNameOrPort takes a string and returns either the input string or a port
|
||||
// AppSlugOrPort takes a string and returns either the input string or a port
|
||||
// number.
|
||||
func AppNameOrPort(val string) (string, uint16) {
|
||||
func AppSlugOrPort(val string) (string, uint16) {
|
||||
port, err := strconv.ParseUint(val, 10, 16)
|
||||
if err != nil || port == 0 {
|
||||
port = 0
|
||||
|
|
|
@ -25,7 +25,7 @@ func TestApplicationURLString(t *testing.T) {
|
|||
{
|
||||
Name: "AppName",
|
||||
URL: httpapi.ApplicationURL{
|
||||
AppName: "app",
|
||||
AppSlug: "app",
|
||||
Port: 0,
|
||||
AgentName: "agent",
|
||||
WorkspaceName: "workspace",
|
||||
|
@ -36,7 +36,7 @@ func TestApplicationURLString(t *testing.T) {
|
|||
{
|
||||
Name: "Port",
|
||||
URL: httpapi.ApplicationURL{
|
||||
AppName: "",
|
||||
AppSlug: "",
|
||||
Port: 8080,
|
||||
AgentName: "agent",
|
||||
WorkspaceName: "workspace",
|
||||
|
@ -47,7 +47,7 @@ func TestApplicationURLString(t *testing.T) {
|
|||
{
|
||||
Name: "Both",
|
||||
URL: httpapi.ApplicationURL{
|
||||
AppName: "app",
|
||||
AppSlug: "app",
|
||||
Port: 8080,
|
||||
AgentName: "agent",
|
||||
WorkspaceName: "workspace",
|
||||
|
@ -111,7 +111,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
|
|||
Name: "AppName--Agent--Workspace--User",
|
||||
Subdomain: "app--agent--workspace--user",
|
||||
Expected: httpapi.ApplicationURL{
|
||||
AppName: "app",
|
||||
AppSlug: "app",
|
||||
Port: 0,
|
||||
AgentName: "agent",
|
||||
WorkspaceName: "workspace",
|
||||
|
@ -122,7 +122,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
|
|||
Name: "Port--Agent--Workspace--User",
|
||||
Subdomain: "8080--agent--workspace--user",
|
||||
Expected: httpapi.ApplicationURL{
|
||||
AppName: "",
|
||||
AppSlug: "",
|
||||
Port: 8080,
|
||||
AgentName: "agent",
|
||||
WorkspaceName: "workspace",
|
||||
|
@ -131,9 +131,9 @@ func TestParseSubdomainAppURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Name: "HyphenatedNames",
|
||||
Subdomain: "app-name--agent-name--workspace-name--user-name",
|
||||
Subdomain: "app-slug--agent-name--workspace-name--user-name",
|
||||
Expected: httpapi.ApplicationURL{
|
||||
AppName: "app-name",
|
||||
AppSlug: "app-slug",
|
||||
Port: 0,
|
||||
AgentName: "agent-name",
|
||||
WorkspaceName: "workspace-name",
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/telemetry"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner"
|
||||
"github.com/coder/coder/provisionerd/proto"
|
||||
"github.com/coder/coder/provisionersdk"
|
||||
sdkproto "github.com/coder/coder/provisionersdk/proto"
|
||||
|
@ -755,6 +756,7 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
|||
}
|
||||
snapshot.WorkspaceResources = append(snapshot.WorkspaceResources, telemetry.ConvertWorkspaceResource(resource))
|
||||
|
||||
var appSlugs = make(map[string]struct{})
|
||||
for _, prAgent := range protoResource.Agents {
|
||||
var instanceID sql.NullString
|
||||
if prAgent.GetInstanceId() != "" {
|
||||
|
@ -806,6 +808,18 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
|||
snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent))
|
||||
|
||||
for _, app := range prAgent.Apps {
|
||||
slug := app.Slug
|
||||
if slug == "" {
|
||||
return xerrors.Errorf("app must have a slug or name set")
|
||||
}
|
||||
if !provisioner.AppSlugRegex.MatchString(slug) {
|
||||
return xerrors.Errorf("app slug %q does not match regex %q", slug, provisioner.AppSlugRegex.String())
|
||||
}
|
||||
if _, exists := appSlugs[slug]; exists {
|
||||
return xerrors.Errorf("duplicate app slug, must be unique per template: %q", slug)
|
||||
}
|
||||
appSlugs[slug] = struct{}{}
|
||||
|
||||
health := database.WorkspaceAppHealthDisabled
|
||||
if app.Healthcheck == nil {
|
||||
app.Healthcheck = &sdkproto.Healthcheck{}
|
||||
|
@ -823,11 +837,12 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
|||
}
|
||||
|
||||
dbApp, err := db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: database.Now(),
|
||||
AgentID: dbAgent.ID,
|
||||
Name: app.Name,
|
||||
Icon: app.Icon,
|
||||
ID: uuid.New(),
|
||||
CreatedAt: database.Now(),
|
||||
AgentID: dbAgent.ID,
|
||||
Slug: slug,
|
||||
DisplayName: app.DisplayName,
|
||||
Icon: app.Icon,
|
||||
Command: sql.NullString{
|
||||
String: app.Command,
|
||||
Valid: app.Command != "",
|
||||
|
|
|
@ -596,7 +596,8 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp {
|
|||
for _, dbApp := range dbApps {
|
||||
apps = append(apps, codersdk.WorkspaceApp{
|
||||
ID: dbApp.ID,
|
||||
Name: dbApp.Name,
|
||||
Slug: dbApp.Slug,
|
||||
DisplayName: dbApp.DisplayName,
|
||||
Command: dbApp.Command.String,
|
||||
Icon: dbApp.Icon,
|
||||
Subdomain: dbApp.Subdomain,
|
||||
|
@ -868,7 +869,7 @@ func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request)
|
|||
for name, newHealth := range req.Healths {
|
||||
old := func() *database.WorkspaceApp {
|
||||
for _, app := range apps {
|
||||
if app.Name == name {
|
||||
if app.DisplayName == name {
|
||||
return &app
|
||||
}
|
||||
}
|
||||
|
|
|
@ -555,8 +555,9 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
|
|||
// should not exist in the response.
|
||||
_, appLPort := generateUnfilteredPort(t)
|
||||
app := &proto.App{
|
||||
Name: "test-app",
|
||||
Url: fmt.Sprintf("http://localhost:%d", appLPort),
|
||||
Slug: "test-app",
|
||||
DisplayName: "test-app",
|
||||
Url: fmt.Sprintf("http://localhost:%d", appLPort),
|
||||
}
|
||||
|
||||
// Generate a filtered port that should not exist in the response.
|
||||
|
@ -623,16 +624,18 @@ func TestWorkspaceAgentAppHealth(t *testing.T) {
|
|||
authToken := uuid.NewString()
|
||||
apps := []*proto.App{
|
||||
{
|
||||
Name: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Slug: "code-server",
|
||||
DisplayName: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
},
|
||||
{
|
||||
Name: "code-server-2",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Slug: "code-server-2",
|
||||
DisplayName: "code-server-2",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Healthcheck: &proto.Healthcheck{
|
||||
Url: "http://localhost:3000",
|
||||
Interval: 5,
|
||||
|
|
|
@ -51,9 +51,9 @@ func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
|
|||
workspace := httpmw.WorkspaceParam(r)
|
||||
agent := httpmw.WorkspaceAgentParam(r)
|
||||
|
||||
// We do not support port proxying on paths, so lookup the app by name.
|
||||
appName := chi.URLParam(r, "workspaceapp")
|
||||
app, ok := api.lookupWorkspaceApp(rw, r, agent.ID, appName)
|
||||
// We do not support port proxying on paths, so lookup the app by slug.
|
||||
appSlug := chi.URLParam(r, "workspaceapp")
|
||||
app, ok := api.lookupWorkspaceApp(rw, r, agent.ID, appSlug)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -180,8 +180,8 @@ func (api *API) handleSubdomainApplications(middlewares ...func(http.Handler) ht
|
|||
agent := httpmw.WorkspaceAgentParam(r)
|
||||
|
||||
var workspaceAppPtr *database.WorkspaceApp
|
||||
if app.AppName != "" {
|
||||
workspaceApp, ok := api.lookupWorkspaceApp(rw, r, agent.ID, app.AppName)
|
||||
if app.AppSlug != "" {
|
||||
workspaceApp, ok := api.lookupWorkspaceApp(rw, r, agent.ID, app.AppSlug)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -251,14 +251,14 @@ func (api *API) parseWorkspaceApplicationHostname(rw http.ResponseWriter, r *htt
|
|||
return app, true
|
||||
}
|
||||
|
||||
// lookupWorkspaceApp looks up the workspace application by name in the given
|
||||
// lookupWorkspaceApp looks up the workspace application by slug in the given
|
||||
// agent and returns it. If the application is not found or there was a server
|
||||
// error while looking it up, an HTML error page is returned and false is
|
||||
// returned so the caller can return early.
|
||||
func (api *API) lookupWorkspaceApp(rw http.ResponseWriter, r *http.Request, agentID uuid.UUID, appName string) (database.WorkspaceApp, bool) {
|
||||
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
|
||||
func (api *API) lookupWorkspaceApp(rw http.ResponseWriter, r *http.Request, agentID uuid.UUID, appSlug string) (database.WorkspaceApp, bool) {
|
||||
app, err := api.Database.GetWorkspaceAppByAgentIDAndSlug(r.Context(), database.GetWorkspaceAppByAgentIDAndSlugParams{
|
||||
AgentID: agentID,
|
||||
Name: appName,
|
||||
Slug: appSlug,
|
||||
})
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
renderApplicationNotFound(rw, r, api.AccessURL)
|
||||
|
@ -402,12 +402,28 @@ func (api *API) verifyWorkspaceApplicationSubdomainAuth(rw http.ResponseWriter,
|
|||
return false
|
||||
}
|
||||
|
||||
hostSplit := strings.SplitN(api.AppHostname, ".", 2)
|
||||
if len(hostSplit) != 2 {
|
||||
// This should be impossible as we verify the app hostname on
|
||||
// startup, but we'll check anyways.
|
||||
api.Logger.Error(r.Context(), "could not split invalid app hostname", slog.F("hostname", api.AppHostname))
|
||||
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
|
||||
Status: http.StatusInternalServerError,
|
||||
Title: "Internal Server Error",
|
||||
Description: "The app is configured with an invalid app wildcard hostname. Please contact an administrator.",
|
||||
RetryEnabled: false,
|
||||
DashboardURL: api.AccessURL.String(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// Set the app cookie for all subdomains of api.AppHostname. This cookie
|
||||
// is handled properly by the ExtractAPIKey middleware.
|
||||
cookieHost := "." + hostSplit[1]
|
||||
http.SetCookie(rw, &http.Cookie{
|
||||
Name: httpmw.DevURLSessionTokenCookie,
|
||||
Value: apiKey,
|
||||
Domain: "." + api.AppHostname,
|
||||
Domain: cookieHost,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
|
@ -589,21 +605,18 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res
|
|||
return
|
||||
}
|
||||
|
||||
// If the app does not exist, but the app name is a port number, then
|
||||
// route to the port as an "anonymous app". We only support HTTP for
|
||||
// port-based URLs.
|
||||
// If the app does not exist, but the app slug is a port number, then route
|
||||
// to the port as an "anonymous app". We only support HTTP for port-based
|
||||
// URLs.
|
||||
//
|
||||
// This is only supported for subdomain-based applications.
|
||||
internalURL := fmt.Sprintf("http://127.0.0.1:%d", proxyApp.Port)
|
||||
|
||||
// If the app name was used instead, fetch the app from the database so we
|
||||
// can get the internal URL.
|
||||
if proxyApp.App != nil {
|
||||
if !proxyApp.App.Url.Valid {
|
||||
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: "Bad Request",
|
||||
Description: fmt.Sprintf("Application %q does not have a URL set.", proxyApp.App.Name),
|
||||
Description: fmt.Sprintf("Application %q does not have a URL set.", proxyApp.App.Slug),
|
||||
RetryEnabled: true,
|
||||
DashboardURL: api.AccessURL.String(),
|
||||
})
|
||||
|
|
|
@ -160,23 +160,27 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
|
|||
},
|
||||
Apps: []*proto.App{
|
||||
{
|
||||
Name: proxyTestAppNameFake,
|
||||
Slug: proxyTestAppNameFake,
|
||||
DisplayName: proxyTestAppNameFake,
|
||||
SharingLevel: proto.AppSharingLevel_OWNER,
|
||||
// Hopefully this IP and port doesn't exist.
|
||||
Url: "http://127.1.0.1:65535",
|
||||
},
|
||||
{
|
||||
Name: proxyTestAppNameOwner,
|
||||
Slug: proxyTestAppNameOwner,
|
||||
DisplayName: proxyTestAppNameOwner,
|
||||
SharingLevel: proto.AppSharingLevel_OWNER,
|
||||
Url: appURL,
|
||||
},
|
||||
{
|
||||
Name: proxyTestAppNameAuthenticated,
|
||||
Slug: proxyTestAppNameAuthenticated,
|
||||
DisplayName: proxyTestAppNameAuthenticated,
|
||||
SharingLevel: proto.AppSharingLevel_AUTHENTICATED,
|
||||
Url: appURL,
|
||||
},
|
||||
{
|
||||
Name: proxyTestAppNamePublic,
|
||||
Slug: proxyTestAppNamePublic,
|
||||
DisplayName: proxyTestAppNamePublic,
|
||||
SharingLevel: proto.AppSharingLevel_PUBLIC,
|
||||
Url: appURL,
|
||||
},
|
||||
|
@ -624,7 +628,7 @@ func TestWorkspaceAppsProxySubdomain(t *testing.T) {
|
|||
require.NoError(t, err, "get app host")
|
||||
|
||||
subdomain := httpapi.ApplicationURL{
|
||||
AppName: appName,
|
||||
AppSlug: appName,
|
||||
Port: port,
|
||||
AgentName: proxyTestAgentName,
|
||||
WorkspaceName: workspaces[0].Name,
|
||||
|
@ -855,7 +859,7 @@ func TestAppSharing(t *testing.T) {
|
|||
proxyTestAppNamePublic: codersdk.WorkspaceAppSharingLevelPublic,
|
||||
}
|
||||
for _, app := range agnt.Apps {
|
||||
found[app.Name] = app.SharingLevel
|
||||
found[app.DisplayName] = app.SharingLevel
|
||||
}
|
||||
require.Equal(t, expected, found, "apps have incorrect sharing levels")
|
||||
|
||||
|
|
|
@ -1435,16 +1435,18 @@ func TestWorkspaceResource(t *testing.T) {
|
|||
user := coderdtest.CreateFirstUser(t, client)
|
||||
apps := []*proto.App{
|
||||
{
|
||||
Name: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Slug: "code-server",
|
||||
DisplayName: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
},
|
||||
{
|
||||
Name: "code-server-2",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Slug: "code-server-2",
|
||||
DisplayName: "code-server-2",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Healthcheck: &proto.Healthcheck{
|
||||
Url: "http://localhost:3000",
|
||||
Interval: 5,
|
||||
|
@ -1487,7 +1489,7 @@ func TestWorkspaceResource(t *testing.T) {
|
|||
app := apps[0]
|
||||
require.EqualValues(t, app.Command, got.Command)
|
||||
require.EqualValues(t, app.Icon, got.Icon)
|
||||
require.EqualValues(t, app.Name, got.Name)
|
||||
require.EqualValues(t, app.DisplayName, got.DisplayName)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health)
|
||||
require.EqualValues(t, "", got.Healthcheck.URL)
|
||||
require.EqualValues(t, 0, got.Healthcheck.Interval)
|
||||
|
@ -1496,7 +1498,7 @@ func TestWorkspaceResource(t *testing.T) {
|
|||
app = apps[1]
|
||||
require.EqualValues(t, app.Command, got.Command)
|
||||
require.EqualValues(t, app.Icon, got.Icon)
|
||||
require.EqualValues(t, app.Name, got.Name)
|
||||
require.EqualValues(t, app.DisplayName, got.DisplayName)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, got.Health)
|
||||
require.EqualValues(t, app.Healthcheck.Url, got.Healthcheck.URL)
|
||||
require.EqualValues(t, app.Healthcheck.Interval, got.Healthcheck.Interval)
|
||||
|
|
|
@ -23,9 +23,11 @@ const (
|
|||
|
||||
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"`
|
||||
// Slug is a unique identifier within the agent.
|
||||
Slug string `json:"slug"`
|
||||
// DisplayName is a friendly name for the app.
|
||||
DisplayName string `json:"display_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"`
|
||||
|
|
|
@ -19,7 +19,8 @@ be used as a Coder application. For example:
|
|||
# Note: Portainer must be already running in the workspace
|
||||
resource "coder_app" "portainer" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "portainer"
|
||||
slug = "portainer"
|
||||
display_name = "Portainer"
|
||||
icon = "https://simpleicons.org/icons/portainer.svg"
|
||||
url = "https://localhost:9443/api/status"
|
||||
|
||||
|
@ -75,10 +76,11 @@ You'll also need to specify a `coder_app` resource related to the agent. This is
|
|||
|
||||
```hcl
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
@ -179,10 +181,11 @@ EOT
|
|||
}
|
||||
|
||||
resource "coder_app" "intellij" {
|
||||
agent_id = coder_agent.coder.id
|
||||
name = "${var.jetbrains-ide}"
|
||||
icon = "/icon/intellij.svg"
|
||||
url = "http://localhost:8997/"
|
||||
agent_id = coder_agent.coder.id
|
||||
slug = "intellij"
|
||||
display_name = "${var.jetbrains-ide}"
|
||||
icon = "/icon/intellij.svg"
|
||||
url = "http://localhost:8997/"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:8997/"
|
||||
|
@ -233,10 +236,11 @@ EOF
|
|||
}
|
||||
|
||||
resource "coder_app" "jupyter" {
|
||||
agent_id = coder_agent.coder.id
|
||||
name = "JupyterLab"
|
||||
url = "http://localhost:8888${local.jupyter_base_path}"
|
||||
icon = "/icon/jupyter.svg"
|
||||
agent_id = coder_agent.coder.id
|
||||
slug = "jupyter"
|
||||
display_name = "JupyterLab"
|
||||
url = "http://localhost:8888${local.jupyter_base_path}"
|
||||
icon = "/icon/jupyter.svg"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:8888${local.jupyter_base_path}"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
@ -38,12 +38,13 @@ resource "coder_agent" "dev" {
|
|||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.dev.id
|
||||
name = "code-server"
|
||||
url = "http://localhost:13337/"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.dev.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
url = "http://localhost:13337/"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -83,17 +83,20 @@ func setupWorkspaceAgent(t *testing.T, client *codersdk.Client, user codersdk.Cr
|
|||
},
|
||||
Apps: []*proto.App{
|
||||
{
|
||||
Name: testAppNameOwner,
|
||||
Slug: testAppNameOwner,
|
||||
DisplayName: testAppNameOwner,
|
||||
SharingLevel: proto.AppSharingLevel_OWNER,
|
||||
Url: fmt.Sprintf("http://localhost:%d", appPort),
|
||||
},
|
||||
{
|
||||
Name: testAppNameAuthenticated,
|
||||
Slug: testAppNameAuthenticated,
|
||||
DisplayName: testAppNameAuthenticated,
|
||||
SharingLevel: proto.AppSharingLevel_AUTHENTICATED,
|
||||
Url: fmt.Sprintf("http://localhost:%d", appPort),
|
||||
},
|
||||
{
|
||||
Name: testAppNamePublic,
|
||||
Slug: testAppNamePublic,
|
||||
DisplayName: testAppNamePublic,
|
||||
SharingLevel: proto.AppSharingLevel_PUBLIC,
|
||||
Url: fmt.Sprintf("http://localhost:%d", appPort),
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ terraform {
|
|||
}
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,12 +105,13 @@ resource "coder_agent" "coder" {
|
|||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.coder.id
|
||||
name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.coder.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,12 +86,13 @@ resource "coder_agent" "main" {
|
|||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
@ -38,12 +38,13 @@ resource "coder_agent" "main" {
|
|||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
url = "http://localhost:8080/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
url = "http://localhost:8080/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:8080/healthz"
|
||||
|
|
|
@ -3,7 +3,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
@ -34,12 +34,13 @@ resource "coder_agent" "main" {
|
|||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -9,7 +9,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
@ -43,12 +43,13 @@ resource "coder_agent" "main" {
|
|||
}
|
||||
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
|
@ -60,12 +60,13 @@ resource "coder_agent" "main" {
|
|||
|
||||
# code-server
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
|
@ -50,12 +50,13 @@ resource "coder_agent" "main" {
|
|||
|
||||
# code-server
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
kubernetes = {
|
||||
source = "hashicorp/kubernetes"
|
||||
|
@ -70,12 +70,13 @@ resource "coder_agent" "main" {
|
|||
|
||||
# code-server
|
||||
resource "coder_app" "code-server" {
|
||||
agent_id = coder_agent.main.id
|
||||
name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
agent_id = coder_agent.main.id
|
||||
slug = "code-server"
|
||||
display_name = "code-server"
|
||||
icon = "/icon/code.svg"
|
||||
url = "http://localhost:13337?folder=/home/coder"
|
||||
subdomain = false
|
||||
share = "owner"
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package provisioner
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
// AppSlugRegex is the regex used to validate the slug of a coder_app
|
||||
// resource. It must be a valid hostname and cannot contain two consecutive
|
||||
// hyphens or start/end with a hyphen.
|
||||
//
|
||||
// This regex is duplicated in the terraform provider code, so make sure to
|
||||
// update it there as well.
|
||||
//
|
||||
// There are test cases for this regex in appslug_test.go.
|
||||
AppSlugRegex = regexp.MustCompile(`^[a-z0-9](-?[a-z0-9])*$`)
|
||||
)
|
|
@ -0,0 +1,64 @@
|
|||
package provisioner_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/provisioner"
|
||||
)
|
||||
|
||||
func TestValidAppSlugRegex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validStrings := []string{
|
||||
"a",
|
||||
"1",
|
||||
"a1",
|
||||
"1a",
|
||||
"1a1",
|
||||
"1-1",
|
||||
"a-a",
|
||||
"ab-cd",
|
||||
"ab-cd-ef",
|
||||
"abc-123",
|
||||
"a-123",
|
||||
"abc-1",
|
||||
"ab-c",
|
||||
"a-bc",
|
||||
}
|
||||
|
||||
for _, s := range validStrings {
|
||||
require.True(t, provisioner.AppSlugRegex.MatchString(s), s)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
invalidStrings := []string{
|
||||
"",
|
||||
"-",
|
||||
"-abc",
|
||||
"abc-",
|
||||
"ab--cd",
|
||||
"a--bc",
|
||||
"ab--c",
|
||||
"_",
|
||||
"ab_cd",
|
||||
"_abc",
|
||||
"abc_",
|
||||
" ",
|
||||
"abc ",
|
||||
" abc",
|
||||
"ab cd",
|
||||
}
|
||||
|
||||
for _, s := range invalidStrings {
|
||||
require.False(t, provisioner.AppSlugRegex.MatchString(s), s)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/provisioner"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
|
@ -25,7 +26,12 @@ type agentAttributes struct {
|
|||
|
||||
// A mapping of attributes on the "coder_app" resource.
|
||||
type agentAppAttributes struct {
|
||||
AgentID string `mapstructure:"agent_id"`
|
||||
AgentID string `mapstructure:"agent_id"`
|
||||
// Slug is required in terraform, but to avoid breaking existing users we
|
||||
// will default to the resource name if it is not specified.
|
||||
Slug string `mapstructure:"slug"`
|
||||
DisplayName string `mapstructure:"display_name"`
|
||||
// Name is deprecated in favor of DisplayName.
|
||||
Name string `mapstructure:"name"`
|
||||
Icon string `mapstructure:"icon"`
|
||||
URL string `mapstructure:"url"`
|
||||
|
@ -214,19 +220,40 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
}
|
||||
|
||||
// Associate Apps with agents.
|
||||
appSlugs := make(map[string]struct{})
|
||||
for _, resource := range tfResourceByLabel {
|
||||
if resource.Type != "coder_app" {
|
||||
continue
|
||||
}
|
||||
|
||||
var attrs agentAppAttributes
|
||||
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
|
||||
|
||||
// Default to the resource name if none is set!
|
||||
if attrs.Slug == "" {
|
||||
attrs.Slug = resource.Name
|
||||
}
|
||||
if attrs.DisplayName == "" {
|
||||
if attrs.Name != "" {
|
||||
// Name is deprecated but still accepted.
|
||||
attrs.DisplayName = attrs.Name
|
||||
} else {
|
||||
attrs.DisplayName = attrs.Slug
|
||||
}
|
||||
}
|
||||
|
||||
if !provisioner.AppSlugRegex.MatchString(attrs.Slug) {
|
||||
return nil, xerrors.Errorf("invalid app slug %q, please update your coder/coder provider to the latest version and specify the slug property on each coder_app", attrs.Slug)
|
||||
}
|
||||
|
||||
if _, exists := appSlugs[attrs.Slug]; exists {
|
||||
return nil, xerrors.Errorf("duplicate app slug, they must be unique per template: %q", attrs.Slug)
|
||||
}
|
||||
appSlugs[attrs.Slug] = struct{}{}
|
||||
|
||||
var healthcheck *proto.Healthcheck
|
||||
if len(attrs.Healthcheck) != 0 {
|
||||
healthcheck = &proto.Healthcheck{
|
||||
|
@ -253,7 +280,8 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
continue
|
||||
}
|
||||
agent.Apps = append(agent.Apps, &proto.App{
|
||||
Name: attrs.Name,
|
||||
Slug: attrs.Slug,
|
||||
DisplayName: attrs.DisplayName,
|
||||
Command: attrs.Command,
|
||||
Url: attrs.URL,
|
||||
Icon: attrs.Icon,
|
||||
|
|
|
@ -110,13 +110,15 @@ func TestConvertResources(t *testing.T) {
|
|||
Architecture: "amd64",
|
||||
Apps: []*proto.App{
|
||||
{
|
||||
Name: "app1",
|
||||
Slug: "app1",
|
||||
DisplayName: "app1",
|
||||
// Subdomain defaults to false if unspecified.
|
||||
Subdomain: false,
|
||||
},
|
||||
{
|
||||
Name: "app2",
|
||||
Subdomain: true,
|
||||
Slug: "app2",
|
||||
DisplayName: "app2",
|
||||
Subdomain: true,
|
||||
Healthcheck: &proto.Healthcheck{
|
||||
Url: "http://localhost:13337/healthz",
|
||||
Interval: 5,
|
||||
|
@ -124,8 +126,9 @@ func TestConvertResources(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "app3",
|
||||
Subdomain: false,
|
||||
Slug: "app3",
|
||||
DisplayName: "app3",
|
||||
Subdomain: false,
|
||||
},
|
||||
},
|
||||
Auth: &proto.Agent_Token{},
|
||||
|
@ -182,12 +185,23 @@ func TestConvertResources(t *testing.T) {
|
|||
expectedNoMetadata = append(expectedNoMetadata, resourceCopy)
|
||||
}
|
||||
|
||||
resourcesWant, err := json.Marshal(expectedNoMetadata)
|
||||
// Convert expectedNoMetadata and resources into a
|
||||
// []map[string]interface{} so they can be compared easily.
|
||||
data, err := json.Marshal(expectedNoMetadata)
|
||||
require.NoError(t, err)
|
||||
resourcesGot, err := json.Marshal(resources)
|
||||
var expectedNoMetadataMap []map[string]interface{}
|
||||
err = json.Unmarshal(data, &expectedNoMetadataMap)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(resourcesWant), string(resourcesGot))
|
||||
|
||||
data, err = json.Marshal(resources)
|
||||
require.NoError(t, err)
|
||||
var resourcesMap []map[string]interface{}
|
||||
err = json.Unmarshal(data, &resourcesMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedNoMetadataMap, resourcesMap)
|
||||
})
|
||||
|
||||
t.Run("Provision", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tfStateRaw, err := os.ReadFile(filepath.Join(dir, folderName+".tfstate.json"))
|
||||
|
@ -212,17 +226,67 @@ func TestConvertResources(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
resourcesWant, err := json.Marshal(expected)
|
||||
// Convert expectedNoMetadata and resources into a
|
||||
// []map[string]interface{} so they can be compared easily.
|
||||
data, err := json.Marshal(expected)
|
||||
require.NoError(t, err)
|
||||
resourcesGot, err := json.Marshal(resources)
|
||||
var expectedMap []map[string]interface{}
|
||||
err = json.Unmarshal(data, &expectedMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, string(resourcesWant), string(resourcesGot))
|
||||
data, err = json.Marshal(resources)
|
||||
require.NoError(t, err)
|
||||
var resourcesMap []map[string]interface{}
|
||||
err = json.Unmarshal(data, &resourcesMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedMap, resourcesMap)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppSlugValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// nolint:dogsled
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
|
||||
// Load the multiple-apps state file and edit it.
|
||||
dir := filepath.Join(filepath.Dir(filename), "testdata", "multiple-apps")
|
||||
tfPlanRaw, err := os.ReadFile(filepath.Join(dir, "multiple-apps.tfplan.json"))
|
||||
require.NoError(t, err)
|
||||
var tfPlan tfjson.Plan
|
||||
err = json.Unmarshal(tfPlanRaw, &tfPlan)
|
||||
require.NoError(t, err)
|
||||
tfPlanGraph, err := os.ReadFile(filepath.Join(dir, "multiple-apps.tfplan.dot"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Change all slugs to be invalid.
|
||||
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
|
||||
if resource.Type == "coder_app" {
|
||||
resource.AttributeValues["slug"] = "$$$ invalid slug $$$"
|
||||
}
|
||||
}
|
||||
|
||||
resources, err := terraform.ConvertResources(tfPlan.PlannedValues.RootModule, string(tfPlanGraph))
|
||||
require.Nil(t, resources)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "invalid app slug")
|
||||
|
||||
// Change all slugs to be identical and valid.
|
||||
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
|
||||
if resource.Type == "coder_app" {
|
||||
resource.AttributeValues["slug"] = "valid"
|
||||
}
|
||||
}
|
||||
|
||||
resources, err = terraform.ConvertResources(tfPlan.PlannedValues.RootModule, string(tfPlanGraph))
|
||||
require.Nil(t, resources)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "duplicate app slug")
|
||||
}
|
||||
|
||||
func TestInstanceIDAssociation(t *testing.T) {
|
||||
t.Parallel()
|
||||
type tc struct {
|
||||
|
@ -304,7 +368,7 @@ func sortResources(resources []*proto.Resource) {
|
|||
for _, resource := range resources {
|
||||
for _, agent := range resource.Agents {
|
||||
sort.Slice(agent.Apps, func(i, j int) bool {
|
||||
return agent.Apps[i].Name < agent.Apps[j].Name
|
||||
return agent.Apps[i].Slug < agent.Apps[j].Slug
|
||||
})
|
||||
}
|
||||
sort.Slice(resource.Agents, func(i, j int) bool {
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,9 @@
|
|||
"name": "main",
|
||||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"change": {
|
||||
"actions": ["create"],
|
||||
"actions": [
|
||||
"create"
|
||||
],
|
||||
"before": null,
|
||||
"after": {
|
||||
"arch": "amd64",
|
||||
|
@ -95,7 +97,9 @@
|
|||
"name": "script",
|
||||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"change": {
|
||||
"actions": ["read"],
|
||||
"actions": [
|
||||
"read"
|
||||
],
|
||||
"before": null,
|
||||
"after": {
|
||||
"inputs": {}
|
||||
|
@ -125,7 +129,9 @@
|
|||
"name": "example",
|
||||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"change": {
|
||||
"actions": ["create"],
|
||||
"actions": [
|
||||
"create"
|
||||
],
|
||||
"before": null,
|
||||
"after": {
|
||||
"triggers": null
|
||||
|
@ -143,7 +149,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"module.module:null": {
|
||||
"name": "null",
|
||||
|
@ -175,7 +181,10 @@
|
|||
"source": "./module",
|
||||
"expressions": {
|
||||
"script": {
|
||||
"references": ["coder_agent.main.init_script", "coder_agent.main"]
|
||||
"references": [
|
||||
"coder_agent.main.init_script",
|
||||
"coder_agent.main"
|
||||
]
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
|
@ -187,7 +196,9 @@
|
|||
"name": "example",
|
||||
"provider_config_key": "module.module:null",
|
||||
"schema_version": 0,
|
||||
"depends_on": ["data.null_data_source.script"]
|
||||
"depends_on": [
|
||||
"data.null_data_source.script"
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "data.null_data_source.script",
|
||||
|
@ -197,7 +208,9 @@
|
|||
"provider_config_key": "module.module:null",
|
||||
"expressions": {
|
||||
"inputs": {
|
||||
"references": ["var.script"]
|
||||
"references": [
|
||||
"var.script"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schema_version": 0
|
||||
|
@ -214,7 +227,9 @@
|
|||
"relevant_attributes": [
|
||||
{
|
||||
"resource": "coder_agent.main",
|
||||
"attribute": ["init_script"]
|
||||
"attribute": [
|
||||
"init_script"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "b92bd0ce-d854-47af-a2f6-4941cd5dbd27",
|
||||
"id": "8a08d6a8-2ae8-4af3-b385-9d7c9230c3d3",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "3f1b6b3f-7ea9-4944-bef4-8be9b78db8ae"
|
||||
"token": "e5397170-34e8-4f59-9b3d-85d11203aba1"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
|||
"outputs": {
|
||||
"script": ""
|
||||
},
|
||||
"random": "5257014674084238393"
|
||||
"random": "4606778210381604065"
|
||||
},
|
||||
"sensitive_values": {
|
||||
"inputs": {},
|
||||
|
@ -59,7 +59,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "6805057619323391144",
|
||||
"id": "8484494817832091886",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
generated
vendored
2
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
generated
vendored
|
@ -121,7 +121,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
|
8
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
generated
vendored
8
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
generated
vendored
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "d8de89cb-bb6b-4f4f-80f8-e5d39e8c5f62",
|
||||
"id": "8c46ed09-5988-47fe-8f1b-2afe4ec0b35a",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "4e877d5c-95c4-4365-b9a1-856348b54f43"
|
||||
"token": "af26634c-4fa8-4b60-aff4-736d43457b35"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "2870641260310442024",
|
||||
"id": "1333327345487383126",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -46,7 +46,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "7093709823890756895",
|
||||
"id": "1306294717300675697",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "5c00c97c-7291-47b7-96cf-3ac7d7588a99",
|
||||
"id": "3621f0c7-090a-4610-8fd0-bdcf835225bd",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "a1939d12-8b8a-414b-b745-3fac020e51c0"
|
||||
"token": "4cb0ef71-0161-4a1a-b8f1-b9d81f53d658"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "8930370582092686733",
|
||||
"id": "3108014752132131382",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -46,7 +46,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "8209925920170986769",
|
||||
"id": "8356243415524842498",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "google-instance-identity",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "248ed639-3dbe-479e-909a-37d5d226529f",
|
||||
"id": "1156666a-c202-4c54-9831-6b62dbf665fe",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "8bee2595-095f-4965-ade2-deef475023d6"
|
||||
"token": "80a893a4-fcb1-4a3a-824d-74cf5317d307"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,8 +32,8 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "248ed639-3dbe-479e-909a-37d5d226529f",
|
||||
"id": "edbfac7a-a88d-433a-ab7c-be3816656477",
|
||||
"agent_id": "1156666a-c202-4c54-9831-6b62dbf665fe",
|
||||
"id": "ec6451f5-fea2-4d6f-aedc-822b93723abd",
|
||||
"instance_id": "example"
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -47,7 +47,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "5674804341417746589",
|
||||
"id": "5076117657273396114",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "882ce97a-3c12-410f-8916-e3bc03862162",
|
||||
"id": "dc6b52bf-7bcb-4657-9c11-2859d8721ba9",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "b24ba29b-8cb3-42da-91c5-599c7be310f7"
|
||||
"token": "85317d35-1e92-4565-850e-8ee17bf86992"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -36,11 +36,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "8a26cec7-3189-4eaf-99a1-1dce00b756dc",
|
||||
"id": "a709bb80-b4df-4d4a-9cc3-4bedd009b44f",
|
||||
"init_script": "",
|
||||
"os": "darwin",
|
||||
"startup_script": null,
|
||||
"token": "6a155e3b-3279-40cb-9c16-4b827b561bc1"
|
||||
"token": "a4b37df4-dbdd-494b-9434-92abaa88c23b"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -56,11 +56,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "57486477-64a5-4fea-8223-dbf3c259d710",
|
||||
"id": "e429fb2c-1d4a-4c7c-9747-f495e5611c9e",
|
||||
"init_script": "",
|
||||
"os": "windows",
|
||||
"startup_script": null,
|
||||
"token": "0fa9933e-802a-4d6a-b273-43c05993e52a"
|
||||
"token": "27009ab7-ec2e-476c-9193-177eeea0766c"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -72,7 +72,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "8587500025119121667",
|
||||
"id": "4682926564646626748",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ resource "coder_agent" "dev1" {
|
|||
# app1 is for testing subdomain default.
|
||||
resource "coder_app" "app1" {
|
||||
agent_id = coder_agent.dev1.id
|
||||
slug = "app1"
|
||||
# subdomain should default to false.
|
||||
# subdomain = false
|
||||
}
|
||||
|
@ -22,6 +23,7 @@ resource "coder_app" "app1" {
|
|||
# app2 tests that subdomaincan be true, and that healthchecks work.
|
||||
resource "coder_app" "app2" {
|
||||
agent_id = coder_agent.dev1.id
|
||||
slug = "app2"
|
||||
subdomain = true
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
|
@ -33,6 +35,7 @@ resource "coder_app" "app2" {
|
|||
# app3 tests that subdomain can explicitly be false.
|
||||
resource "coder_app" "app3" {
|
||||
agent_id = coder_agent.dev1.id
|
||||
slug = "app3"
|
||||
subdomain = false
|
||||
}
|
||||
|
||||
|
|
|
@ -30,10 +30,13 @@
|
|||
"schema_version": 0,
|
||||
"values": {
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app1",
|
||||
"subdomain": null,
|
||||
"url": null
|
||||
},
|
||||
|
@ -50,6 +53,7 @@
|
|||
"schema_version": 0,
|
||||
"values": {
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": 5,
|
||||
|
@ -60,6 +64,8 @@
|
|||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app2",
|
||||
"subdomain": true,
|
||||
"url": null
|
||||
},
|
||||
|
@ -76,10 +82,13 @@
|
|||
"schema_version": 0,
|
||||
"values": {
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app3",
|
||||
"subdomain": false,
|
||||
"url": null
|
||||
},
|
||||
|
@ -142,10 +151,13 @@
|
|||
"before": null,
|
||||
"after": {
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app1",
|
||||
"subdomain": null,
|
||||
"url": null
|
||||
},
|
||||
|
@ -171,6 +183,7 @@
|
|||
"before": null,
|
||||
"after": {
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": 5,
|
||||
|
@ -181,6 +194,8 @@
|
|||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app2",
|
||||
"subdomain": true,
|
||||
"url": null
|
||||
},
|
||||
|
@ -206,10 +221,13 @@
|
|||
"before": null,
|
||||
"after": {
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app3",
|
||||
"subdomain": false,
|
||||
"url": null
|
||||
},
|
||||
|
@ -249,7 +267,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
@ -283,6 +301,9 @@
|
|||
"expressions": {
|
||||
"agent_id": {
|
||||
"references": ["coder_agent.dev1.id", "coder_agent.dev1"]
|
||||
},
|
||||
"slug": {
|
||||
"constant_value": "app1"
|
||||
}
|
||||
},
|
||||
"schema_version": 0
|
||||
|
@ -310,6 +331,9 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"slug": {
|
||||
"constant_value": "app2"
|
||||
},
|
||||
"subdomain": {
|
||||
"constant_value": true
|
||||
}
|
||||
|
@ -326,6 +350,9 @@
|
|||
"agent_id": {
|
||||
"references": ["coder_agent.dev1.id", "coder_agent.dev1"]
|
||||
},
|
||||
"slug": {
|
||||
"constant_value": "app3"
|
||||
},
|
||||
"subdomain": {
|
||||
"constant_value": false
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "ecf210c8-aaa7-4a14-9b44-2a5f805f0126",
|
||||
"id": "4fa379bd-8aa9-48f2-9868-2da104013c3c",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "7e748146-cea2-45cb-927d-b4a90b0021b3"
|
||||
"token": "4eb813cb-8f29-454c-91d9-b430d76d7fcd"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,13 +32,16 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "ecf210c8-aaa7-4a14-9b44-2a5f805f0126",
|
||||
"agent_id": "4fa379bd-8aa9-48f2-9868-2da104013c3c",
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"id": "95667002-bd60-4d2c-9313-0666f66c44ff",
|
||||
"id": "f303f406-b9ea-4253-935e-f80f7be54a97",
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app1",
|
||||
"subdomain": null,
|
||||
"url": null
|
||||
},
|
||||
|
@ -55,8 +58,9 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "ecf210c8-aaa7-4a14-9b44-2a5f805f0126",
|
||||
"agent_id": "4fa379bd-8aa9-48f2-9868-2da104013c3c",
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": 5,
|
||||
|
@ -65,9 +69,11 @@
|
|||
}
|
||||
],
|
||||
"icon": null,
|
||||
"id": "817c6904-69e1-485f-a057-4ddac83a9c5a",
|
||||
"id": "7086ae57-501d-4b39-bfaf-d30b83f753d4",
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app2",
|
||||
"subdomain": true,
|
||||
"url": null
|
||||
},
|
||||
|
@ -84,13 +90,16 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "ecf210c8-aaa7-4a14-9b44-2a5f805f0126",
|
||||
"agent_id": "4fa379bd-8aa9-48f2-9868-2da104013c3c",
|
||||
"command": null,
|
||||
"display_name": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"id": "c4a502b3-cc82-4fdf-952b-4b429e711798",
|
||||
"id": "e4b1f16b-2b8d-4278-abec-1f876f8a6aba",
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"share": "owner",
|
||||
"slug": "app3",
|
||||
"subdomain": false,
|
||||
"url": null
|
||||
},
|
||||
|
@ -107,7 +116,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "1281108380136021489",
|
||||
"id": "7676198272426781226",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.5.0"
|
||||
"version_constraint": "0.6.0"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
|
10
provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
generated
vendored
10
provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
generated
vendored
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "0bfa269a-e373-4fbc-929a-07b8ed0f3477",
|
||||
"id": "a7e62a9d-ef94-4abc-8bd5-e0555eae4aaf",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "4bc54f84-7d97-492a-ad98-40ae7dfbb300"
|
||||
"token": "812935fe-858a-4ff5-b890-6c8eea6a3764"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -34,7 +34,7 @@
|
|||
"values": {
|
||||
"hide": true,
|
||||
"icon": "/icon/server.svg",
|
||||
"id": "2ee6d253-dec1-4336-95ba-bd5e93cf4c84",
|
||||
"id": "5e954683-7a6d-47f4-bc82-5831c0ea2120",
|
||||
"item": [
|
||||
{
|
||||
"is_null": false,
|
||||
|
@ -61,7 +61,7 @@
|
|||
"value": "squirrel"
|
||||
}
|
||||
],
|
||||
"resource_id": "3043919679469754967"
|
||||
"resource_id": "288893601116381968"
|
||||
},
|
||||
"sensitive_values": {
|
||||
"item": [{}, {}, {}, {}]
|
||||
|
@ -76,7 +76,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "3043919679469754967",
|
||||
"id": "288893601116381968",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
|
|
|
@ -899,13 +899,16 @@ type App struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"`
|
||||
Subdomain bool `protobuf:"varint,5,opt,name=subdomain,proto3" json:"subdomain,omitempty"`
|
||||
Healthcheck *Healthcheck `protobuf:"bytes,6,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"`
|
||||
SharingLevel AppSharingLevel `protobuf:"varint,7,opt,name=sharing_level,json=sharingLevel,proto3,enum=provisioner.AppSharingLevel" json:"sharing_level,omitempty"`
|
||||
// slug is the unique identifier for the app, usually the name from the
|
||||
// template. It must be URL-safe and hostname-safe.
|
||||
Slug string `protobuf:"bytes,1,opt,name=slug,proto3" json:"slug,omitempty"`
|
||||
DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`
|
||||
Command string `protobuf:"bytes,3,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Icon string `protobuf:"bytes,5,opt,name=icon,proto3" json:"icon,omitempty"`
|
||||
Subdomain bool `protobuf:"varint,6,opt,name=subdomain,proto3" json:"subdomain,omitempty"`
|
||||
Healthcheck *Healthcheck `protobuf:"bytes,7,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"`
|
||||
SharingLevel AppSharingLevel `protobuf:"varint,8,opt,name=sharing_level,json=sharingLevel,proto3,enum=provisioner.AppSharingLevel" json:"sharing_level,omitempty"`
|
||||
}
|
||||
|
||||
func (x *App) Reset() {
|
||||
|
@ -940,9 +943,16 @@ func (*App) Descriptor() ([]byte, []int) {
|
|||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *App) GetName() string {
|
||||
func (x *App) GetSlug() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
return x.Slug
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *App) GetDisplayName() string {
|
||||
if x != nil {
|
||||
return x.DisplayName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -2009,148 +2019,150 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
|
|||
0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xf6,
|
||||
0x01, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
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, 0x1c, 0x0a, 0x09, 0x73, 0x75,
|
||||
0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73,
|
||||
0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c,
|
||||
0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c,
|
||||
0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63,
|
||||
0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f,
|
||||
0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74,
|
||||
0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f,
|
||||
0x6c, 0x64, 0x22, 0xad, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74,
|
||||
0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68,
|
||||
0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64,
|
||||
0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73,
|
||||
0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
||||
0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f,
|
||||
0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75,
|
||||
0x6c, 0x6c, 0x22, 0xfc, 0x01, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65,
|
||||
0x63, 0x74, 0x6f, 0x72, 0x79, 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,
|
||||
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x99,
|
||||
0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69,
|
||||
0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f,
|
||||
0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a,
|
||||
0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68,
|
||||
0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48,
|
||||
0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c,
|
||||
0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70,
|
||||
0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68,
|
||||
0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65,
|
||||
0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73,
|
||||
0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65,
|
||||
0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xad, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06,
|
||||
0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||
0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e,
|
||||
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||
0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65,
|
||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07,
|
||||
0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69,
|
||||
0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xfc, 0x01, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a,
|
||||
0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69,
|
||||
0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64,
|
||||
0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
|
||||
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70,
|
||||
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a,
|
||||
0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c,
|
||||
0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f,
|
||||
0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
|
||||
0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65,
|
||||
0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04,
|
||||
0x74, 0x79, 0x70, 0x65, 0x22, 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x1a, 0xd1, 0x02, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
|
||||
0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14,
|
||||
0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
||||
0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f,
|
||||
0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73,
|
||||
0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65,
|
||||
0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
||||
0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
|
||||
0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72,
|
||||
0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
|
||||
0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65,
|
||||
0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74,
|
||||
0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x46,
|
||||
0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||
0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
|
||||
0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79,
|
||||
0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52,
|
||||
0x75, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a,
|
||||
0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72,
|
||||
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
|
||||
0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37,
|
||||
0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52,
|
||||
0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a,
|
||||
0x6b, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73,
|
||||
0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
|
||||
0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x77, 0x0a, 0x08,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39,
|
||||
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, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50,
|
||||
0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52,
|
||||
0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70,
|
||||
0x65, 0x22, 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a,
|
||||
0xd1, 0x02, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72,
|
||||
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54,
|
||||
0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73,
|
||||
0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25,
|
||||
0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
|
||||
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
||||
0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
|
||||
0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49,
|
||||
0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f,
|
||||
0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12,
|
||||
0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e,
|
||||
0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13,
|
||||
0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d,
|
||||
0x61, 0x69, 0x6c, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1c, 0x0a,
|
||||
0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x70,
|
||||
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18,
|
||||
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65,
|
||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75,
|
||||
0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a,
|
||||
0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61,
|
||||
0x72, 0x74, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63,
|
||||
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61,
|
||||
0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0x6b, 0x0a, 0x08,
|
||||
0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
|
||||
0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09,
|
||||
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
|
||||
0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63,
|
||||
0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
|
||||
0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50,
|
||||
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a,
|
||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65,
|
||||
0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
|
||||
0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10,
|
||||
0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45,
|
||||
0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e,
|
||||
0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49,
|
||||
0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49,
|
||||
0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
|
||||
0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54,
|
||||
0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12,
|
||||
0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a,
|
||||
0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05,
|
||||
0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
|
||||
0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01,
|
||||
0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76,
|
||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00,
|
||||
0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79,
|
||||
0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09,
|
||||
0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42,
|
||||
0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f,
|
||||
0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e,
|
||||
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10,
|
||||
0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54,
|
||||
0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02,
|
||||
0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61,
|
||||
0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54,
|
||||
0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72,
|
||||
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72,
|
||||
0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
|
||||
0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72,
|
||||
0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a,
|
||||
0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42,
|
||||
0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f,
|
||||
0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 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 (
|
||||
|
|
|
@ -88,20 +88,23 @@ message Agent {
|
|||
}
|
||||
|
||||
enum AppSharingLevel {
|
||||
OWNER = 0;
|
||||
AUTHENTICATED = 1;
|
||||
PUBLIC = 2;
|
||||
OWNER = 0;
|
||||
AUTHENTICATED = 1;
|
||||
PUBLIC = 2;
|
||||
}
|
||||
|
||||
// App represents a dev-accessible application on the workspace.
|
||||
message App {
|
||||
string name = 1;
|
||||
string command = 2;
|
||||
string url = 3;
|
||||
string icon = 4;
|
||||
bool subdomain = 5;
|
||||
Healthcheck healthcheck = 6;
|
||||
AppSharingLevel sharing_level = 7;
|
||||
// slug is the unique identifier for the app, usually the name from the
|
||||
// template. It must be URL-safe and hostname-safe.
|
||||
string slug = 1;
|
||||
string display_name = 2;
|
||||
string command = 3;
|
||||
string url = 4;
|
||||
string icon = 5;
|
||||
bool subdomain = 6;
|
||||
Healthcheck healthcheck = 7;
|
||||
AppSharingLevel sharing_level = 8;
|
||||
}
|
||||
|
||||
// Healthcheck represents configuration for checking for app readiness.
|
||||
|
@ -125,7 +128,7 @@ message Resource {
|
|||
}
|
||||
repeated Metadata metadata = 4;
|
||||
bool hide = 5;
|
||||
string icon = 6;
|
||||
string icon = 6;
|
||||
}
|
||||
|
||||
// Parse consumes source-code from a directory to produce inputs.
|
||||
|
|
|
@ -823,7 +823,8 @@ export interface WorkspaceAgentResourceMetadata {
|
|||
// From codersdk/workspaceapps.go
|
||||
export interface WorkspaceApp {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly slug: string
|
||||
readonly display_name: string
|
||||
readonly command?: string
|
||||
readonly icon?: string
|
||||
readonly subdomain: boolean
|
||||
|
|
|
@ -31,18 +31,27 @@ export const AppLink: FC<AppLinkProps> = ({
|
|||
const styles = useStyles()
|
||||
const username = workspace.owner_name
|
||||
|
||||
let appSlug = app.slug
|
||||
let appDisplayName = app.display_name
|
||||
if (!appSlug) {
|
||||
appSlug = appDisplayName
|
||||
}
|
||||
if (!appDisplayName) {
|
||||
appDisplayName = appSlug
|
||||
}
|
||||
|
||||
// The backend redirects if the trailing slash isn't included, so we add it
|
||||
// here to avoid extra roundtrips.
|
||||
let href = `/@${username}/${workspace.name}.${
|
||||
agent.name
|
||||
}/apps/${encodeURIComponent(app.name)}/`
|
||||
}/apps/${encodeURIComponent(appSlug)}/`
|
||||
if (app.command) {
|
||||
href = `/@${username}/${workspace.name}.${
|
||||
agent.name
|
||||
}/terminal?command=${encodeURIComponent(app.command)}`
|
||||
}
|
||||
if (appsHost && app.subdomain) {
|
||||
const subdomain = `${app.name}--${agent.name}--${workspace.name}--${username}`
|
||||
const subdomain = `${appSlug}--${agent.name}--${workspace.name}--${username}`
|
||||
href = `${window.location.protocol}//${appsHost}/`.replace("*", subdomain)
|
||||
}
|
||||
|
||||
|
@ -75,7 +84,7 @@ export const AppLink: FC<AppLinkProps> = ({
|
|||
className={styles.button}
|
||||
disabled={!canClick}
|
||||
>
|
||||
<span className={styles.appName}>{app.name}</span>
|
||||
<span className={styles.appName}>{appDisplayName}</span>
|
||||
</Button>
|
||||
)
|
||||
|
||||
|
@ -92,7 +101,7 @@ export const AppLink: FC<AppLinkProps> = ({
|
|||
event.preventDefault()
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(app.name, generateRandomString(12)),
|
||||
Language.appTitle(appDisplayName, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
|
|||
spacing={1}
|
||||
>
|
||||
<BaseIcon app={app} />
|
||||
{app.name}
|
||||
{app.display_name}
|
||||
<ShareIcon app={app} />
|
||||
</Stack>
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ import ComputerIcon from "@material-ui/icons/Computer"
|
|||
|
||||
export const BaseIcon: FC<{ app: WorkspaceApp }> = ({ app }) => {
|
||||
return app.icon ? (
|
||||
<img alt={`${app.name} Icon`} src={app.icon} />
|
||||
<img alt={`${app.display_name} Icon`} src={app.icon} />
|
||||
) : (
|
||||
<ComputerIcon />
|
||||
)
|
||||
|
|
|
@ -74,7 +74,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||
<>
|
||||
{agent.apps.map((app) => (
|
||||
<AppLink
|
||||
key={app.name}
|
||||
key={app.slug}
|
||||
appsHost={applicationsHost}
|
||||
app={app}
|
||||
agent={agent}
|
||||
|
|
|
@ -73,7 +73,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({ agent }) => {
|
|||
wrap="wrap"
|
||||
>
|
||||
{agent.apps.map((app) => (
|
||||
<AppPreviewLink key={app.name} app={app} />
|
||||
<AppPreviewLink key={app.slug} app={app} />
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
|
|
@ -200,7 +200,8 @@ export const MockTemplate: TypesGen.Template = {
|
|||
|
||||
export const MockWorkspaceApp: TypesGen.WorkspaceApp = {
|
||||
id: "test-app",
|
||||
name: "test-app",
|
||||
slug: "test-app",
|
||||
display_name: "Test App",
|
||||
icon: "",
|
||||
subdomain: false,
|
||||
health: "disabled",
|
||||
|
|
Loading…
Reference in New Issue