diff --git a/agent/agent.go b/agent/agent.go index 27219c7ef0c1e..a971c0e7987b6 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1200,7 +1200,7 @@ func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(co network := a.network a.closeMutex.Unlock() if network == nil { - keySeed, err := SSHKeySeed(manifest.OwnerUsername, manifest.WorkspaceName, manifest.AgentName) + keySeed, err := SSHKeySeed(manifest.OwnerName, manifest.WorkspaceName, manifest.AgentName) if err != nil { return xerrors.Errorf("generate SSH key seed: %w", err) } diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index 9ab18e168e16a..a844a7e8c6258 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -1027,8 +1027,8 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { BytesPerTick: bytesPerTick, Duration: strategy.timeout, TickInterval: tickInterval, - ReadMetrics: metrics.ReadMetrics(ws.OwnerUsername, ws.Name, agent.Name), - WriteMetrics: metrics.WriteMetrics(ws.OwnerUsername, ws.Name, agent.Name), + ReadMetrics: metrics.ReadMetrics(ws.OwnerName, ws.Name, agent.Name), + WriteMetrics: metrics.WriteMetrics(ws.OwnerName, ws.Name, agent.Name), SSH: ssh, Echo: ssh, App: appConfig, @@ -1420,7 +1420,7 @@ func isScaleTestUser(user codersdk.User) bool { } func isScaleTestWorkspace(workspace codersdk.Workspace) bool { - return strings.HasPrefix(workspace.OwnerUsername, "scaletest-") || + return strings.HasPrefix(workspace.OwnerName, "scaletest-") || strings.HasPrefix(workspace.Name, "scaletest-") } @@ -1592,7 +1592,7 @@ func createWorkspaceAppConfig(client *codersdk.Client, appHost, app string, work c.URL = fmt.Sprintf("%s://%s", client.URL.Scheme, strings.Replace(appHost, "*", agent.Apps[i].SubdomainName, 1)) } else { - c.URL = fmt.Sprintf("%s/@%s/%s.%s/apps/%s", client.URL.String(), workspace.OwnerUsername, workspace.Name, agent.Name, agent.Apps[i].Slug) + c.URL = fmt.Sprintf("%s/@%s/%s.%s/apps/%s", client.URL.String(), workspace.OwnerName, workspace.Name, agent.Name, agent.Apps[i].Slug) } return c, nil diff --git a/cli/list.go b/cli/list.go index 7eb90b4b6b547..083d32c6e8fa1 100644 --- a/cli/list.go +++ b/cli/list.go @@ -54,7 +54,7 @@ func workspaceListRowFromWorkspace(now time.Time, workspace codersdk.Workspace) if workspace.Favorite { favIco = "★" } - workspaceName := favIco + " " + workspace.OwnerUsername + "/" + workspace.Name + workspaceName := favIco + " " + workspace.OwnerName + "/" + workspace.Name return workspaceListRow{ Favorite: workspace.Favorite, Workspace: workspace, diff --git a/cli/open.go b/cli/open.go index 1d47241564fa9..ff950b552a853 100644 --- a/cli/open.go +++ b/cli/open.go @@ -403,7 +403,7 @@ func buildVSCodeWorkspaceLink( ) (*url.URL, url.Values) { qp := url.Values{} qp.Add("url", clientURL) - qp.Add("owner", workspace.OwnerUsername) + qp.Add("owner", workspace.OwnerName) qp.Add("workspace", workspace.Name) qp.Add("agent", workspaceAgent.Name) @@ -435,7 +435,7 @@ func buildVSCodeWorkspaceDevContainerLink( qp := url.Values{} qp.Add("url", clientURL) - qp.Add("owner", workspace.OwnerUsername) + qp.Add("owner", workspace.OwnerName) qp.Add("workspace", workspace.Name) qp.Add("agent", workspaceAgent.Name) qp.Add("devContainerName", containerName) @@ -596,7 +596,7 @@ func buildAppLinkURL(baseURL *url.URL, workspace codersdk.Workspace, agent coder u.Path = fmt.Sprintf( "%s/@%s/%s.%s/apps/%s/", preferredPathBase, - workspace.OwnerUsername, + workspace.OwnerName, workspace.Name, agent.Name, url.PathEscape(app.Slug), @@ -606,7 +606,7 @@ func buildAppLinkURL(baseURL *url.URL, workspace codersdk.Workspace, agent coder u.Path = fmt.Sprintf( "%s/@%s/%s.%s/terminal", preferredPathBase, - workspace.OwnerUsername, + workspace.OwnerName, workspace.Name, agent.Name, ) diff --git a/cli/open_internal_test.go b/cli/open_internal_test.go index 7cee89ee78727..7af4359a56bc2 100644 --- a/cli/open_internal_test.go +++ b/cli/open_internal_test.go @@ -91,8 +91,8 @@ func Test_buildAppLinkURL(t *testing.T) { name: "without subdomain", baseURL: "https://coder.tld", workspace: codersdk.Workspace{ - Name: "Test-Workspace", - OwnerUsername: "username", + Name: "Test-Workspace", + OwnerName: "username", }, agent: codersdk.WorkspaceAgent{ Name: "a-workspace-agent", @@ -108,8 +108,8 @@ func Test_buildAppLinkURL(t *testing.T) { name: "with command", baseURL: "https://coder.tld", workspace: codersdk.Workspace{ - Name: "Test-Workspace", - OwnerUsername: "username", + Name: "Test-Workspace", + OwnerName: "username", }, agent: codersdk.WorkspaceAgent{ Name: "a-workspace-agent", @@ -123,8 +123,8 @@ func Test_buildAppLinkURL(t *testing.T) { name: "with subdomain", baseURL: "ftps://coder.tld", workspace: codersdk.Workspace{ - Name: "Test-Workspace", - OwnerUsername: "username", + Name: "Test-Workspace", + OwnerName: "username", }, agent: codersdk.WorkspaceAgent{ Name: "a-workspace-agent", @@ -141,8 +141,8 @@ func Test_buildAppLinkURL(t *testing.T) { name: "with subdomain, but not apps host", baseURL: "https://coder.tld", workspace: codersdk.Workspace{ - Name: "Test-Workspace", - OwnerUsername: "username", + Name: "Test-Workspace", + OwnerName: "username", }, agent: codersdk.WorkspaceAgent{ Name: "a-workspace-agent", diff --git a/cli/restart_test.go b/cli/restart_test.go index 46d3c7b675949..d69344435bf28 100644 --- a/cli/restart_test.go +++ b/cli/restart_test.go @@ -342,7 +342,7 @@ func TestRestartWithParameters(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) require.NoError(t, err) @@ -393,7 +393,7 @@ func TestRestartWithParameters(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) require.NoError(t, err) diff --git a/cli/schedule.go b/cli/schedule.go index 02445576f427f..9ade82b9c4a36 100644 --- a/cli/schedule.go +++ b/cli/schedule.go @@ -327,7 +327,7 @@ func scheduleListRowFromWorkspace(now time.Time, workspace codersdk.Workspace) s } } return scheduleListRow{ - WorkspaceName: workspace.OwnerUsername + "/" + workspace.Name, + WorkspaceName: workspace.OwnerName + "/" + workspace.Name, StartsAt: autostartDisplay, StartsNext: nextStartDisplay, StopsAfter: autostopDisplay, diff --git a/cli/schedule_test.go b/cli/schedule_test.go index e702c0a8d65de..60fbf19f4db08 100644 --- a/cli/schedule_test.go +++ b/cli/schedule_test.go @@ -71,8 +71,8 @@ func setupTestSchedule(t *testing.T, sched *cron.Schedule) (ownerClient, memberC // Ensure same order as in CLI output ws = resp.Workspaces sort.Slice(ws, func(i, j int) bool { - a := ws[i].OwnerUsername + "/" + ws[i].Name - b := ws[j].OwnerUsername + "/" + ws[j].Name + a := ws[i].OwnerName + "/" + ws[i].Name + b := ws[j].OwnerName + "/" + ws[j].Name return a < b }) @@ -102,13 +102,13 @@ func TestScheduleShow(t *testing.T) { // Then: they should see their own workspaces. // 1st workspace: a-owner-ws1 has both autostart and autostop enabled. - pty.ExpectMatch(ws[0].OwnerUsername + "/" + ws[0].Name) + pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) pty.ExpectMatch("8h") pty.ExpectMatch(ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: b-owner-ws2 has only autostart enabled. - pty.ExpectMatch(ws[1].OwnerUsername + "/" + ws[1].Name) + pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) }) @@ -123,21 +123,21 @@ func TestScheduleShow(t *testing.T) { // Then: they should see all workspaces // 1st workspace: a-owner-ws1 has both autostart and autostop enabled. - pty.ExpectMatch(ws[0].OwnerUsername + "/" + ws[0].Name) + pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) pty.ExpectMatch("8h") pty.ExpectMatch(ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: b-owner-ws2 has only autostart enabled. - pty.ExpectMatch(ws[1].OwnerUsername + "/" + ws[1].Name) + pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) // 3rd workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerUsername + "/" + ws[2].Name) + pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) pty.ExpectMatch("8h") pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 4th workspace: d-member-ws4 has neither autostart nor autostop enabled. - pty.ExpectMatch(ws[3].OwnerUsername + "/" + ws[3].Name) + pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) }) t.Run("OwnerSearchByName", func(t *testing.T) { @@ -150,14 +150,14 @@ func TestScheduleShow(t *testing.T) { // Then: they should see workspaces matching that query // 2nd workspace: b-owner-ws2 has only autostart enabled. - pty.ExpectMatch(ws[1].OwnerUsername + "/" + ws[1].Name) + pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) }) t.Run("OwnerOneArg", func(t *testing.T) { // When: owner asks for a specific workspace by name - inv, root := clitest.New(t, "schedule", "show", ws[2].OwnerUsername+"/"+ws[2].Name) + inv, root := clitest.New(t, "schedule", "show", ws[2].OwnerName+"/"+ws[2].Name) //nolint:gocritic // Testing that owner user sees all clitest.SetupConfig(t, ownerClient, root) pty := ptytest.New(t).Attach(inv) @@ -165,7 +165,7 @@ func TestScheduleShow(t *testing.T) { // Then: they should see that workspace // 3rd workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerUsername + "/" + ws[2].Name) + pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) pty.ExpectMatch("8h") pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) }) @@ -179,11 +179,11 @@ func TestScheduleShow(t *testing.T) { // Then: they should see their own workspaces // 1st workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerUsername + "/" + ws[2].Name) + pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) pty.ExpectMatch("8h") pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: d-member-ws4 has neither autostart nor autostop enabled. - pty.ExpectMatch(ws[3].OwnerUsername + "/" + ws[3].Name) + pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) }) t.Run("MemberAll", func(t *testing.T) { @@ -200,11 +200,11 @@ func TestScheduleShow(t *testing.T) { // Then: they should only see their own // 1st workspace: c-member-ws3 has only autostop enabled. - pty.ExpectMatch(ws[2].OwnerUsername + "/" + ws[2].Name) + pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) pty.ExpectMatch("8h") pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) // 2nd workspace: d-member-ws4 has neither autostart nor autostop enabled. - pty.ExpectMatch(ws[3].OwnerUsername + "/" + ws[3].Name) + pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) }) t.Run("JSON", func(t *testing.T) { @@ -231,25 +231,25 @@ func TestScheduleShow(t *testing.T) { return a < b }) // 1st workspace: a-owner-ws1 has both autostart and autostop enabled. - assert.Equal(t, ws[0].OwnerUsername+"/"+ws[0].Name, parsed[0]["workspace"]) + assert.Equal(t, ws[0].OwnerName+"/"+ws[0].Name, parsed[0]["workspace"]) assert.Equal(t, sched.Humanize(), parsed[0]["starts_at"]) assert.Equal(t, sched.Next(now).In(loc).Format(time.RFC3339), parsed[0]["starts_next"]) assert.Equal(t, "8h", parsed[0]["stops_after"]) assert.Equal(t, ws[0].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339), parsed[0]["stops_next"]) // 2nd workspace: b-owner-ws2 has only autostart enabled. - assert.Equal(t, ws[1].OwnerUsername+"/"+ws[1].Name, parsed[1]["workspace"]) + assert.Equal(t, ws[1].OwnerName+"/"+ws[1].Name, parsed[1]["workspace"]) assert.Equal(t, sched.Humanize(), parsed[1]["starts_at"]) assert.Equal(t, sched.Next(now).In(loc).Format(time.RFC3339), parsed[1]["starts_next"]) assert.Empty(t, parsed[1]["stops_after"]) assert.Empty(t, parsed[1]["stops_next"]) // 3rd workspace: c-member-ws3 has only autostop enabled. - assert.Equal(t, ws[2].OwnerUsername+"/"+ws[2].Name, parsed[2]["workspace"]) + assert.Equal(t, ws[2].OwnerName+"/"+ws[2].Name, parsed[2]["workspace"]) assert.Empty(t, parsed[2]["starts_at"]) assert.Empty(t, parsed[2]["starts_next"]) assert.Equal(t, "8h", parsed[2]["stops_after"]) assert.Equal(t, ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339), parsed[2]["stops_next"]) // 4th workspace: d-member-ws4 has neither autostart nor autostop enabled. - assert.Equal(t, ws[3].OwnerUsername+"/"+ws[3].Name, parsed[3]["workspace"]) + assert.Equal(t, ws[3].OwnerName+"/"+ws[3].Name, parsed[3]["workspace"]) assert.Empty(t, parsed[3]["starts_at"]) assert.Empty(t, parsed[3]["starts_next"]) assert.Empty(t, parsed[3]["stops_after"]) @@ -272,7 +272,7 @@ func TestScheduleModify(t *testing.T) { t.Run("SetStart", func(t *testing.T) { // When: we set the start schedule inv, root := clitest.New(t, - "schedule", "start", ws[3].OwnerUsername+"/"+ws[3].Name, "7:30AM", "Mon-Fri", "Europe/Dublin", + "schedule", "start", ws[3].OwnerName+"/"+ws[3].Name, "7:30AM", "Mon-Fri", "Europe/Dublin", ) //nolint:gocritic // this workspace is not owned by the same user clitest.SetupConfig(t, ownerClient, root) @@ -280,7 +280,7 @@ func TestScheduleModify(t *testing.T) { require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[3].OwnerUsername + "/" + ws[3].Name) + pty.ExpectMatch(ws[3].OwnerName + "/" + ws[3].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) }) @@ -288,7 +288,7 @@ func TestScheduleModify(t *testing.T) { t.Run("SetStop", func(t *testing.T) { // When: we set the stop schedule inv, root := clitest.New(t, - "schedule", "stop", ws[2].OwnerUsername+"/"+ws[2].Name, "8h30m", + "schedule", "stop", ws[2].OwnerName+"/"+ws[2].Name, "8h30m", ) //nolint:gocritic // this workspace is not owned by the same user clitest.SetupConfig(t, ownerClient, root) @@ -296,7 +296,7 @@ func TestScheduleModify(t *testing.T) { require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[2].OwnerUsername + "/" + ws[2].Name) + pty.ExpectMatch(ws[2].OwnerName + "/" + ws[2].Name) pty.ExpectMatch("8h30m") pty.ExpectMatch(ws[2].LatestBuild.Deadline.Time.In(loc).Format(time.RFC3339)) }) @@ -304,7 +304,7 @@ func TestScheduleModify(t *testing.T) { t.Run("UnsetStart", func(t *testing.T) { // When: we unset the start schedule inv, root := clitest.New(t, - "schedule", "start", ws[1].OwnerUsername+"/"+ws[1].Name, "manual", + "schedule", "start", ws[1].OwnerName+"/"+ws[1].Name, "manual", ) //nolint:gocritic // this workspace is owned by owner clitest.SetupConfig(t, ownerClient, root) @@ -312,13 +312,13 @@ func TestScheduleModify(t *testing.T) { require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[1].OwnerUsername + "/" + ws[1].Name) + pty.ExpectMatch(ws[1].OwnerName + "/" + ws[1].Name) }) t.Run("UnsetStop", func(t *testing.T) { // When: we unset the stop schedule inv, root := clitest.New(t, - "schedule", "stop", ws[0].OwnerUsername+"/"+ws[0].Name, "manual", + "schedule", "stop", ws[0].OwnerName+"/"+ws[0].Name, "manual", ) //nolint:gocritic // this workspace is owned by owner clitest.SetupConfig(t, ownerClient, root) @@ -326,7 +326,7 @@ func TestScheduleModify(t *testing.T) { require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[0].OwnerUsername + "/" + ws[0].Name) + pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) }) } @@ -359,7 +359,7 @@ func TestScheduleOverride(t *testing.T) { // When: we override the stop schedule inv, root := clitest.New(t, - "schedule", tt.command, ws[0].OwnerUsername+"/"+ws[0].Name, "10h", + "schedule", tt.command, ws[0].OwnerName+"/"+ws[0].Name, "10h", ) clitest.SetupConfig(t, ownerClient, root) @@ -367,7 +367,7 @@ func TestScheduleOverride(t *testing.T) { require.NoError(t, inv.Run()) // Then: the updated schedule should be shown - pty.ExpectMatch(ws[0].OwnerUsername + "/" + ws[0].Name) + pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name) pty.ExpectMatch(sched.Humanize()) pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339)) pty.ExpectMatch("8h") diff --git a/cli/ssh.go b/cli/ssh.go index f16cbba9316b7..51f53e10bcbd2 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -305,7 +305,7 @@ func (r *RootCmd) ssh() *serpent.Command { return xerrors.Errorf("get agent connection info: %w", err) } coderConnectHost := fmt.Sprintf("%s.%s.%s.%s", - workspaceAgent.Name, workspace.Name, workspace.OwnerUsername, connInfo.HostnameSuffix) + workspaceAgent.Name, workspace.Name, workspace.OwnerName, connInfo.HostnameSuffix) exists, _ := workspacesdk.ExistsViaCoderConnect(ctx, coderConnectHost) if exists { defer cancel() @@ -1022,7 +1022,7 @@ func verifyWorkspaceOutdated(client *codersdk.Client, workspace codersdk.Workspa // Build the user workspace link which navigates to the Coder web UI. func buildWorkspaceLink(serverURL *url.URL, workspace codersdk.Workspace) *url.URL { - return serverURL.ResolveReference(&url.URL{Path: fmt.Sprintf("@%s/%s", workspace.OwnerUsername, workspace.Name)}) + return serverURL.ResolveReference(&url.URL{Path: fmt.Sprintf("@%s/%s", workspace.OwnerName, workspace.Name)}) } // runLocal runs a command on the local machine. diff --git a/cli/ssh_internal_test.go b/cli/ssh_internal_test.go index c445d4fadf44b..003bc697a4052 100644 --- a/cli/ssh_internal_test.go +++ b/cli/ssh_internal_test.go @@ -25,7 +25,7 @@ import ( ) const ( - fakeOwnerUsername = "fake-owner-name" + fakeOwnerName = "fake-owner-name" fakeServerURL = "https://fake-foo-url" fakeWorkspaceName = "fake-workspace-name" ) @@ -41,7 +41,7 @@ func TestVerifyWorkspaceOutdated(t *testing.T) { t.Run("Up-to-date", func(t *testing.T) { t.Parallel() - workspace := codersdk.Workspace{Name: fakeWorkspaceName, OwnerUsername: fakeOwnerUsername} + workspace := codersdk.Workspace{Name: fakeWorkspaceName, OwnerName: fakeOwnerName} _, outdated := verifyWorkspaceOutdated(&client, workspace) @@ -50,7 +50,7 @@ func TestVerifyWorkspaceOutdated(t *testing.T) { t.Run("Outdated", func(t *testing.T) { t.Parallel() - workspace := codersdk.Workspace{Name: fakeWorkspaceName, OwnerUsername: fakeOwnerUsername, Outdated: true} + workspace := codersdk.Workspace{Name: fakeWorkspaceName, OwnerName: fakeOwnerName, Outdated: true} updateWorkspaceBanner, outdated := verifyWorkspaceOutdated(&client, workspace) @@ -65,10 +65,10 @@ func TestBuildWorkspaceLink(t *testing.T) { serverURL, err := url.Parse(fakeServerURL) require.NoError(t, err) - workspace := codersdk.Workspace{Name: fakeWorkspaceName, OwnerUsername: fakeOwnerUsername} + workspace := codersdk.Workspace{Name: fakeWorkspaceName, OwnerName: fakeOwnerName} workspaceLink := buildWorkspaceLink(serverURL, workspace) - assert.Equal(t, workspaceLink.String(), fakeServerURL+"/@"+fakeOwnerUsername+"/"+fakeWorkspaceName) + assert.Equal(t, workspaceLink.String(), fakeServerURL+"/@"+fakeOwnerName+"/"+fakeWorkspaceName) } func TestCloserStack_Mainline(t *testing.T) { diff --git a/cli/start_test.go b/cli/start_test.go index d15dcbd356711..29fa4cdb46e5f 100644 --- a/cli/start_test.go +++ b/cli/start_test.go @@ -148,7 +148,7 @@ func TestStart(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) require.NoError(t, err) @@ -191,7 +191,7 @@ func TestStart(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) require.NoError(t, err) @@ -247,7 +247,7 @@ func TestStartWithParameters(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) require.NoError(t, err) @@ -302,7 +302,7 @@ func TestStartWithParameters(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID) require.NoError(t, err) diff --git a/cli/testdata/coder_list_--output_json.golden b/cli/testdata/coder_list_--output_json.golden index 80180c6262b21..d8e6a306cabcf 100644 --- a/cli/testdata/coder_list_--output_json.golden +++ b/cli/testdata/coder_list_--output_json.golden @@ -4,7 +4,7 @@ "created_at": "====[timestamp]=====", "updated_at": "====[timestamp]=====", "owner_id": "==========[first user ID]===========", - "owner_username": "testuser", + "owner_name": "testuser", "owner_avatar_url": "", "organization_id": "===========[first org ID]===========", "organization_name": "coder", diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 0cc518d09399f..5e8b8d6afa89e 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -16095,6 +16095,23 @@ const docTemplate = `{ "ephemeral": { "type": "boolean" }, + "form_type": { + "description": "FormType has an enum value of empty string, ` + "`" + `\"\"` + "`" + `.\nKeep the leading comma in the enums struct tag.", + "type": "string", + "enum": [ + "", + "radio", + "dropdown", + "input", + "textarea", + "slider", + "checkbox", + "switch", + "tag-select", + "multi-select", + "error" + ] + }, "icon": { "type": "string" }, @@ -17002,9 +17019,7 @@ const docTemplate = `{ "format": "uuid" }, "owner_name": { - "type": "string" - }, - "owner_username": { + "description": "OwnerName is the username of the owner of the workspace.", "type": "string" }, "template_active_version_id": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 0fdc08d22e228..ef32dcd24f375 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -14662,6 +14662,23 @@ "ephemeral": { "type": "boolean" }, + "form_type": { + "description": "FormType has an enum value of empty string, `\"\"`.\nKeep the leading comma in the enums struct tag.", + "type": "string", + "enum": [ + "", + "radio", + "dropdown", + "input", + "textarea", + "slider", + "checkbox", + "switch", + "tag-select", + "multi-select", + "error" + ] + }, "icon": { "type": "string" }, @@ -15507,9 +15524,7 @@ "format": "uuid" }, "owner_name": { - "type": "string" - }, - "owner_username": { + "description": "OwnerName is the username of the owner of the workspace.", "type": "string" }, "template_active_version_id": { diff --git a/coderd/audit.go b/coderd/audit.go index ee647fba2f39b..63b6e49ebb05a 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -462,7 +462,7 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit if getWorkspaceErr != nil { return "" } - return fmt.Sprintf("/@%s/%s", workspace.OwnerUsername, workspace.Name) + return fmt.Sprintf("/@%s/%s", workspace.OwnerName, workspace.Name) case database.ResourceTypeWorkspaceApp: if additionalFields.WorkspaceOwner != "" && additionalFields.WorkspaceName != "" { @@ -472,7 +472,7 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit if getWorkspaceErr != nil { return "" } - return fmt.Sprintf("/@%s/%s", workspace.OwnerUsername, workspace.Name) + return fmt.Sprintf("/@%s/%s", workspace.OwnerName, workspace.Name) case database.ResourceTypeOauth2ProviderApp: return fmt.Sprintf("/deployment/oauth2-provider/apps/%s", alog.AuditLog.ResourceID) diff --git a/coderd/audit_test.go b/coderd/audit_test.go index c61ded694171d..18bcd78b38807 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -139,7 +139,7 @@ func TestAuditLogs(t *testing.T) { require.NoError(t, err) buildNumberString := strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10) require.Equal(t, auditLogs.AuditLogs[0].ResourceLink, fmt.Sprintf("/@%s/%s/builds/%s", - workspace.OwnerUsername, workspace.Name, buildNumberString)) + workspace.OwnerName, workspace.Name, buildNumberString)) }) t.Run("Organization", func(t *testing.T) { diff --git a/coderd/coderd.go b/coderd/coderd.go index 37e7d22a6d080..0aab4b26262ea 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1532,17 +1532,19 @@ func New(options *Options) *API { // Add CSP headers to all static assets and pages. CSP headers only affect // browsers, so these don't make sense on api routes. - cspMW := httpmw.CSPHeaders(options.Telemetry.Enabled(), func() []string { - if api.DeploymentValues.Dangerous.AllowAllCors { - // In this mode, allow all external requests - return []string{"*"} - } - if f := api.WorkspaceProxyHostsFn.Load(); f != nil { - return (*f)() - } - // By default we do not add extra websocket connections to the CSP - return []string{} - }, additionalCSPHeaders) + cspMW := httpmw.CSPHeaders( + api.Experiments, + options.Telemetry.Enabled(), func() []string { + if api.DeploymentValues.Dangerous.AllowAllCors { + // In this mode, allow all external requests + return []string{"*"} + } + if f := api.WorkspaceProxyHostsFn.Load(); f != nil { + return (*f)() + } + // By default we do not add extra websocket connections to the CSP + return []string{} + }, additionalCSPHeaders) // Static file handler must be wrapped with HSTS handler if the // StrictTransportSecurityAge is set. We only need to set this header on diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index f30cec2c84830..40b1423a0f730 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -92,13 +92,13 @@ func WorkspaceBuildParameters(params []database.WorkspaceBuildParameter) []coder } func TemplateVersionParameters(params []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) { - out := make([]codersdk.TemplateVersionParameter, len(params)) - var err error - for i, p := range params { - out[i], err = TemplateVersionParameter(p) + out := make([]codersdk.TemplateVersionParameter, 0, len(params)) + for _, p := range params { + np, err := TemplateVersionParameter(p) if err != nil { return nil, xerrors.Errorf("convert template version parameter %q: %w", p.Name, err) } + out = append(out, np) } return out, nil @@ -131,6 +131,7 @@ func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk Description: param.Description, DescriptionPlaintext: descriptionPlaintext, Type: param.Type, + FormType: string(param.FormType), Mutable: param.Mutable, DefaultValue: param.DefaultValue, Icon: param.Icon, @@ -293,7 +294,8 @@ func templateVersionParameterOptions(rawOptions json.RawMessage) ([]codersdk.Tem if err != nil { return nil, err } - var options []codersdk.TemplateVersionParameterOption + + options := make([]codersdk.TemplateVersionParameterOption, 0) for _, option := range protoOptions { options = append(options, codersdk.TemplateVersionParameterOption{ Name: option.Name, diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 8a6f312e650a8..c85db83a2adc9 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -992,6 +992,7 @@ func TemplateVersionParameter(t testing.TB, db database.Store, orig database.Tem Name: takeFirst(orig.Name, testutil.GetRandomName(t)), Description: takeFirst(orig.Description, testutil.GetRandomName(t)), Type: takeFirst(orig.Type, "string"), + FormType: orig.FormType, // empty string is ok! Mutable: takeFirst(orig.Mutable, false), DefaultValue: takeFirst(orig.DefaultValue, testutil.GetRandomName(t)), Icon: takeFirst(orig.Icon, testutil.GetRandomName(t)), diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 0833fa1677c3b..69bb3a540eccf 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9393,6 +9393,7 @@ func (q *FakeQuerier) InsertTemplateVersionParameter(_ context.Context, arg data DisplayName: arg.DisplayName, Description: arg.Description, Type: arg.Type, + FormType: arg.FormType, Mutable: arg.Mutable, DefaultValue: arg.DefaultValue, Icon: arg.Icon, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index acb9780b82ea6..22a0b3d5a8adc 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -132,6 +132,22 @@ CREATE TYPE parameter_destination_scheme AS ENUM ( 'provisioner_variable' ); +CREATE TYPE parameter_form_type AS ENUM ( + '', + 'error', + 'radio', + 'dropdown', + 'input', + 'textarea', + 'slider', + 'checkbox', + 'switch', + 'tag-select', + 'multi-select' +); + +COMMENT ON TYPE parameter_form_type IS 'Enum set should match the terraform provider set. This is defined as future form_types are not supported, and should be rejected. Always include the empty string for using the default form type.'; + CREATE TYPE parameter_scope AS ENUM ( 'template', 'import_job', @@ -1434,6 +1450,7 @@ CREATE TABLE template_version_parameters ( display_name text DEFAULT ''::text NOT NULL, display_order integer DEFAULT 0 NOT NULL, ephemeral boolean DEFAULT false NOT NULL, + form_type parameter_form_type DEFAULT ''::parameter_form_type NOT NULL, CONSTRAINT validation_monotonic_order CHECK ((validation_monotonic = ANY (ARRAY['increasing'::text, 'decreasing'::text, ''::text]))) ); @@ -1469,6 +1486,8 @@ COMMENT ON COLUMN template_version_parameters.display_order IS 'Specifies the or COMMENT ON COLUMN template_version_parameters.ephemeral IS 'The value of an ephemeral parameter will not be preserved between consecutive workspace builds.'; +COMMENT ON COLUMN template_version_parameters.form_type IS 'Specify what form_type should be used to render the parameter in the UI. Unsupported values are rejected.'; + CREATE TABLE template_version_preset_parameters ( id uuid DEFAULT gen_random_uuid() NOT NULL, template_version_preset_id uuid NOT NULL, diff --git a/coderd/database/migrations/000333_parameter_form_type.down.sql b/coderd/database/migrations/000333_parameter_form_type.down.sql new file mode 100644 index 0000000000000..906d9c0cba610 --- /dev/null +++ b/coderd/database/migrations/000333_parameter_form_type.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE template_version_parameters DROP COLUMN form_type; +DROP TYPE parameter_form_type; diff --git a/coderd/database/migrations/000333_parameter_form_type.up.sql b/coderd/database/migrations/000333_parameter_form_type.up.sql new file mode 100644 index 0000000000000..fce755eb5193e --- /dev/null +++ b/coderd/database/migrations/000333_parameter_form_type.up.sql @@ -0,0 +1,11 @@ +CREATE TYPE parameter_form_type AS ENUM ('', 'error', 'radio', 'dropdown', 'input', 'textarea', 'slider', 'checkbox', 'switch', 'tag-select', 'multi-select'); +COMMENT ON TYPE parameter_form_type + IS 'Enum set should match the terraform provider set. This is defined as future form_types are not supported, and should be rejected. ' + 'Always include the empty string for using the default form type.'; + +-- Intentionally leaving the default blank. The provisioner will not re-run any +-- imports to backfill these values. Missing values just have to be handled. +ALTER TABLE template_version_parameters ADD COLUMN form_type parameter_form_type NOT NULL DEFAULT ''; + +COMMENT ON COLUMN template_version_parameters.form_type + IS 'Specify what form_type should be used to render the parameter in the UI. Unsupported values are rejected.'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 367c46b5b71d7..69ae70b6c3bd3 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1108,6 +1108,92 @@ func AllParameterDestinationSchemeValues() []ParameterDestinationScheme { } } +// Enum set should match the terraform provider set. This is defined as future form_types are not supported, and should be rejected. Always include the empty string for using the default form type. +type ParameterFormType string + +const ( + ParameterFormTypeValue0 ParameterFormType = "" + ParameterFormTypeError ParameterFormType = "error" + ParameterFormTypeRadio ParameterFormType = "radio" + ParameterFormTypeDropdown ParameterFormType = "dropdown" + ParameterFormTypeInput ParameterFormType = "input" + ParameterFormTypeTextarea ParameterFormType = "textarea" + ParameterFormTypeSlider ParameterFormType = "slider" + ParameterFormTypeCheckbox ParameterFormType = "checkbox" + ParameterFormTypeSwitch ParameterFormType = "switch" + ParameterFormTypeTagSelect ParameterFormType = "tag-select" + ParameterFormTypeMultiSelect ParameterFormType = "multi-select" +) + +func (e *ParameterFormType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = ParameterFormType(s) + case string: + *e = ParameterFormType(s) + default: + return fmt.Errorf("unsupported scan type for ParameterFormType: %T", src) + } + return nil +} + +type NullParameterFormType struct { + ParameterFormType ParameterFormType `json:"parameter_form_type"` + Valid bool `json:"valid"` // Valid is true if ParameterFormType is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullParameterFormType) Scan(value interface{}) error { + if value == nil { + ns.ParameterFormType, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.ParameterFormType.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullParameterFormType) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.ParameterFormType), nil +} + +func (e ParameterFormType) Valid() bool { + switch e { + case ParameterFormTypeValue0, + ParameterFormTypeError, + ParameterFormTypeRadio, + ParameterFormTypeDropdown, + ParameterFormTypeInput, + ParameterFormTypeTextarea, + ParameterFormTypeSlider, + ParameterFormTypeCheckbox, + ParameterFormTypeSwitch, + ParameterFormTypeTagSelect, + ParameterFormTypeMultiSelect: + return true + } + return false +} + +func AllParameterFormTypeValues() []ParameterFormType { + return []ParameterFormType{ + ParameterFormTypeValue0, + ParameterFormTypeError, + ParameterFormTypeRadio, + ParameterFormTypeDropdown, + ParameterFormTypeInput, + ParameterFormTypeTextarea, + ParameterFormTypeSlider, + ParameterFormTypeCheckbox, + ParameterFormTypeSwitch, + ParameterFormTypeTagSelect, + ParameterFormTypeMultiSelect, + } +} + type ParameterScope string const ( @@ -3308,6 +3394,8 @@ type TemplateVersionParameter struct { DisplayOrder int32 `db:"display_order" json:"display_order"` // The value of an ephemeral parameter will not be preserved between consecutive workspace builds. Ephemeral bool `db:"ephemeral" json:"ephemeral"` + // Specify what form_type should be used to render the parameter in the UI. Unsupported values are rejected. + FormType ParameterFormType `db:"form_type" json:"form_type"` } type TemplateVersionPreset struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ed8551691efd6..eec91c7586d61 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11178,7 +11178,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT } const getTemplateVersionParameters = `-- name: GetTemplateVersionParameters :many -SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral FROM template_version_parameters WHERE template_version_id = $1 ORDER BY display_order ASC, LOWER(name) ASC +SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral, form_type FROM template_version_parameters WHERE template_version_id = $1 ORDER BY display_order ASC, LOWER(name) ASC ` func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) { @@ -11208,6 +11208,7 @@ func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateV &i.DisplayName, &i.DisplayOrder, &i.Ephemeral, + &i.FormType, ); err != nil { return nil, err } @@ -11229,6 +11230,7 @@ INSERT INTO name, description, type, + form_type, mutable, default_value, icon, @@ -11261,28 +11263,30 @@ VALUES $14, $15, $16, - $17 - ) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral + $17, + $18 + ) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral, form_type ` type InsertTemplateVersionParameterParams struct { - TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` - Name string `db:"name" json:"name"` - Description string `db:"description" json:"description"` - Type string `db:"type" json:"type"` - Mutable bool `db:"mutable" json:"mutable"` - DefaultValue string `db:"default_value" json:"default_value"` - Icon string `db:"icon" json:"icon"` - Options json.RawMessage `db:"options" json:"options"` - ValidationRegex string `db:"validation_regex" json:"validation_regex"` - ValidationMin sql.NullInt32 `db:"validation_min" json:"validation_min"` - ValidationMax sql.NullInt32 `db:"validation_max" json:"validation_max"` - ValidationError string `db:"validation_error" json:"validation_error"` - ValidationMonotonic string `db:"validation_monotonic" json:"validation_monotonic"` - Required bool `db:"required" json:"required"` - DisplayName string `db:"display_name" json:"display_name"` - DisplayOrder int32 `db:"display_order" json:"display_order"` - Ephemeral bool `db:"ephemeral" json:"ephemeral"` + TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` + Name string `db:"name" json:"name"` + Description string `db:"description" json:"description"` + Type string `db:"type" json:"type"` + FormType ParameterFormType `db:"form_type" json:"form_type"` + Mutable bool `db:"mutable" json:"mutable"` + DefaultValue string `db:"default_value" json:"default_value"` + Icon string `db:"icon" json:"icon"` + Options json.RawMessage `db:"options" json:"options"` + ValidationRegex string `db:"validation_regex" json:"validation_regex"` + ValidationMin sql.NullInt32 `db:"validation_min" json:"validation_min"` + ValidationMax sql.NullInt32 `db:"validation_max" json:"validation_max"` + ValidationError string `db:"validation_error" json:"validation_error"` + ValidationMonotonic string `db:"validation_monotonic" json:"validation_monotonic"` + Required bool `db:"required" json:"required"` + DisplayName string `db:"display_name" json:"display_name"` + DisplayOrder int32 `db:"display_order" json:"display_order"` + Ephemeral bool `db:"ephemeral" json:"ephemeral"` } func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error) { @@ -11291,6 +11295,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins arg.Name, arg.Description, arg.Type, + arg.FormType, arg.Mutable, arg.DefaultValue, arg.Icon, @@ -11324,6 +11329,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins &i.DisplayName, &i.DisplayOrder, &i.Ephemeral, + &i.FormType, ) return i, err } diff --git a/coderd/database/queries/templateversionparameters.sql b/coderd/database/queries/templateversionparameters.sql index 039070b8a3515..549d9eafa1899 100644 --- a/coderd/database/queries/templateversionparameters.sql +++ b/coderd/database/queries/templateversionparameters.sql @@ -5,6 +5,7 @@ INSERT INTO name, description, type, + form_type, mutable, default_value, icon, @@ -37,7 +38,8 @@ VALUES $14, $15, $16, - $17 + $17, + $18 ) RETURNING *; -- name: GetTemplateVersionParameters :many diff --git a/coderd/httpmw/csp.go b/coderd/httpmw/csp.go index e6864b7448c41..afc19ddaf0c1f 100644 --- a/coderd/httpmw/csp.go +++ b/coderd/httpmw/csp.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/coder/coder/v2/codersdk" ) // cspDirectives is a map of all csp fetch directives to their values. @@ -37,6 +39,7 @@ const ( CSPDirectiveFormAction CSPFetchDirective = "form-action" CSPDirectiveMediaSrc CSPFetchDirective = "media-src" CSPFrameAncestors CSPFetchDirective = "frame-ancestors" + CSPFrameSource CSPFetchDirective = "frame-src" CSPDirectiveWorkerSrc CSPFetchDirective = "worker-src" ) @@ -55,7 +58,7 @@ const ( // Example: https://github.com/coder/coder/issues/15118 // //nolint:revive -func CSPHeaders(telemetry bool, websocketHosts func() []string, staticAdditions map[CSPFetchDirective][]string) func(next http.Handler) http.Handler { +func CSPHeaders(experiments codersdk.Experiments, telemetry bool, websocketHosts func() []string, staticAdditions map[CSPFetchDirective][]string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Content-Security-Policy disables loading certain content types and can prevent XSS injections. @@ -88,13 +91,21 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string, staticAdditions CSPDirectiveMediaSrc: {"'self'"}, // Report all violations back to the server to log CSPDirectiveReportURI: {"/api/v2/csp/reports"}, - CSPFrameAncestors: {"'none'"}, // Only scripts can manipulate the dom. This prevents someone from // naming themselves something like ''. // "require-trusted-types-for" : []string{"'script'"}, } + if experiments.Enabled(codersdk.ExperimentAITasks) { + // AI tasks use iframe embeds of local apps. + // TODO: Handle region domains too, not just path based apps + cspSrcs.Append(CSPFrameAncestors, `'self'`) + cspSrcs.Append(CSPFrameSource, `'self'`) + } else { + cspSrcs.Append(CSPFrameAncestors, `'none'`) + } + if telemetry { // If telemetry is enabled, we report to coder.com. cspSrcs.Append(CSPDirectiveConnectSrc, "https://coder.com") diff --git a/coderd/httpmw/csp_test.go b/coderd/httpmw/csp_test.go index c5000d3a29370..bef6ab196eb6e 100644 --- a/coderd/httpmw/csp_test.go +++ b/coderd/httpmw/csp_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/codersdk" ) func TestCSPConnect(t *testing.T) { @@ -20,7 +21,7 @@ func TestCSPConnect(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) rw := httptest.NewRecorder() - httpmw.CSPHeaders(false, func() []string { + httpmw.CSPHeaders(codersdk.Experiments{}, false, func() []string { return expected }, map[httpmw.CSPFetchDirective][]string{ httpmw.CSPDirectiveMediaSrc: expectedMedia, diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index b220cae90b629..111f4185d3910 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -28,6 +28,7 @@ import ( protobuf "google.golang.org/protobuf/proto" "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk/drpcsdk" @@ -1453,12 +1454,24 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro } } + pft, err := sdkproto.ProviderFormType(richParameter.FormType) + if err != nil { + return xerrors.Errorf("parameter %q: %w", richParameter.Name, err) + } + + dft := database.ParameterFormType(pft) + if !dft.Valid() { + list := strings.Join(slice.ToStrings(database.AllParameterFormTypeValues()), ", ") + return xerrors.Errorf("parameter %q field 'form_type' not valid, currently supported: %s", richParameter.Name, list) + } + _, err = db.InsertTemplateVersionParameter(ctx, database.InsertTemplateVersionParameterParams{ TemplateVersionID: input.TemplateVersionID, Name: richParameter.Name, DisplayName: richParameter.DisplayName, Description: richParameter.Description, Type: richParameter.Type, + FormType: dft, Mutable: richParameter.Mutable, DefaultValue: richParameter.DefaultValue, Icon: richParameter.Icon, diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 087f6aa21a5d7..1756aa68e15fc 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -1384,6 +1384,60 @@ func TestCompleteJob(t *testing.T) { }) }) + t.Run("WorkspaceBuild_BadFormType", func(t *testing.T) { + t.Parallel() + srv, db, _, pd := setup(t, false, &overrides{}) + jobID := uuid.New() + versionID := uuid.New() + err := db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{ + ID: versionID, + JobID: jobID, + OrganizationID: pd.OrganizationID, + }) + require.NoError(t, err) + job, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ + ID: jobID, + Provisioner: database.ProvisionerTypeEcho, + Input: []byte(`{"template_version_id": "` + versionID.String() + `"}`), + StorageMethod: database.ProvisionerStorageMethodFile, + Type: database.ProvisionerJobTypeWorkspaceBuild, + OrganizationID: pd.OrganizationID, + }) + require.NoError(t, err) + _, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ + OrganizationID: pd.OrganizationID, + WorkerID: uuid.NullUUID{ + UUID: pd.ID, + Valid: true, + }, + Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, + }) + require.NoError(t, err) + + _, err = srv.CompleteJob(ctx, &proto.CompletedJob{ + JobId: job.ID.String(), + Type: &proto.CompletedJob_TemplateImport_{ + TemplateImport: &proto.CompletedJob_TemplateImport{ + StartResources: []*sdkproto.Resource{{ + Name: "hello", + Type: "aws_instance", + }}, + StopResources: []*sdkproto.Resource{}, + RichParameters: []*sdkproto.RichParameter{ + { + Name: "parameter", + Type: "string", + FormType: -1, + }, + }, + Plan: []byte("{}"), + }, + }, + }) + require.Error(t, err) + require.ErrorContains(t, err, "unsupported form type") + }) + t.Run("TemplateImport_MissingGitAuth", func(t *testing.T) { t.Parallel() srv, db, _, pd := setup(t, false, &overrides{}) diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 8dd523374d69f..d79f86f1f6626 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -31,14 +31,12 @@ import ( "github.com/coder/coder/v2/coderd/provisionerdserver" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" - "github.com/coder/coder/v2/coderd/render" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/examples" "github.com/coder/coder/v2/provisioner/terraform/tfparse" "github.com/coder/coder/v2/provisionersdk" - sdkproto "github.com/coder/coder/v2/provisionersdk/proto" ) // @Summary Get template version by ID @@ -307,7 +305,7 @@ func (api *API) templateVersionRichParameters(rw http.ResponseWriter, r *http.Re return } - templateVersionParameters, err := convertTemplateVersionParameters(dbTemplateVersionParameters) + templateVersionParameters, err := db2sdk.TemplateVersionParameters(dbTemplateVersionParameters) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting template version parameter.", @@ -1869,67 +1867,6 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi } } -func convertTemplateVersionParameters(dbParams []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) { - params := make([]codersdk.TemplateVersionParameter, 0) - for _, dbParameter := range dbParams { - param, err := convertTemplateVersionParameter(dbParameter) - if err != nil { - return nil, err - } - params = append(params, param) - } - return params, nil -} - -func convertTemplateVersionParameter(param database.TemplateVersionParameter) (codersdk.TemplateVersionParameter, error) { - var protoOptions []*sdkproto.RichParameterOption - err := json.Unmarshal(param.Options, &protoOptions) - if err != nil { - return codersdk.TemplateVersionParameter{}, err - } - options := make([]codersdk.TemplateVersionParameterOption, 0) - for _, option := range protoOptions { - options = append(options, codersdk.TemplateVersionParameterOption{ - Name: option.Name, - Description: option.Description, - Value: option.Value, - Icon: option.Icon, - }) - } - - descriptionPlaintext, err := render.PlaintextFromMarkdown(param.Description) - if err != nil { - return codersdk.TemplateVersionParameter{}, err - } - - var validationMin, validationMax *int32 - if param.ValidationMin.Valid { - validationMin = ¶m.ValidationMin.Int32 - } - if param.ValidationMax.Valid { - validationMax = ¶m.ValidationMax.Int32 - } - - return codersdk.TemplateVersionParameter{ - Name: param.Name, - DisplayName: param.DisplayName, - Description: param.Description, - DescriptionPlaintext: descriptionPlaintext, - Type: param.Type, - Mutable: param.Mutable, - DefaultValue: param.DefaultValue, - Icon: param.Icon, - Options: options, - ValidationRegex: param.ValidationRegex, - ValidationMin: validationMin, - ValidationMax: validationMax, - ValidationError: param.ValidationError, - ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic), - Required: param.Required, - Ephemeral: param.Ephemeral, - }, nil -} - func convertTemplateVersionVariables(dbVariables []database.TemplateVersionVariable) []codersdk.TemplateVersionVariable { variables := make([]codersdk.TemplateVersionVariable, 0) for _, dbVariable := range dbVariables { diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 7137dc16a1b43..a9b981f820be2 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1771,7 +1771,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { // Verify manifest API response. require.Equal(t, workspace.ID, manifest.WorkspaceID) - require.Equal(t, workspace.OwnerUsername, manifest.OwnerUsername) + require.Equal(t, workspace.OwnerName, manifest.OwnerName) require.Equal(t, "First Meta", manifest.Metadata[0].DisplayName) require.Equal(t, "foo1", manifest.Metadata[0].Key) require.Equal(t, "echo hi", manifest.Metadata[0].Script) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index cb954c8690685..fe0c2d3f609a2 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -2248,8 +2248,7 @@ func convertWorkspace( CreatedAt: workspace.CreatedAt, UpdatedAt: workspace.UpdatedAt, OwnerID: workspace.OwnerID, - OwnerName: workspace.OwnerName, - OwnerUsername: workspace.OwnerUsername, + OwnerName: workspace.OwnerUsername, OwnerAvatarURL: workspace.OwnerAvatarUrl, OrganizationID: workspace.OrganizationID, OrganizationName: workspace.OrganizationName, diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index fedbee56942cb..018dd363bdee6 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -42,6 +42,7 @@ import ( "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" + "github.com/coder/terraform-provider-coder/v2/provider" ) func TestWorkspace(t *testing.T) { @@ -1379,12 +1380,12 @@ func TestWorkspaceByOwnerAndName(t *testing.T) { // Then: // When we call without includes_deleted, we don't expect to get the workspace back - _, err = client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + _, err = client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.ErrorContains(t, err, "404") // Then: // When we call with includes_deleted, we should get the workspace back - workspaceNew, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{IncludeDeleted: true}) + workspaceNew, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{IncludeDeleted: true}) require.NoError(t, err) require.Equal(t, workspace.ID, workspaceNew.ID) @@ -1402,7 +1403,7 @@ func TestWorkspaceByOwnerAndName(t *testing.T) { // Then: // We can fetch the most recent workspace - workspaceNew, err = client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{}) + workspaceNew, err = client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{}) require.NoError(t, err) require.Equal(t, workspace.ID, workspaceNew.ID) @@ -1416,7 +1417,7 @@ func TestWorkspaceByOwnerAndName(t *testing.T) { // Then: // When we fetch the deleted workspace, we get the most recently deleted one - workspaceNew, err = client.WorkspaceByOwnerAndName(ctx, workspace.OwnerUsername, workspace.Name, codersdk.WorkspaceOptions{IncludeDeleted: true}) + workspaceNew, err = client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{IncludeDeleted: true}) require.NoError(t, err) require.Equal(t, workspace.ID, workspaceNew.ID) }) @@ -1901,7 +1902,7 @@ func TestWorkspaceFilterManual(t *testing.T) { require.NoError(t, err) require.Len(t, res.Workspaces, len(workspaces)) for _, found := range res.Workspaces { - require.Equal(t, found.OwnerUsername, sdkUser.Username) + require.Equal(t, found.OwnerName, sdkUser.Username) } }) t.Run("IDs", func(t *testing.T) { @@ -2033,7 +2034,7 @@ func TestWorkspaceFilterManual(t *testing.T) { // single workspace res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ - FilterQuery: fmt.Sprintf("template:%s %s/%s", template.Name, workspace.OwnerUsername, workspace.Name), + FilterQuery: fmt.Sprintf("template:%s %s/%s", template.Name, workspace.OwnerName, workspace.Name), }) require.NoError(t, err) require.Len(t, res.Workspaces, 1) @@ -3527,6 +3528,12 @@ func TestWorkspaceWithRichParameters(t *testing.T) { secondParameterDescription = "_This_ is second *parameter*" secondParameterValue = "2" secondParameterValidationMonotonic = codersdk.MonotonicOrderIncreasing + + thirdParameterName = "third_parameter" + thirdParameterType = "list(string)" + thirdParameterFormType = proto.ParameterFormType_MULTISELECT + thirdParameterDefault = `["red"]` + thirdParameterOption = "red" ) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) @@ -3542,6 +3549,7 @@ func TestWorkspaceWithRichParameters(t *testing.T) { Name: firstParameterName, Type: firstParameterType, Description: firstParameterDescription, + FormType: proto.ParameterFormType_INPUT, }, { Name: secondParameterName, @@ -3551,6 +3559,19 @@ func TestWorkspaceWithRichParameters(t *testing.T) { ValidationMin: ptr.Ref(int32(1)), ValidationMax: ptr.Ref(int32(3)), ValidationMonotonic: string(secondParameterValidationMonotonic), + FormType: proto.ParameterFormType_INPUT, + }, + { + Name: thirdParameterName, + Type: thirdParameterType, + DefaultValue: thirdParameterDefault, + Options: []*proto.RichParameterOption{ + { + Name: thirdParameterOption, + Value: thirdParameterOption, + }, + }, + FormType: thirdParameterFormType, }, }, }, @@ -3575,12 +3596,13 @@ func TestWorkspaceWithRichParameters(t *testing.T) { templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID) require.NoError(t, err) - require.Len(t, templateRichParameters, 2) + require.Len(t, templateRichParameters, 3) require.Equal(t, firstParameterName, templateRichParameters[0].Name) require.Equal(t, firstParameterType, templateRichParameters[0].Type) require.Equal(t, firstParameterDescription, templateRichParameters[0].Description) require.Equal(t, firstParameterDescriptionPlaintext, templateRichParameters[0].DescriptionPlaintext) require.Equal(t, codersdk.ValidationMonotonicOrder(""), templateRichParameters[0].ValidationMonotonic) // no validation for string + require.Equal(t, secondParameterName, templateRichParameters[1].Name) require.Equal(t, secondParameterDisplayName, templateRichParameters[1].DisplayName) require.Equal(t, secondParameterType, templateRichParameters[1].Type) @@ -3588,9 +3610,18 @@ func TestWorkspaceWithRichParameters(t *testing.T) { require.Equal(t, secondParameterDescriptionPlaintext, templateRichParameters[1].DescriptionPlaintext) require.Equal(t, secondParameterValidationMonotonic, templateRichParameters[1].ValidationMonotonic) + third := templateRichParameters[2] + require.Equal(t, thirdParameterName, third.Name) + require.Equal(t, thirdParameterType, third.Type) + require.Equal(t, string(database.ParameterFormTypeMultiSelect), third.FormType) + require.Equal(t, thirdParameterDefault, third.DefaultValue) + require.Equal(t, thirdParameterOption, third.Options[0].Name) + require.Equal(t, thirdParameterOption, third.Options[0].Value) + expectedBuildParameters := []codersdk.WorkspaceBuildParameter{ {Name: firstParameterName, Value: firstParameterValue}, {Name: secondParameterName, Value: secondParameterValue}, + {Name: thirdParameterName, Value: thirdParameterDefault}, } template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) @@ -3606,6 +3637,72 @@ func TestWorkspaceWithRichParameters(t *testing.T) { require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters) } +func TestWorkspaceWithMultiSelectFailure(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Response{ + { + Type: &proto.Response_Plan{ + Plan: &proto.PlanComplete{ + Parameters: []*proto.RichParameter{ + { + Name: "param", + Type: provider.OptionTypeListString, + DefaultValue: `["red"]`, + Options: []*proto.RichParameterOption{ + { + Name: "red", + Value: "red", + }, + }, + FormType: proto.ParameterFormType_MULTISELECT, + }, + }, + }, + }, + }, + }, + ProvisionApply: []*proto.Response{{ + Type: &proto.Response_Apply{ + Apply: &proto.ApplyComplete{}, + }, + }}, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID) + require.NoError(t, err) + require.Len(t, templateRichParameters, 1) + + expectedBuildParameters := []codersdk.WorkspaceBuildParameter{ + // purple is not in the response set + {Name: "param", Value: `["red", "purple"]`}, + } + + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + req := codersdk.CreateWorkspaceRequest{ + TemplateID: template.ID, + Name: coderdtest.RandomUsername(t), + AutostartSchedule: ptr.Ref("CRON_TZ=US/Central 30 9 * * 1-5"), + TTLMillis: ptr.Ref((8 * time.Hour).Milliseconds()), + AutomaticUpdates: codersdk.AutomaticUpdatesNever, + RichParameterValues: expectedBuildParameters, + } + + _, err = client.CreateUserWorkspace(context.Background(), codersdk.Me, req) + require.Error(t, err) + var apiError *codersdk.Error + require.ErrorAs(t, err, &apiError) + require.Equal(t, http.StatusBadRequest, apiError.StatusCode()) +} + func TestWorkspaceWithOptionalRichParameters(t *testing.T) { t.Parallel() diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 1178a193a21a0..e3b036dcdf00a 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -107,7 +107,7 @@ type Manifest struct { // OwnerUsername and WorkspaceID are used by an open-source user to identify the workspace. // We do not provide insurance that this will not be removed in the future, // but if it's easy to persist lets keep it around. - OwnerUsername string `json:"owner_username"` + OwnerName string `json:"owner_name"` WorkspaceID uuid.UUID `json:"workspace_id"` WorkspaceName string `json:"workspace_name"` // GitAuthConfigs stores the number of Git configurations diff --git a/codersdk/agentsdk/convert.go b/codersdk/agentsdk/convert.go index f67ac63f3861b..2b7dff950a3e7 100644 --- a/codersdk/agentsdk/convert.go +++ b/codersdk/agentsdk/convert.go @@ -38,7 +38,7 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) { return Manifest{ AgentID: agentID, AgentName: manifest.AgentName, - OwnerUsername: manifest.OwnerUsername, + OwnerName: manifest.OwnerUsername, WorkspaceID: workspaceID, WorkspaceName: manifest.WorkspaceName, Apps: apps, @@ -64,7 +64,7 @@ func ProtoFromManifest(manifest Manifest) (*proto.Manifest, error) { return &proto.Manifest{ AgentId: manifest.AgentID[:], AgentName: manifest.AgentName, - OwnerUsername: manifest.OwnerUsername, + OwnerUsername: manifest.OwnerName, WorkspaceId: manifest.WorkspaceID[:], WorkspaceName: manifest.WorkspaceName, // #nosec G115 - Safe conversion for GitAuthConfigs which is expected to be small and positive diff --git a/codersdk/agentsdk/convert_test.go b/codersdk/agentsdk/convert_test.go index dcafe3127a3df..09482b1694910 100644 --- a/codersdk/agentsdk/convert_test.go +++ b/codersdk/agentsdk/convert_test.go @@ -21,7 +21,7 @@ func TestManifest(t *testing.T) { manifest := agentsdk.Manifest{ AgentID: uuid.New(), AgentName: "test-agent", - OwnerUsername: "test-owner", + OwnerName: "test-owner", WorkspaceID: uuid.New(), WorkspaceName: "test-workspace", GitAuthConfigs: 3, @@ -144,7 +144,7 @@ func TestManifest(t *testing.T) { require.NoError(t, err) require.Equal(t, manifest.AgentID, back.AgentID) require.Equal(t, manifest.AgentName, back.AgentName) - require.Equal(t, manifest.OwnerUsername, back.OwnerUsername) + require.Equal(t, manifest.OwnerName, back.OwnerName) require.Equal(t, manifest.WorkspaceID, back.WorkspaceID) require.Equal(t, manifest.WorkspaceName, back.WorkspaceName) require.Equal(t, manifest.GitAuthConfigs, back.GitAuthConfigs) diff --git a/codersdk/richparameters.go b/codersdk/richparameters.go index f00c947715f9d..db109316fdfc0 100644 --- a/codersdk/richparameters.go +++ b/codersdk/richparameters.go @@ -1,9 +1,12 @@ package codersdk import ( + "encoding/json" + "golang.org/x/xerrors" "tailscale.com/types/ptr" + "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/terraform-provider-coder/v2/provider" ) @@ -66,18 +69,8 @@ func validateBuildParameter(richParameter TemplateVersionParameter, buildParamet current = richParameter.DefaultValue } - if len(richParameter.Options) > 0 { - var matched bool - for _, opt := range richParameter.Options { - if opt.Value == current { - matched = true - break - } - } - - if !matched { - return xerrors.Errorf("parameter value must match one of options: %s", parameterValuesAsArray(richParameter.Options)) - } + if len(richParameter.Options) > 0 && !inOptionSet(richParameter, current) { + return xerrors.Errorf("parameter value must match one of options: %s", parameterValuesAsArray(richParameter.Options)) } if !validationEnabled(richParameter) { @@ -104,6 +97,37 @@ func validateBuildParameter(richParameter TemplateVersionParameter, buildParamet return validation.Valid(richParameter.Type, current, previous) } +// inOptionSet returns if the value given is in the set of options for a parameter. +func inOptionSet(richParameter TemplateVersionParameter, value string) bool { + optionValues := make([]string, 0, len(richParameter.Options)) + for _, option := range richParameter.Options { + optionValues = append(optionValues, option.Value) + } + + // If the type is `list(string)` and the form_type is `multi-select`, then we check each individual + // value in the list against the option set. + isMultiSelect := richParameter.Type == provider.OptionTypeListString && richParameter.FormType == string(provider.ParameterFormTypeMultiSelect) + + if !isMultiSelect { + // This is the simple case. Just checking if the value is in the option set. + return slice.Contains(optionValues, value) + } + + var checks []string + err := json.Unmarshal([]byte(value), &checks) + if err != nil { + return false + } + + for _, check := range checks { + if !slice.Contains(optionValues, check) { + return false + } + } + + return true +} + func findBuildParameter(params []WorkspaceBuildParameter, parameterName string) (*WorkspaceBuildParameter, bool) { if params == nil { return nil, false diff --git a/codersdk/richparameters_internal_test.go b/codersdk/richparameters_internal_test.go new file mode 100644 index 0000000000000..038e89c7442b3 --- /dev/null +++ b/codersdk/richparameters_internal_test.go @@ -0,0 +1,149 @@ +package codersdk + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/terraform-provider-coder/v2/provider" +) + +func Test_inOptionSet(t *testing.T) { + t.Parallel() + + options := func(vals ...string) []TemplateVersionParameterOption { + opts := make([]TemplateVersionParameterOption, 0, len(vals)) + for _, val := range vals { + opts = append(opts, TemplateVersionParameterOption{ + Name: val, + Value: val, + }) + } + return opts + } + + tests := []struct { + name string + param TemplateVersionParameter + value string + want bool + }{ + // The function should never be called with 0 options, but if it is, + // it should always return false. + { + name: "empty", + want: false, + }, + { + name: "no-options", + param: TemplateVersionParameter{ + Options: make([]TemplateVersionParameterOption, 0), + }, + }, + { + name: "no-options-multi", + param: TemplateVersionParameter{ + Type: provider.OptionTypeListString, + FormType: string(provider.ParameterFormTypeMultiSelect), + Options: make([]TemplateVersionParameterOption, 0), + }, + want: false, + }, + { + name: "no-options-list(string)", + param: TemplateVersionParameter{ + Type: provider.OptionTypeListString, + FormType: "", + Options: make([]TemplateVersionParameterOption, 0), + }, + want: false, + }, + { + name: "list(string)-no-form", + param: TemplateVersionParameter{ + Type: provider.OptionTypeListString, + FormType: "", + Options: options("red", "green", "blue"), + }, + want: false, + value: `["red", "blue", "green"]`, + }, + // now for some reasonable values + { + name: "list(string)-multi", + param: TemplateVersionParameter{ + Type: provider.OptionTypeListString, + FormType: string(provider.ParameterFormTypeMultiSelect), + Options: options("red", "green", "blue"), + }, + want: true, + value: `["red", "blue", "green"]`, + }, + { + name: "string with json", + param: TemplateVersionParameter{ + Type: provider.OptionTypeString, + Options: options(`["red","blue","green"]`, `["red","orange"]`), + }, + want: true, + value: `["red","blue","green"]`, + }, + { + name: "string", + param: TemplateVersionParameter{ + Type: provider.OptionTypeString, + Options: options("red", "green", "blue"), + }, + want: true, + value: "red", + }, + // False values + { + name: "list(string)-multi", + param: TemplateVersionParameter{ + Type: provider.OptionTypeListString, + FormType: string(provider.ParameterFormTypeMultiSelect), + Options: options("red", "green", "blue"), + }, + want: false, + value: `["red", "blue", "purple"]`, + }, + { + name: "string with json", + param: TemplateVersionParameter{ + Type: provider.OptionTypeString, + Options: options(`["red","blue"]`, `["red","orange"]`), + }, + want: false, + value: `["red","blue","green"]`, + }, + { + name: "string", + param: TemplateVersionParameter{ + Type: provider.OptionTypeString, + Options: options("red", "green", "blue"), + }, + want: false, + value: "purple", + }, + { + name: "list(string)-multi-scalar-value", + param: TemplateVersionParameter{ + Type: provider.OptionTypeListString, + FormType: string(provider.ParameterFormTypeMultiSelect), + Options: options("red", "green", "blue"), + }, + want: false, + value: "green", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := inOptionSet(tt.param, tt.value) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go index de8bb7b970957..a47cbb685898b 100644 --- a/codersdk/templateversions.go +++ b/codersdk/templateversions.go @@ -54,22 +54,25 @@ const ( // TemplateVersionParameter represents a parameter for a template version. type TemplateVersionParameter struct { - Name string `json:"name"` - DisplayName string `json:"display_name,omitempty"` - Description string `json:"description"` - DescriptionPlaintext string `json:"description_plaintext"` - Type string `json:"type" enums:"string,number,bool,list(string)"` - Mutable bool `json:"mutable"` - DefaultValue string `json:"default_value"` - Icon string `json:"icon"` - Options []TemplateVersionParameterOption `json:"options"` - ValidationError string `json:"validation_error,omitempty"` - ValidationRegex string `json:"validation_regex,omitempty"` - ValidationMin *int32 `json:"validation_min,omitempty"` - ValidationMax *int32 `json:"validation_max,omitempty"` - ValidationMonotonic ValidationMonotonicOrder `json:"validation_monotonic,omitempty" enums:"increasing,decreasing"` - Required bool `json:"required"` - Ephemeral bool `json:"ephemeral"` + Name string `json:"name"` + DisplayName string `json:"display_name,omitempty"` + Description string `json:"description"` + DescriptionPlaintext string `json:"description_plaintext"` + Type string `json:"type" enums:"string,number,bool,list(string)"` + // FormType has an enum value of empty string, `""`. + // Keep the leading comma in the enums struct tag. + FormType string `json:"form_type" enums:",radio,dropdown,input,textarea,slider,checkbox,switch,tag-select,multi-select,error"` + Mutable bool `json:"mutable"` + DefaultValue string `json:"default_value"` + Icon string `json:"icon"` + Options []TemplateVersionParameterOption `json:"options"` + ValidationError string `json:"validation_error,omitempty"` + ValidationRegex string `json:"validation_regex,omitempty"` + ValidationMin *int32 `json:"validation_min,omitempty"` + ValidationMax *int32 `json:"validation_max,omitempty"` + ValidationMonotonic ValidationMonotonicOrder `json:"validation_monotonic,omitempty" enums:"increasing,decreasing"` + Required bool `json:"required"` + Ephemeral bool `json:"ephemeral"` } // TemplateVersionParameterOption represents a selectable option for a template parameter. diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index ee762ddcd3d30..2c73d60a2696c 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -26,12 +26,12 @@ const ( // Workspace is a deployment of a template. It references a specific // version and can be updated. type Workspace struct { - ID uuid.UUID `json:"id" format:"uuid"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - UpdatedAt time.Time `json:"updated_at" format:"date-time"` - OwnerID uuid.UUID `json:"owner_id" format:"uuid"` - OwnerName string `json:"owner_name,omitempty"` - OwnerUsername string `json:"owner_username"` + ID uuid.UUID `json:"id" format:"uuid"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + UpdatedAt time.Time `json:"updated_at" format:"date-time"` + OwnerID uuid.UUID `json:"owner_id" format:"uuid"` + // OwnerName is the username of the owner of the workspace. + OwnerName string `json:"owner_name"` OwnerAvatarURL string `json:"owner_avatar_url"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid"` OrganizationName string `json:"organization_name"` @@ -50,7 +50,6 @@ type Workspace struct { AutostartSchedule *string `json:"autostart_schedule,omitempty"` TTLMillis *int64 `json:"ttl_ms,omitempty"` LastUsedAt time.Time `json:"last_used_at" format:"date-time"` - // DeletingAt indicates the time at which the workspace will be permanently deleted. // A workspace is eligible for deletion if it is dormant (a non-nil dormant_at value) // and a value has been specified for time_til_dormant_autodelete on its template. @@ -70,7 +69,7 @@ type Workspace struct { } func (w Workspace) FullName() string { - return fmt.Sprintf("%s/%s", w.OwnerUsername, w.Name) + return fmt.Sprintf("%s/%s", w.OwnerName, w.Name) } type WorkspaceHealth struct { diff --git a/docs/admin/users/github-auth.md b/docs/admin/users/github-auth.md index c556c87a2accb..57ed6f9eeb37a 100644 --- a/docs/admin/users/github-auth.md +++ b/docs/admin/users/github-auth.md @@ -1,80 +1,91 @@ # GitHub -## Default Configuration - By default, new Coder deployments use a Coder-managed GitHub app to authenticate -users. We provide it for convenience, allowing you to experiment with Coder -without setting up your own GitHub OAuth app. Once you authenticate with it, you -grant Coder server read access to: +users. +We provide it for convenience, allowing you to experiment with Coder +without setting up your own GitHub OAuth app. -- Your GitHub user email -- Your GitHub organization membership -- Other metadata listed during the authentication flow +If you authenticate with it, you grant Coder server read access to your GitHub +user email and other metadata listed during the authentication flow. This access is necessary for the Coder server to complete the authentication -process. To the best of our knowledge, Coder, the company, does not gain access +process. +To the best of our knowledge, Coder, the company, does not gain access to this data by administering the GitHub app. +## Default Configuration + > [!IMPORTANT] -> The default GitHub app requires [device flow](#device-flow) to authenticate. -> This is enabled by default when using the default GitHub app. If you disable -> device flow using `CODER_OAUTH2_GITHUB_DEVICE_FLOW=false`, it will be ignored. +> Installation of the default GitHub app grants Coder (the company) access to your organization's GitHub data. +> +> For production environments, we strongly recommend that you +> [configure your own GitHub OAuth app](#step-1-configure-the-oauth-application-in-github) +> to ensure that your data is not shared with Coder (the company). -By default, only the admin user can sign up. To allow additional users to sign -up with GitHub, add the following environment variable: +To use the default configuration: -```env -CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS=true -``` +1. [Install the GitHub app](https://github.com/apps/coder/installations/select_target) + in any GitHub organization that you want to use with Coder. -To limit sign ups to members of specific GitHub organizations, set: + The default GitHub app requires [device flow](#device-flow) to authenticate. + This is enabled by default when using the default GitHub app. + If you disable device flow using `CODER_OAUTH2_GITHUB_DEVICE_FLOW=false`, it will be ignored. -```env -CODER_OAUTH2_GITHUB_ALLOWED_ORGS="your-org" -``` +1. By default, only the admin user can sign up. + To allow additional users to sign up with GitHub, add: + + ```shell + CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS=true + ``` -For production deployments, we recommend configuring your own GitHub OAuth app -as outlined below. The default is automatically disabled if you configure your -own app or set: +1. (Optional) If you want to limit sign-ups to specific GitHub organizations, set: -```env + ```shell + CODER_OAUTH2_GITHUB_ALLOWED_ORGS="your-org" + ``` + +## Disable the Default GitHub App + +You can disable the default GitHub app by [configuring your own app](#step-1-configure-the-oauth-application-in-github) +or by adding the following environment variable to your [Coder server configuration](../../reference/cli/server.md#options): + +```shell CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE=false ``` > [!NOTE] -> After you disable the default GitHub provider with the setting above, the -> **Sign in with GitHub** button might still appear on your login page even though -> the authentication flow is disabled. +> After you disable the default GitHub provider, the **Sign in with GitHub** button +> might still appear on your login page even though the authentication flow is disabled. > -> To completely hide the GitHub sign-in button, you must both disable the default -> provider and ensure you don't have a custom GitHub OAuth app configured. +> To completely hide the GitHub sign-in button, you must disable the default provider +> and ensure you don't have a custom GitHub OAuth app configured. ## Step 1: Configure the OAuth application in GitHub -First, -[register a GitHub OAuth app](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). -GitHub will ask you for the following Coder parameters: +1. [Register a GitHub OAuth app](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). + +1. GitHub will ask you for the following Coder parameters: -- **Homepage URL**: Set to your Coder deployments - [`CODER_ACCESS_URL`](../../reference/cli/server.md#--access-url) (e.g. - `https://coder.domain.com`) -- **User Authorization Callback URL**: Set to `https://coder.domain.com` + - **Homepage URL**: Set to your Coder deployment's + [`CODER_ACCESS_URL`](../../reference/cli/server.md#--access-url) (e.g. + `https://coder.domain.com`) + - **User Authorization Callback URL**: Set to `https://coder.domain.com` -If you want to allow multiple Coder deployments hosted on subdomains, such as -`coder1.domain.com`, `coder2.domain.com`, to authenticate with the -same GitHub OAuth app, then you can set **User Authorization Callback URL** to -the `https://domain.com` + If you want to allow multiple Coder deployments hosted on subdomains, such as + `coder1.domain.com`, `coder2.domain.com`, to authenticate with the + same GitHub OAuth app, then you can set **User Authorization Callback URL** to + the `https://domain.com` -Take note of the Client ID and Client Secret generated by GitHub. You will use these -values in the next step. +1. Take note of the Client ID and Client Secret generated by GitHub. + You will use these values in the next step. -Coder will need permission to access user email addresses. Find the "Account -Permissions" settings for your app and select "read-only" for "Email addresses". +1. Coder needs permission to access user email addresses. + + Find the **Account Permissions** settings for your app and select **read-only** for **Email addresses**. ## Step 2: Configure Coder with the OAuth credentials -Navigate to your Coder host and run the following command to start up the Coder -server: +Go to your Coder host and run the following command to start up the Coder server: ```shell coder server --oauth2-github-allow-signups=true --oauth2-github-allowed-orgs="your-org" --oauth2-github-client-id="8d1...e05" --oauth2-github-client-secret="57ebc9...02c24c" @@ -87,7 +98,7 @@ Alternatively, if you are running Coder as a system service, you can achieve the same result as the command above by adding the following environment variables to the `/etc/coder.d/coder.env` file: -```env +```shell CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS=true CODER_OAUTH2_GITHUB_ALLOWED_ORGS="your-org" CODER_OAUTH2_GITHUB_CLIENT_ID="8d1...e05" @@ -97,7 +108,7 @@ CODER_OAUTH2_GITHUB_CLIENT_SECRET="57ebc9...02c24c" > [!TIP] > To allow everyone to sign up using GitHub, set: > -> ```env +> ```shell > CODER_OAUTH2_GITHUB_ALLOW_EVERYONE=true > ``` @@ -129,23 +140,24 @@ To upgrade Coder, run: helm upgrade coder-v2/coder -n -f values.yaml ``` -We recommend requiring and auditing MFA usage for all users in your GitHub -organizations. This can be enforced from the organization settings page in the -"Authentication security" sidebar tab. +We recommend requiring and auditing MFA usage for all users in your GitHub organizations. +This can be enforced from the organization settings page in the **Authentication security** sidebar tab. ## Device Flow Coder supports [device flow](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow) -for GitHub OAuth. This is enabled by default for the default GitHub app and cannot be disabled -for that app. For your own custom GitHub OAuth app, you can enable device flow by setting: +for GitHub OAuth. +This is enabled by default for the default GitHub app and cannot be disabled for that app. -```env +For your own custom GitHub OAuth app, you can enable device flow by setting: + +```shell CODER_OAUTH2_GITHUB_DEVICE_FLOW=true ``` -Device flow is optional for custom GitHub OAuth apps. We generally recommend using -the standard OAuth flow instead, as it is more convenient for end users. +Device flow is optional for custom GitHub OAuth apps. +We generally recommend using the standard OAuth flow instead, as it is more convenient for end users. > [!NOTE] > If you're using the default GitHub app, device flow is always enabled regardless of diff --git a/docs/manifest.json b/docs/manifest.json index 1ec955c6244cc..a7a45bb489563 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -198,7 +198,15 @@ "description": "Use Coder Desktop to access your workspace like it's a local machine", "path": "./user-guides/desktop/index.md", "icon_path": "./images/icons/computer-code.svg", - "state": ["beta"] + "state": ["beta"], + "children": [ + { + "title": "Coder Desktop connect and sync", + "description": "Use Coder Desktop to manage your workspace code and files locally", + "path": "./user-guides/desktop/desktop-connect-sync.md", + "state": ["beta"] + } + ] }, { "title": "Workspace Management", diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 84681a9134530..65e3b5f7c8ec8 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -7185,6 +7185,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W "description_plaintext": "string", "display_name": "string", "ephemeral": true, + "form_type": "", "icon": "string", "mutable": true, "name": "string", @@ -7208,29 +7209,41 @@ Restarts will only happen on weekdays in this list on weeks which line up with W ### Properties -| Name | Type | Required | Restrictions | Description | -|-------------------------|---------------------------------------------------------------------------------------------|----------|--------------|-------------| -| `default_value` | string | false | | | -| `description` | string | false | | | -| `description_plaintext` | string | false | | | -| `display_name` | string | false | | | -| `ephemeral` | boolean | false | | | -| `icon` | string | false | | | -| `mutable` | boolean | false | | | -| `name` | string | false | | | -| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | | -| `required` | boolean | false | | | -| `type` | string | false | | | -| `validation_error` | string | false | | | -| `validation_max` | integer | false | | | -| `validation_min` | integer | false | | | -| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | | -| `validation_regex` | string | false | | | +| Name | Type | Required | Restrictions | Description | +|-------------------------|---------------------------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------| +| `default_value` | string | false | | | +| `description` | string | false | | | +| `description_plaintext` | string | false | | | +| `display_name` | string | false | | | +| `ephemeral` | boolean | false | | | +| `form_type` | string | false | | Form type has an enum value of empty string, `""`. Keep the leading comma in the enums struct tag. | +| `icon` | string | false | | | +| `mutable` | boolean | false | | | +| `name` | string | false | | | +| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | | +| `required` | boolean | false | | | +| `type` | string | false | | | +| `validation_error` | string | false | | | +| `validation_max` | integer | false | | | +| `validation_min` | integer | false | | | +| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | | +| `validation_regex` | string | false | | | #### Enumerated Values | Property | Value | |------------------------|----------------| +| `form_type` | `` | +| `form_type` | `radio` | +| `form_type` | `dropdown` | +| `form_type` | `input` | +| `form_type` | `textarea` | +| `form_type` | `slider` | +| `form_type` | `checkbox` | +| `form_type` | `switch` | +| `form_type` | `tag-select` | +| `form_type` | `multi-select` | +| `form_type` | `error` | | `type` | `string` | | `type` | `number` | | `type` | `bool` | @@ -8420,7 +8433,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -8457,8 +8469,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `outdated` | boolean | false | | | | `owner_avatar_url` | string | false | | | | `owner_id` | string | false | | | -| `owner_name` | string | false | | | -| `owner_username` | string | false | | | +| `owner_name` | string | false | | Owner name is the username of the owner of the workspace. | | `template_active_version_id` | string | false | | | | `template_allow_user_cancel_workspace_jobs` | boolean | false | | | | `template_display_name` | string | false | | | @@ -10125,7 +10136,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index 1b16556277edb..6075af775c9bc 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -3165,6 +3165,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "description_plaintext": "string", "display_name": "string", "ephemeral": true, + "form_type": "", "icon": "string", "mutable": true, "name": "string", @@ -3197,34 +3198,46 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r Status Code **200** -| Name | Type | Required | Restrictions | Description | -|---------------------------|----------------------------------------------------------------------------------|----------|--------------|-------------| -| `[array item]` | array | false | | | -| `» default_value` | string | false | | | -| `» description` | string | false | | | -| `» description_plaintext` | string | false | | | -| `» display_name` | string | false | | | -| `» ephemeral` | boolean | false | | | -| `» icon` | string | false | | | -| `» mutable` | boolean | false | | | -| `» name` | string | false | | | -| `» options` | array | false | | | -| `»» description` | string | false | | | -| `»» icon` | string | false | | | -| `»» name` | string | false | | | -| `»» value` | string | false | | | -| `» required` | boolean | false | | | -| `» type` | string | false | | | -| `» validation_error` | string | false | | | -| `» validation_max` | integer | false | | | -| `» validation_min` | integer | false | | | -| `» validation_monotonic` | [codersdk.ValidationMonotonicOrder](schemas.md#codersdkvalidationmonotonicorder) | false | | | -| `» validation_regex` | string | false | | | +| Name | Type | Required | Restrictions | Description | +|---------------------------|----------------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------| +| `[array item]` | array | false | | | +| `» default_value` | string | false | | | +| `» description` | string | false | | | +| `» description_plaintext` | string | false | | | +| `» display_name` | string | false | | | +| `» ephemeral` | boolean | false | | | +| `» form_type` | string | false | | Form type has an enum value of empty string, `""`. Keep the leading comma in the enums struct tag. | +| `» icon` | string | false | | | +| `» mutable` | boolean | false | | | +| `» name` | string | false | | | +| `» options` | array | false | | | +| `»» description` | string | false | | | +| `»» icon` | string | false | | | +| `»» name` | string | false | | | +| `»» value` | string | false | | | +| `» required` | boolean | false | | | +| `» type` | string | false | | | +| `» validation_error` | string | false | | | +| `» validation_max` | integer | false | | | +| `» validation_min` | integer | false | | | +| `» validation_monotonic` | [codersdk.ValidationMonotonicOrder](schemas.md#codersdkvalidationmonotonicorder) | false | | | +| `» validation_regex` | string | false | | | #### Enumerated Values | Property | Value | |------------------------|----------------| +| `form_type` | `` | +| `form_type` | `radio` | +| `form_type` | `dropdown` | +| `form_type` | `input` | +| `form_type` | `textarea` | +| `form_type` | `slider` | +| `form_type` | `checkbox` | +| `form_type` | `switch` | +| `form_type` | `tag-select` | +| `form_type` | `multi-select` | +| `form_type` | `error` | | `type` | `string` | | `type` | `number` | | `type` | `bool` | diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 6f3b2ec9ce76d..1e73787dfb77e 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -291,7 +291,6 @@ of the template will be used. "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -577,7 +576,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -889,7 +887,6 @@ of the template will be used. "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1161,7 +1158,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1448,7 +1444,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1850,7 +1845,6 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", - "owner_username": "string", "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", diff --git a/docs/user-guides/desktop/desktop-connect-sync.md b/docs/user-guides/desktop/desktop-connect-sync.md new file mode 100644 index 0000000000000..1a09c9b7c5f5d --- /dev/null +++ b/docs/user-guides/desktop/desktop-connect-sync.md @@ -0,0 +1,175 @@ +# Coder Desktop Connect and Sync + +Use Coder Desktop to work on your workspaces and files as though they're on your LAN. + +> [!NOTE] +> Coder Desktop requires a Coder deployment running [v2.20.0](https://github.com/coder/coder/releases/tag/v2.20.0) or later. + +## Coder Connect + +While active, Coder Connect will list the workspaces you own and will configure your system to connect to them over private IPv6 addresses and custom hostnames ending in `.coder`. + +![Coder Desktop list of workspaces](../../images/user-guides/desktop/coder-desktop-workspaces.png) + +To copy the `.coder` hostname of a workspace agent, you can click the copy icon beside it. + +You can also connect to the SSH server in your workspace using any SSH client, such as OpenSSH or PuTTY: + + ```shell + ssh your-workspace.coder + ``` + +Any services listening on ports in your workspace will be available on the same hostname. For example, you can access a web server on port `8080` by visiting `http://your-workspace.coder:8080` in your browser. + +> [!NOTE] +> For Coder versions v2.21.3 and earlier: the Coder IDE extensions for VSCode and JetBrains create their own tunnel and do not utilize the Coder Connect tunnel to connect to workspaces. + +### Ping your workspace + +
+ +### macOS + +Use `ping6` in your terminal to verify the connection to your workspace: + + ```shell + ping6 -c 5 your-workspace.coder + ``` + +### Windows + +Use `ping` in a Command Prompt or PowerShell terminal to verify the connection to your workspace: + + ```shell + ping -n 5 your-workspace.coder + ``` + +
+ +## Sync a local directory with your workspace + +Coder Desktop file sync provides bidirectional synchronization between a local directory and your workspace. +You can work offline, add screenshots to documentation, or use local development tools while keeping your files in sync with your workspace. + +1. Create a new local directory. + + If you select an existing clone of your repository, Desktop will recognize it as conflicting files. + +1. In the Coder Desktop app, select **File sync**. + + ![Coder Desktop File Sync screen](../../images/user-guides/desktop/coder-desktop-file-sync.png) + +1. Select the **+** in the corner to select the local path, workspace, and remote path, then select **Add**: + + ![Coder Desktop File Sync add paths](../../images/user-guides/desktop/coder-desktop-file-sync-add.png) + +1. File sync clones your workspace directory to your local directory, then watches for changes: + + ![Coder Desktop File Sync watching](../../images/user-guides/desktop/coder-desktop-file-sync-watching.png) + + For more information about the current status, hover your mouse over the status. + +File sync excludes version control system directories like `.git/` from synchronization, so keep your Git-cloned repository wherever you run Git commands. +This means that if you use an IDE with a built-in terminal to edit files on your remote workspace, that should be the Git clone and your local directory should be for file syncs. + +> [!NOTE] +> Coder Desktop uses `alpha` and `beta` to distinguish between the: +> +> - Local directory: `alpha` +> - Remote directory: `beta` + +### File sync conflicts + +File sync shows a `Conflicts` status when it detects conflicting files. + +You can hover your mouse over the status for the list of conflicts: + +![Desktop file sync conflicts mouseover](../../images/user-guides/desktop/coder-desktop-file-sync-conflicts-mouseover.png) + +If you encounter a synchronization conflict, delete the conflicting file that contains changes you don't want to keep. + +## Accessing web apps in a secure browser context + +Some web applications require a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) to function correctly. +A browser typically considers an origin secure if the connection is to `localhost`, or over `HTTPS`. + +As Coder Connect uses its own hostnames and does not provide TLS to the browser, Google Chrome and Firefox will not allow any web APIs that require a secure context. + +> [!NOTE] +> Despite the browser showing an insecure connection without `HTTPS`, the underlying tunnel is encrypted with WireGuard in the same fashion as other Coder workspace connections (e.g. `coder port-forward`). + +If you require secure context web APIs, you will need to mark the workspace hostnames as secure in your browser settings. + +We are planning some changes to Coder Desktop that will make accessing secure context web apps easier. Stay tuned for updates. + +
+ +### Chrome + +1. Open Chrome and visit `chrome://flags/#unsafely-treat-insecure-origin-as-secure`. + +1. Enter the full workspace hostname, including the `http` scheme and the port (e.g. `http://your-workspace.coder:8080`), into the **Insecure origins treated as secure** text field. + + If you need to enter multiple URLs, use a comma to separate them. + + ![Google Chrome insecure origin settings](../../images/user-guides/desktop/chrome-insecure-origin.png) + +1. Ensure that the dropdown to the right of the text field is set to **Enabled**. + +1. You will be prompted to relaunch Google Chrome at the bottom of the page. Select **Relaunch** to restart Google Chrome. + +1. On relaunch and subsequent launches, Google Chrome will show a banner stating "You are using an unsupported command-line flag". This banner can be safely dismissed. + +1. Web apps accessed on the configured hostnames and ports will now function correctly in a secure context. + +### Firefox + +1. Open Firefox and visit `about:config`. + +1. Read the warning and select **Accept the Risk and Continue** to access the Firefox configuration page. + +1. Enter `dom.securecontext.allowlist` into the search bar at the top. + +1. Select **String** on the entry with the same name at the bottom of the list, then select the plus icon on the right. + +1. In the text field, enter the full workspace hostname, without the `http` scheme and port: `your-workspace.coder`. Then select the tick icon. + + If you need to enter multiple URLs, use a comma to separate them. + + ![Firefox insecure origin settings](../../images/user-guides/desktop/firefox-insecure-origin.png) + +1. Web apps accessed on the configured hostnames will now function correctly in a secure context without requiring a restart. + +
+ +## Troubleshooting + +### Mac: Issues updating Coder Desktop + +> No workspaces! + +And + +> Internal Error: The VPN must be started with the app open during first-time setup. + +Due to an issue with the way Coder Desktop works with the macOS [interprocess communication mechanism](https://developer.apple.com/documentation/xpc)(XPC) system network extension, core Desktop functionality can break when you upgrade the application. + +
+ +The resolution depends on which version of macOS you use: + +### macOS <=14 + +1. Delete the application from `/Applications`. +1. Restart your device. + +### macOS 15+ + +1. Open **System Settings** +1. Select **General** +1. Select **Login Items & Extensions** +1. Scroll down, and select the **ⓘ** for **Network Extensions** +1. Select the **...** next to Coder Desktop, then **Delete Extension**, and follow the prompts. +1. Re-open Coder Desktop and follow the prompts to reinstall the network extension. + +
diff --git a/docs/user-guides/desktop/index.md b/docs/user-guides/desktop/index.md index 69a32837a8b87..1f28f46d7c733 100644 --- a/docs/user-guides/desktop/index.md +++ b/docs/user-guides/desktop/index.md @@ -121,171 +121,6 @@ Before you can use Coder Desktop, you will need to sign in. 1. Coder Connect is now running! -## Coder Connect +## Next Steps -While active, Coder Connect will list the workspaces you own and will configure your system to connect to them over private IPv6 addresses and custom hostnames ending in `.coder`. - -![Coder Desktop list of workspaces](../../images/user-guides/desktop/coder-desktop-workspaces.png) - -To copy the `.coder` hostname of a workspace agent, you can click the copy icon beside it. - -You can also connect to the SSH server in your workspace using any SSH client, such as OpenSSH or PuTTY: - - ```shell - ssh your-workspace.coder - ``` - -Any services listening on ports in your workspace will be available on the same hostname. For example, you can access a web server on port `8080` by visiting `http://your-workspace.coder:8080` in your browser. - -> [!NOTE] -> Currently, the Coder IDE extensions for VSCode and JetBrains create their own tunnel and do not utilize the Coder Connect tunnel to connect to workspaces. - -### Ping your workspace - -
- -### macOS - -Use `ping6` in your terminal to verify the connection to your workspace: - - ```shell - ping6 -c 5 your-workspace.coder - ``` - -### Windows - -Use `ping` in a Command Prompt or PowerShell terminal to verify the connection to your workspace: - - ```shell - ping -n 5 your-workspace.coder - ``` - -
- -## Sync a local directory with your workspace - -Coder Desktop file sync provides bidirectional synchronization between a local directory and your workspace. -You can work offline, add screenshots to documentation, or use local development tools while keeping your files in sync with your workspace. - -1. Create a new local directory. - - If you select an existing clone of your repository, Desktop will recognize it as conflicting files. - -1. In the Coder Desktop app, select **File sync**. - - ![Coder Desktop File Sync screen](../../images/user-guides/desktop/coder-desktop-file-sync.png) - -1. Select the **+** in the corner to select the local path, workspace, and remote path, then select **Add**: - - ![Coder Desktop File Sync add paths](../../images/user-guides/desktop/coder-desktop-file-sync-add.png) - -1. File sync clones your workspace directory to your local directory, then watches for changes: - - ![Coder Desktop File Sync watching](../../images/user-guides/desktop/coder-desktop-file-sync-watching.png) - - For more information about the current status, hover your mouse over the status. - -File sync excludes version control system directories like `.git/` from synchronization, so keep your Git-cloned repository wherever you run Git commands. -This means that if you use an IDE with a built-in terminal to edit files on your remote workspace, that should be the Git clone and your local directory should be for file syncs. - -> [!NOTE] -> Coder Desktop uses `alpha` and `beta` to distinguish between the: -> -> - Local directory: `alpha` -> - Remote directory: `beta` - -### File sync conflicts - -File sync shows a `Conflicts` status when it detects conflicting files. - -You can hover your mouse over the status for the list of conflicts: - -![Desktop file sync conflicts mouseover](../../images/user-guides/desktop/coder-desktop-file-sync-conflicts-mouseover.png) - -If you encounter a synchronization conflict, delete the conflicting file that contains changes you don't want to keep. - -## Accessing web apps in a secure browser context - -Some web applications require a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) to function correctly. -A browser typically considers an origin secure if the connection is to `localhost`, or over `HTTPS`. - -As Coder Connect uses its own hostnames and does not provide TLS to the browser, Google Chrome and Firefox will not allow any web APIs that require a secure context. - -> [!NOTE] -> Despite the browser showing an insecure connection without `HTTPS`, the underlying tunnel is encrypted with WireGuard in the same fashion as other Coder workspace connections (e.g. `coder port-forward`). - -If you require secure context web APIs, you will need to mark the workspace hostnames as secure in your browser settings. - -We are planning some changes to Coder Desktop that will make accessing secure context web apps easier. Stay tuned for updates. - -
- -### Chrome - -1. Open Chrome and visit `chrome://flags/#unsafely-treat-insecure-origin-as-secure`. - -1. Enter the full workspace hostname, including the `http` scheme and the port (e.g. `http://your-workspace.coder:8080`), into the **Insecure origins treated as secure** text field. - - If you need to enter multiple URLs, use a comma to separate them. - - ![Google Chrome insecure origin settings](../../images/user-guides/desktop/chrome-insecure-origin.png) - -1. Ensure that the dropdown to the right of the text field is set to **Enabled**. - -1. You will be prompted to relaunch Google Chrome at the bottom of the page. Select **Relaunch** to restart Google Chrome. - -1. On relaunch and subsequent launches, Google Chrome will show a banner stating "You are using an unsupported command-line flag". This banner can be safely dismissed. - -1. Web apps accessed on the configured hostnames and ports will now function correctly in a secure context. - -### Firefox - -1. Open Firefox and visit `about:config`. - -1. Read the warning and select **Accept the Risk and Continue** to access the Firefox configuration page. - -1. Enter `dom.securecontext.allowlist` into the search bar at the top. - -1. Select **String** on the entry with the same name at the bottom of the list, then select the plus icon on the right. - -1. In the text field, enter the full workspace hostname, without the `http` scheme and port: `your-workspace.coder`. Then select the tick icon. - - If you need to enter multiple URLs, use a comma to separate them. - - ![Firefox insecure origin settings](../../images/user-guides/desktop/firefox-insecure-origin.png) - -1. Web apps accessed on the configured hostnames will now function correctly in a secure context without requiring a restart. - -
- -## Troubleshooting - -### Mac: Issues updating Coder Desktop - -> No workspaces! - -And - -> Internal Error: The VPN must be started with the app open during first-time setup. - -Due to an issue with the way Coder Desktop works with the macOS [interprocess communication mechanism](https://developer.apple.com/documentation/xpc)(XPC) system network extension, core Desktop functionality can break when you upgrade the application. - -
- -The resolution depends on which version of macOS you use: - -### macOS <=14 - -1. Delete the application from `/Applications`. -1. Restart your device. - -### macOS 15+ - -1. Open **System Settings** -1. Select **General** -1. Select **Login Items & Extensions** -1. Scroll down, and select the **ⓘ** for **Network Extensions** -1. Select the **...** next to Coder Desktop, then **Delete Extension**, and follow the prompts. -1. Re-open Coder Desktop and follow the prompts to reinstall the network extension. - -
+- [Connect to and work on your workspace](./desktop-connect-sync.md) diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index bf23dfa96b2f8..63430109589c0 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -267,14 +267,11 @@ module "vscode-web" { } module "jetbrains" { - count = data.coder_workspace.me.start_count - source = "git::https://github.com/coder/modules.git//jetbrains?ref=jetbrains" - agent_id = coder_agent.dev.id - folder = local.repo_dir - options = ["WS", "GO"] - default = "GO" - latest = true - channel = "eap" + count = data.coder_workspace.me.start_count + source = "git::https://github.com/coder/registry.git//registry/coder/modules/jetbrains?ref=jetbrains" + agent_id = coder_agent.dev.id + folder = local.repo_dir + major_version = "latest" } module "filebrowser" { diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 747bb4f41a7de..a6724d2b0fd1c 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -757,10 +757,17 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s if param.Default != nil { defaultVal = *param.Default } + + pft, err := proto.FormType(param.FormType) + if err != nil { + return nil, xerrors.Errorf("decode form_type for coder_parameter.%s: %w", resource.Name, err) + } + protoParam := &proto.RichParameter{ Name: param.Name, DisplayName: param.DisplayName, Description: param.Description, + FormType: pft, Type: param.Type, Mutable: param.Mutable, DefaultValue: defaultVal, diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index 7a303096f19cf..e58f5c039f9e4 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -608,24 +608,28 @@ func TestConvertResources(t *testing.T) { Description: "First parameter from child module", Mutable: true, DefaultValue: "abcdef", + FormType: proto.ParameterFormType_INPUT, }, { Name: "Second parameter from child module", Type: "string", Description: "Second parameter from child module", Mutable: true, DefaultValue: "ghijkl", + FormType: proto.ParameterFormType_INPUT, }, { Name: "First parameter from module", Type: "string", Description: "First parameter from module", Mutable: true, DefaultValue: "abcdef", + FormType: proto.ParameterFormType_INPUT, }, { Name: "Second parameter from module", Type: "string", Description: "Second parameter from module", Mutable: true, DefaultValue: "ghijkl", + FormType: proto.ParameterFormType_INPUT, }, { Name: "Example", Type: "string", @@ -637,35 +641,41 @@ func TestConvertResources(t *testing.T) { Value: "second", }}, Required: true, + FormType: proto.ParameterFormType_RADIO, }, { Name: "number_example", Type: "number", DefaultValue: "4", ValidationMin: nil, ValidationMax: nil, + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_max_zero", Type: "number", DefaultValue: "-2", ValidationMin: terraform.PtrInt32(-3), ValidationMax: terraform.PtrInt32(0), + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_min_max", Type: "number", DefaultValue: "4", ValidationMin: terraform.PtrInt32(3), ValidationMax: terraform.PtrInt32(6), + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_min_zero", Type: "number", DefaultValue: "4", ValidationMin: terraform.PtrInt32(0), ValidationMax: terraform.PtrInt32(6), + FormType: proto.ParameterFormType_INPUT, }, { Name: "Sample", Type: "string", Description: "blah blah", DefaultValue: "ok", + FormType: proto.ParameterFormType_INPUT, }}, }, "rich-parameters-order": { @@ -688,12 +698,14 @@ func TestConvertResources(t *testing.T) { Type: "string", Required: true, Order: 55, + FormType: proto.ParameterFormType_INPUT, }, { Name: "Sample", Type: "string", Description: "blah blah", DefaultValue: "ok", Order: 99, + FormType: proto.ParameterFormType_INPUT, }}, }, "rich-parameters-validation": { @@ -719,36 +731,42 @@ func TestConvertResources(t *testing.T) { Mutable: true, ValidationMin: nil, ValidationMax: nil, + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_max", Type: "number", DefaultValue: "4", ValidationMin: nil, ValidationMax: terraform.PtrInt32(6), + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_max_zero", Type: "number", DefaultValue: "-3", ValidationMin: nil, ValidationMax: terraform.PtrInt32(0), + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_min", Type: "number", DefaultValue: "4", ValidationMin: terraform.PtrInt32(3), ValidationMax: nil, + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_min_max", Type: "number", DefaultValue: "4", ValidationMin: terraform.PtrInt32(3), ValidationMax: terraform.PtrInt32(6), + FormType: proto.ParameterFormType_INPUT, }, { Name: "number_example_min_zero", Type: "number", DefaultValue: "4", ValidationMin: terraform.PtrInt32(0), ValidationMax: nil, + FormType: proto.ParameterFormType_INPUT, }}, }, "external-auth-providers": { @@ -824,29 +842,34 @@ func TestConvertResources(t *testing.T) { Description: "First parameter from child module", Mutable: true, DefaultValue: "abcdef", + FormType: proto.ParameterFormType_INPUT, }, { Name: "Second parameter from child module", Type: "string", Description: "Second parameter from child module", Mutable: true, DefaultValue: "ghijkl", + FormType: proto.ParameterFormType_INPUT, }, { Name: "First parameter from module", Type: "string", Description: "First parameter from module", Mutable: true, DefaultValue: "abcdef", + FormType: proto.ParameterFormType_INPUT, }, { Name: "Second parameter from module", Type: "string", Description: "Second parameter from module", Mutable: true, DefaultValue: "ghijkl", + FormType: proto.ParameterFormType_INPUT, }, { Name: "Sample", Type: "string", Description: "blah blah", DefaultValue: "ok", + FormType: proto.ParameterFormType_INPUT, }}, Presets: []*proto.Preset{{ Name: "My First Project", diff --git a/provisionerd/proto/version.go b/provisionerd/proto/version.go index 1da643cecebfd..86a6e289c9329 100644 --- a/provisionerd/proto/version.go +++ b/provisionerd/proto/version.go @@ -28,6 +28,7 @@ import "github.com/coder/coder/v2/apiversion" // - Add new field named `expiration_policy` to `Prebuild`, with a field named // `ttl` to define TTL-based expiration for unclaimed prebuilds. // - Add `group` field to `App` +// - Add `form_type` field to parameters const ( CurrentMajor = 1 CurrentMinor = 6 diff --git a/provisionersdk/proto/converter.go b/provisionersdk/proto/converter.go new file mode 100644 index 0000000000000..d4cfb25640a63 --- /dev/null +++ b/provisionersdk/proto/converter.go @@ -0,0 +1,63 @@ +package proto + +import ( + "golang.org/x/xerrors" + + "github.com/coder/terraform-provider-coder/v2/provider" +) + +func ProviderFormType(ft ParameterFormType) (provider.ParameterFormType, error) { + switch ft { + case ParameterFormType_DEFAULT: + return provider.ParameterFormTypeDefault, nil + case ParameterFormType_FORM_ERROR: + return provider.ParameterFormTypeError, nil + case ParameterFormType_RADIO: + return provider.ParameterFormTypeRadio, nil + case ParameterFormType_DROPDOWN: + return provider.ParameterFormTypeDropdown, nil + case ParameterFormType_INPUT: + return provider.ParameterFormTypeInput, nil + case ParameterFormType_TEXTAREA: + return provider.ParameterFormTypeTextArea, nil + case ParameterFormType_SLIDER: + return provider.ParameterFormTypeSlider, nil + case ParameterFormType_CHECKBOX: + return provider.ParameterFormTypeCheckbox, nil + case ParameterFormType_SWITCH: + return provider.ParameterFormTypeSwitch, nil + case ParameterFormType_TAGSELECT: + return provider.ParameterFormTypeTagSelect, nil + case ParameterFormType_MULTISELECT: + return provider.ParameterFormTypeMultiSelect, nil + } + return provider.ParameterFormTypeDefault, xerrors.Errorf("unsupported form type: %s", ft) +} + +func FormType(ft provider.ParameterFormType) (ParameterFormType, error) { + switch ft { + case provider.ParameterFormTypeDefault: + return ParameterFormType_DEFAULT, nil + case provider.ParameterFormTypeError: + return ParameterFormType_FORM_ERROR, nil + case provider.ParameterFormTypeRadio: + return ParameterFormType_RADIO, nil + case provider.ParameterFormTypeDropdown: + return ParameterFormType_DROPDOWN, nil + case provider.ParameterFormTypeInput: + return ParameterFormType_INPUT, nil + case provider.ParameterFormTypeTextArea: + return ParameterFormType_TEXTAREA, nil + case provider.ParameterFormTypeSlider: + return ParameterFormType_SLIDER, nil + case provider.ParameterFormTypeCheckbox: + return ParameterFormType_CHECKBOX, nil + case provider.ParameterFormTypeSwitch: + return ParameterFormType_SWITCH, nil + case provider.ParameterFormTypeTagSelect: + return ParameterFormType_TAGSELECT, nil + case provider.ParameterFormTypeMultiSelect: + return ParameterFormType_MULTISELECT, nil + } + return ParameterFormType_DEFAULT, xerrors.Errorf("unsupported form type: %s", ft) +} diff --git a/provisionersdk/proto/converter_test.go b/provisionersdk/proto/converter_test.go new file mode 100644 index 0000000000000..5b393c2200a1b --- /dev/null +++ b/provisionersdk/proto/converter_test.go @@ -0,0 +1,26 @@ +package proto_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/provisionersdk/proto" + "github.com/coder/terraform-provider-coder/v2/provider" +) + +// TestProviderFormTypeEnum keeps the provider.ParameterFormTypes() enum in sync with the +// proto.FormType enum. If a new form type is added to the provider, it should also be added +// to the proto file. +func TestProviderFormTypeEnum(t *testing.T) { + t.Parallel() + + all := provider.ParameterFormTypes() + for _, p := range all { + t.Run(string(p), func(t *testing.T) { + t.Parallel() + _, err := proto.FormType(p) + require.NoError(t, err, "proto form type should be valid, add it to the proto file") + }) + } +} diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index ab14f22a9d599..51d6bee4047b5 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -21,6 +21,79 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ParameterFormType int32 + +const ( + ParameterFormType_DEFAULT ParameterFormType = 0 + ParameterFormType_FORM_ERROR ParameterFormType = 1 + ParameterFormType_RADIO ParameterFormType = 2 + ParameterFormType_DROPDOWN ParameterFormType = 3 + ParameterFormType_INPUT ParameterFormType = 4 + ParameterFormType_TEXTAREA ParameterFormType = 5 + ParameterFormType_SLIDER ParameterFormType = 6 + ParameterFormType_CHECKBOX ParameterFormType = 7 + ParameterFormType_SWITCH ParameterFormType = 8 + ParameterFormType_TAGSELECT ParameterFormType = 9 + ParameterFormType_MULTISELECT ParameterFormType = 10 +) + +// Enum value maps for ParameterFormType. +var ( + ParameterFormType_name = map[int32]string{ + 0: "DEFAULT", + 1: "FORM_ERROR", + 2: "RADIO", + 3: "DROPDOWN", + 4: "INPUT", + 5: "TEXTAREA", + 6: "SLIDER", + 7: "CHECKBOX", + 8: "SWITCH", + 9: "TAGSELECT", + 10: "MULTISELECT", + } + ParameterFormType_value = map[string]int32{ + "DEFAULT": 0, + "FORM_ERROR": 1, + "RADIO": 2, + "DROPDOWN": 3, + "INPUT": 4, + "TEXTAREA": 5, + "SLIDER": 6, + "CHECKBOX": 7, + "SWITCH": 8, + "TAGSELECT": 9, + "MULTISELECT": 10, + } +) + +func (x ParameterFormType) Enum() *ParameterFormType { + p := new(ParameterFormType) + *p = x + return p +} + +func (x ParameterFormType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ParameterFormType) Descriptor() protoreflect.EnumDescriptor { + return file_provisionersdk_proto_provisioner_proto_enumTypes[0].Descriptor() +} + +func (ParameterFormType) Type() protoreflect.EnumType { + return &file_provisionersdk_proto_provisioner_proto_enumTypes[0] +} + +func (x ParameterFormType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ParameterFormType.Descriptor instead. +func (ParameterFormType) EnumDescriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{0} +} + // LogLevel represents severity of the log. type LogLevel int32 @@ -61,11 +134,11 @@ func (x LogLevel) String() string { } func (LogLevel) Descriptor() protoreflect.EnumDescriptor { - return file_provisionersdk_proto_provisioner_proto_enumTypes[0].Descriptor() + return file_provisionersdk_proto_provisioner_proto_enumTypes[1].Descriptor() } func (LogLevel) Type() protoreflect.EnumType { - return &file_provisionersdk_proto_provisioner_proto_enumTypes[0] + return &file_provisionersdk_proto_provisioner_proto_enumTypes[1] } func (x LogLevel) Number() protoreflect.EnumNumber { @@ -74,7 +147,7 @@ func (x LogLevel) Number() protoreflect.EnumNumber { // Deprecated: Use LogLevel.Descriptor instead. func (LogLevel) EnumDescriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{1} } type AppSharingLevel int32 @@ -110,11 +183,11 @@ func (x AppSharingLevel) String() string { } func (AppSharingLevel) Descriptor() protoreflect.EnumDescriptor { - return file_provisionersdk_proto_provisioner_proto_enumTypes[1].Descriptor() + return file_provisionersdk_proto_provisioner_proto_enumTypes[2].Descriptor() } func (AppSharingLevel) Type() protoreflect.EnumType { - return &file_provisionersdk_proto_provisioner_proto_enumTypes[1] + return &file_provisionersdk_proto_provisioner_proto_enumTypes[2] } func (x AppSharingLevel) Number() protoreflect.EnumNumber { @@ -123,7 +196,7 @@ func (x AppSharingLevel) Number() protoreflect.EnumNumber { // Deprecated: Use AppSharingLevel.Descriptor instead. func (AppSharingLevel) EnumDescriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{1} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2} } type AppOpenIn int32 @@ -160,11 +233,11 @@ func (x AppOpenIn) String() string { } func (AppOpenIn) Descriptor() protoreflect.EnumDescriptor { - return file_provisionersdk_proto_provisioner_proto_enumTypes[2].Descriptor() + return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor() } func (AppOpenIn) Type() protoreflect.EnumType { - return &file_provisionersdk_proto_provisioner_proto_enumTypes[2] + return &file_provisionersdk_proto_provisioner_proto_enumTypes[3] } func (x AppOpenIn) Number() protoreflect.EnumNumber { @@ -173,7 +246,7 @@ func (x AppOpenIn) Number() protoreflect.EnumNumber { // Deprecated: Use AppOpenIn.Descriptor instead. func (AppOpenIn) EnumDescriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3} } // WorkspaceTransition is the desired outcome of a build @@ -210,11 +283,11 @@ func (x WorkspaceTransition) String() string { } func (WorkspaceTransition) Descriptor() protoreflect.EnumDescriptor { - return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor() + return file_provisionersdk_proto_provisioner_proto_enumTypes[4].Descriptor() } func (WorkspaceTransition) Type() protoreflect.EnumType { - return &file_provisionersdk_proto_provisioner_proto_enumTypes[3] + return &file_provisionersdk_proto_provisioner_proto_enumTypes[4] } func (x WorkspaceTransition) Number() protoreflect.EnumNumber { @@ -223,7 +296,7 @@ func (x WorkspaceTransition) Number() protoreflect.EnumNumber { // Deprecated: Use WorkspaceTransition.Descriptor instead. func (WorkspaceTransition) EnumDescriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{4} } type PrebuiltWorkspaceBuildStage int32 @@ -259,11 +332,11 @@ func (x PrebuiltWorkspaceBuildStage) String() string { } func (PrebuiltWorkspaceBuildStage) Descriptor() protoreflect.EnumDescriptor { - return file_provisionersdk_proto_provisioner_proto_enumTypes[4].Descriptor() + return file_provisionersdk_proto_provisioner_proto_enumTypes[5].Descriptor() } func (PrebuiltWorkspaceBuildStage) Type() protoreflect.EnumType { - return &file_provisionersdk_proto_provisioner_proto_enumTypes[4] + return &file_provisionersdk_proto_provisioner_proto_enumTypes[5] } func (x PrebuiltWorkspaceBuildStage) Number() protoreflect.EnumNumber { @@ -272,7 +345,7 @@ func (x PrebuiltWorkspaceBuildStage) Number() protoreflect.EnumNumber { // Deprecated: Use PrebuiltWorkspaceBuildStage.Descriptor instead. func (PrebuiltWorkspaceBuildStage) EnumDescriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{4} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{5} } type TimingState int32 @@ -308,11 +381,11 @@ func (x TimingState) String() string { } func (TimingState) Descriptor() protoreflect.EnumDescriptor { - return file_provisionersdk_proto_provisioner_proto_enumTypes[5].Descriptor() + return file_provisionersdk_proto_provisioner_proto_enumTypes[6].Descriptor() } func (TimingState) Type() protoreflect.EnumType { - return &file_provisionersdk_proto_provisioner_proto_enumTypes[5] + return &file_provisionersdk_proto_provisioner_proto_enumTypes[6] } func (x TimingState) Number() protoreflect.EnumNumber { @@ -321,7 +394,7 @@ func (x TimingState) Number() protoreflect.EnumNumber { // Deprecated: Use TimingState.Descriptor instead. func (TimingState) EnumDescriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{5} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{6} } // Empty indicates a successful request/response. @@ -543,9 +616,10 @@ type RichParameter struct { ValidationMonotonic string `protobuf:"bytes,12,opt,name=validation_monotonic,json=validationMonotonic,proto3" json:"validation_monotonic,omitempty"` Required bool `protobuf:"varint,13,opt,name=required,proto3" json:"required,omitempty"` // legacy_variable_name was removed (= 14) - DisplayName string `protobuf:"bytes,15,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` - Order int32 `protobuf:"varint,16,opt,name=order,proto3" json:"order,omitempty"` - Ephemeral bool `protobuf:"varint,17,opt,name=ephemeral,proto3" json:"ephemeral,omitempty"` + DisplayName string `protobuf:"bytes,15,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Order int32 `protobuf:"varint,16,opt,name=order,proto3" json:"order,omitempty"` + Ephemeral bool `protobuf:"varint,17,opt,name=ephemeral,proto3" json:"ephemeral,omitempty"` + FormType ParameterFormType `protobuf:"varint,18,opt,name=form_type,json=formType,proto3,enum=provisioner.ParameterFormType" json:"form_type,omitempty"` } func (x *RichParameter) Reset() { @@ -692,6 +766,13 @@ func (x *RichParameter) GetEphemeral() bool { return false } +func (x *RichParameter) GetFormType() ParameterFormType { + if x != nil { + return x.FormType + } + return ParameterFormType_DEFAULT +} + // RichParameterValue holds the key/value mapping of a parameter. type RichParameterValue struct { state protoimpl.MessageState @@ -3793,7 +3874,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x69, 0x63, 0x6f, 0x6e, 0x22, 0xfe, 0x04, 0x0a, 0x0d, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x69, 0x63, 0x6f, 0x6e, 0x22, 0xbb, 0x05, 0x0a, 0x0d, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, @@ -3829,534 +3910,548 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, - 0x72, 0x61, 0x6c, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, - 0x14, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x12, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x24, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, 0x74, 0x0a, 0x08, 0x50, - 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, - 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, - 0x10, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x22, 0x8d, 0x01, 0x0a, 0x06, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x3c, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x31, - 0x0a, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, - 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x22, 0x3b, 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x47, - 0x0a, 0x13, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0x57, 0x0a, 0x0d, 0x56, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x72, 0x61, 0x6c, 0x12, 0x3b, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x46, 0x6f, + 0x72, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x79, 0x70, 0x65, + 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6d, 0x69, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, 0x14, 0x6c, 0x65, + 0x67, 0x61, 0x63, 0x79, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x12, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 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, - 0x22, 0x4a, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x37, 0x0a, 0x14, - 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x41, 0x75, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x1c, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x22, 0x49, 0x0a, 0x14, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, - 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xda, 0x08, 0x0a, - 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, - 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x76, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, - 0x73, 0x74, 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, - 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, - 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x08, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x05, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, - 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, - 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, - 0x73, 0x68, 0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x12, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, 0x74, - 0x69, 0x6e, 0x67, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x74, 0x64, 0x5f, 0x66, - 0x69, 0x6c, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x74, 0x64, 0x46, - 0x69, 0x6c, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0c, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x52, 0x0b, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, - 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, - 0x61, 0x5f, 0x65, 0x6e, 0x76, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x6e, 0x76, 0x52, 0x09, - 0x65, 0x78, 0x74, 0x72, 0x61, 0x45, 0x6e, 0x76, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x53, 0x0a, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x5f, 0x6d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x52, - 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x12, 0x3f, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x76, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x0d, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, - 0x69, 0x4b, 0x65, 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x1a, 0xa3, 0x01, 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, 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, 0x16, 0x0a, 0x06, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, - 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x1a, - 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x4a, - 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x62, 0x65, 0x66, - 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x22, 0x8f, 0x01, 0x0a, 0x13, 0x52, 0x65, + 0x75, 0x65, 0x22, 0x24, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, 0x74, 0x0a, 0x08, 0x50, 0x72, 0x65, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x70, 0x69, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x10, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x8d, + 0x01, 0x0a, 0x06, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, + 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, + 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x70, + 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x52, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x22, 0x3b, + 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x47, 0x0a, 0x13, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, + 0x61, 0x74, 0x68, 0x73, 0x22, 0x57, 0x0a, 0x0d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 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, 0x22, 0x4a, 0x0a, + 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x75, 0x74, + 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x1c, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, + 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0x49, + 0x0a, 0x14, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xda, 0x08, 0x0a, 0x05, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, + 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, + 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, + 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x74, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x74, 0x64, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0c, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x07, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x65, + 0x6e, 0x76, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x6e, 0x76, 0x52, 0x09, 0x65, 0x78, 0x74, + 0x72, 0x61, 0x45, 0x6e, 0x76, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x53, 0x0a, 0x14, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x5f, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x69, 0x6e, 0x67, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x12, 0x3a, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, - 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x3c, 0x0a, - 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x6f, 0x6c, + 0x67, 0x12, 0x3f, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x52, 0x0d, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, 0x69, 0x4b, 0x65, + 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x1a, 0xa3, 0x01, 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, 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, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x1a, 0x36, 0x0a, 0x08, + 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x4a, 0x04, 0x08, 0x0e, + 0x10, 0x0f, 0x52, 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, + 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x22, 0x8f, 0x01, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x3a, + 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, + 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x6e, 0x69, 0x74, + 0x6f, 0x72, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x3c, 0x0a, 0x07, 0x76, 0x6f, + 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, + 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x15, 0x4d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, + 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x63, 0x0a, 0x15, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x15, 0x4d, - 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x63, 0x0a, 0x15, - 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, - 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 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, 0xc6, 0x01, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x73, 0x63, - 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0e, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x73, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, - 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x54, 0x65, 0x72, - 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x5f, 0x68, 0x65, 0x6c, - 0x70, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x73, 0x68, 0x48, 0x65, - 0x6c, 0x70, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, 0x6f, 0x72, - 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x03, 0x45, 0x6e, - 0x76, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x06, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x5f, 0x6f, - 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, - 0x75, 0x6e, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x75, 0x6e, - 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x72, 0x75, 0x6e, 0x4f, 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x22, 0x6e, 0x0a, - 0x0c, 0x44, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x29, 0x0a, - 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x66, 0x6f, 0x6c, 0x64, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xaa, 0x03, - 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, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, - 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, - 0x64, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x52, 0x06, 0x6f, 0x70, - 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 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, 0x92, 0x03, 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, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 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, 0x5e, 0x0a, 0x06, 0x4d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x22, 0x31, 0x0a, 0x04, 0x52, 0x6f, - 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0x48, 0x0a, - 0x15, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x41, 0x75, 0x74, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xca, 0x09, 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, 0x12, 0x23, 0x0a, 0x0d, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, - 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x12, 0x42, 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, - 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, - 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, - 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x62, 0x61, 0x63, 0x5f, 0x72, - 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x17, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x52, 0x62, 0x61, - 0x63, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x6d, 0x0a, 0x1e, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, - 0x6c, 0x74, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, - 0x62, 0x75, 0x69, 0x6c, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x1b, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, - 0x6c, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x5d, 0x0a, 0x19, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, - 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x16, 0x72, 0x75, + 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 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, 0xc6, + 0x01, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, + 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, + 0x5f, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0e, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x73, 0x68, 0x48, 0x65, 0x6c, 0x70, 0x65, + 0x72, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x14, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x03, 0x45, 0x6e, 0x76, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x06, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x4f, + 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, + 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x75, 0x6e, + 0x4f, 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, + 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x22, 0x6e, 0x0a, 0x0c, 0x44, 0x65, + 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x46, + 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xaa, 0x03, 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, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, + 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, + 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 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, 0x92, 0x03, 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, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 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, 0x5e, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x22, 0x31, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x15, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, - 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, - 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, - 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, - 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 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, 0x22, 0x92, 0x03, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, - 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x6b, 0x65, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xca, 0x09, 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, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, + 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, + 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, + 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x42, + 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x11, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x62, 0x61, 0x63, 0x5f, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x52, 0x62, 0x61, 0x63, 0x52, 0x6f, + 0x6c, 0x65, 0x73, 0x12, 0x6d, 0x0a, 0x1e, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x5f, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, + 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x62, 0x75, 0x69, + 0x6c, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x1b, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, + 0x67, 0x65, 0x12, 0x5d, 0x0a, 0x19, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, + 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x16, 0x72, 0x75, 0x6e, 0x6e, 0x69, + 0x6e, 0x67, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, + 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, + 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, + 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, + 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, + 0x61, 0x67, 0x73, 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, 0x22, 0x92, 0x03, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x5b, 0x0a, 0x19, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, + 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x52, 0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x93, 0x04, 0x0a, 0x0c, 0x50, 0x6c, + 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x01, 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, 0x02, 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, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, + 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, + 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 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, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, - 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, - 0x5b, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x52, 0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x93, 0x04, 0x0a, - 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 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, 0x02, 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, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, - 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, - 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, - 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, - 0x73, 0x65, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, - 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, - 0x65, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 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, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, - 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, - 0x67, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, - 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, - 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, - 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, - 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 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, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, - 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, - 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 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, 0x35, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, - 0x49, 0x6e, 0x12, 0x0e, 0x0a, 0x06, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x1a, 0x02, - 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, - 0x57, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, 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, 0x2a, 0x3e, 0x0a, 0x1b, 0x50, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, - 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, - 0x74, 0x61, 0x67, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0a, - 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, - 0x41, 0x49, 0x4d, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x0b, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, - 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x32, 0x49, 0x0a, 0x0b, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2f, 0x76, 0x32, 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, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, + 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, + 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, + 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, + 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x22, 0xd1, 0x01, 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, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x2a, 0xa8, 0x01, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x46, 0x6f, 0x72, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, + 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x4d, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x41, 0x44, 0x49, 0x4f, + 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x52, 0x4f, 0x50, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, + 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x54, + 0x45, 0x58, 0x54, 0x41, 0x52, 0x45, 0x41, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x4c, 0x49, + 0x44, 0x45, 0x52, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x42, 0x4f, + 0x58, 0x10, 0x07, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x57, 0x49, 0x54, 0x43, 0x48, 0x10, 0x08, 0x12, + 0x0d, 0x0a, 0x09, 0x54, 0x41, 0x47, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x09, 0x12, 0x0f, + 0x0a, 0x0b, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x0a, 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, 0x35, 0x0a, + 0x09, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0e, 0x0a, 0x06, 0x57, 0x49, + 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, + 0x49, 0x4d, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, + 0x41, 0x42, 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, 0x2a, 0x3e, 0x0a, + 0x1b, 0x50, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x08, 0x0a, 0x04, + 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, + 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x02, 0x2a, 0x35, 0x0a, + 0x0b, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, + 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, + 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, + 0x45, 0x44, 0x10, 0x02, 0x32, 0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, + 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 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 ( @@ -4371,125 +4466,127 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte { return file_provisionersdk_proto_provisioner_proto_rawDescData } -var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 7) var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 45) var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ - (LogLevel)(0), // 0: provisioner.LogLevel - (AppSharingLevel)(0), // 1: provisioner.AppSharingLevel - (AppOpenIn)(0), // 2: provisioner.AppOpenIn - (WorkspaceTransition)(0), // 3: provisioner.WorkspaceTransition - (PrebuiltWorkspaceBuildStage)(0), // 4: provisioner.PrebuiltWorkspaceBuildStage - (TimingState)(0), // 5: provisioner.TimingState - (*Empty)(nil), // 6: provisioner.Empty - (*TemplateVariable)(nil), // 7: provisioner.TemplateVariable - (*RichParameterOption)(nil), // 8: provisioner.RichParameterOption - (*RichParameter)(nil), // 9: provisioner.RichParameter - (*RichParameterValue)(nil), // 10: provisioner.RichParameterValue - (*ExpirationPolicy)(nil), // 11: provisioner.ExpirationPolicy - (*Prebuild)(nil), // 12: provisioner.Prebuild - (*Preset)(nil), // 13: provisioner.Preset - (*PresetParameter)(nil), // 14: provisioner.PresetParameter - (*ResourceReplacement)(nil), // 15: provisioner.ResourceReplacement - (*VariableValue)(nil), // 16: provisioner.VariableValue - (*Log)(nil), // 17: provisioner.Log - (*InstanceIdentityAuth)(nil), // 18: provisioner.InstanceIdentityAuth - (*ExternalAuthProviderResource)(nil), // 19: provisioner.ExternalAuthProviderResource - (*ExternalAuthProvider)(nil), // 20: provisioner.ExternalAuthProvider - (*Agent)(nil), // 21: provisioner.Agent - (*ResourcesMonitoring)(nil), // 22: provisioner.ResourcesMonitoring - (*MemoryResourceMonitor)(nil), // 23: provisioner.MemoryResourceMonitor - (*VolumeResourceMonitor)(nil), // 24: provisioner.VolumeResourceMonitor - (*DisplayApps)(nil), // 25: provisioner.DisplayApps - (*Env)(nil), // 26: provisioner.Env - (*Script)(nil), // 27: provisioner.Script - (*Devcontainer)(nil), // 28: provisioner.Devcontainer - (*App)(nil), // 29: provisioner.App - (*Healthcheck)(nil), // 30: provisioner.Healthcheck - (*Resource)(nil), // 31: provisioner.Resource - (*Module)(nil), // 32: provisioner.Module - (*Role)(nil), // 33: provisioner.Role - (*RunningAgentAuthToken)(nil), // 34: provisioner.RunningAgentAuthToken - (*Metadata)(nil), // 35: provisioner.Metadata - (*Config)(nil), // 36: provisioner.Config - (*ParseRequest)(nil), // 37: provisioner.ParseRequest - (*ParseComplete)(nil), // 38: provisioner.ParseComplete - (*PlanRequest)(nil), // 39: provisioner.PlanRequest - (*PlanComplete)(nil), // 40: provisioner.PlanComplete - (*ApplyRequest)(nil), // 41: provisioner.ApplyRequest - (*ApplyComplete)(nil), // 42: provisioner.ApplyComplete - (*Timing)(nil), // 43: provisioner.Timing - (*CancelRequest)(nil), // 44: provisioner.CancelRequest - (*Request)(nil), // 45: provisioner.Request - (*Response)(nil), // 46: provisioner.Response - (*Agent_Metadata)(nil), // 47: provisioner.Agent.Metadata - nil, // 48: provisioner.Agent.EnvEntry - (*Resource_Metadata)(nil), // 49: provisioner.Resource.Metadata - nil, // 50: provisioner.ParseComplete.WorkspaceTagsEntry - (*timestamppb.Timestamp)(nil), // 51: google.protobuf.Timestamp + (ParameterFormType)(0), // 0: provisioner.ParameterFormType + (LogLevel)(0), // 1: provisioner.LogLevel + (AppSharingLevel)(0), // 2: provisioner.AppSharingLevel + (AppOpenIn)(0), // 3: provisioner.AppOpenIn + (WorkspaceTransition)(0), // 4: provisioner.WorkspaceTransition + (PrebuiltWorkspaceBuildStage)(0), // 5: provisioner.PrebuiltWorkspaceBuildStage + (TimingState)(0), // 6: provisioner.TimingState + (*Empty)(nil), // 7: provisioner.Empty + (*TemplateVariable)(nil), // 8: provisioner.TemplateVariable + (*RichParameterOption)(nil), // 9: provisioner.RichParameterOption + (*RichParameter)(nil), // 10: provisioner.RichParameter + (*RichParameterValue)(nil), // 11: provisioner.RichParameterValue + (*ExpirationPolicy)(nil), // 12: provisioner.ExpirationPolicy + (*Prebuild)(nil), // 13: provisioner.Prebuild + (*Preset)(nil), // 14: provisioner.Preset + (*PresetParameter)(nil), // 15: provisioner.PresetParameter + (*ResourceReplacement)(nil), // 16: provisioner.ResourceReplacement + (*VariableValue)(nil), // 17: provisioner.VariableValue + (*Log)(nil), // 18: provisioner.Log + (*InstanceIdentityAuth)(nil), // 19: provisioner.InstanceIdentityAuth + (*ExternalAuthProviderResource)(nil), // 20: provisioner.ExternalAuthProviderResource + (*ExternalAuthProvider)(nil), // 21: provisioner.ExternalAuthProvider + (*Agent)(nil), // 22: provisioner.Agent + (*ResourcesMonitoring)(nil), // 23: provisioner.ResourcesMonitoring + (*MemoryResourceMonitor)(nil), // 24: provisioner.MemoryResourceMonitor + (*VolumeResourceMonitor)(nil), // 25: provisioner.VolumeResourceMonitor + (*DisplayApps)(nil), // 26: provisioner.DisplayApps + (*Env)(nil), // 27: provisioner.Env + (*Script)(nil), // 28: provisioner.Script + (*Devcontainer)(nil), // 29: provisioner.Devcontainer + (*App)(nil), // 30: provisioner.App + (*Healthcheck)(nil), // 31: provisioner.Healthcheck + (*Resource)(nil), // 32: provisioner.Resource + (*Module)(nil), // 33: provisioner.Module + (*Role)(nil), // 34: provisioner.Role + (*RunningAgentAuthToken)(nil), // 35: provisioner.RunningAgentAuthToken + (*Metadata)(nil), // 36: provisioner.Metadata + (*Config)(nil), // 37: provisioner.Config + (*ParseRequest)(nil), // 38: provisioner.ParseRequest + (*ParseComplete)(nil), // 39: provisioner.ParseComplete + (*PlanRequest)(nil), // 40: provisioner.PlanRequest + (*PlanComplete)(nil), // 41: provisioner.PlanComplete + (*ApplyRequest)(nil), // 42: provisioner.ApplyRequest + (*ApplyComplete)(nil), // 43: provisioner.ApplyComplete + (*Timing)(nil), // 44: provisioner.Timing + (*CancelRequest)(nil), // 45: provisioner.CancelRequest + (*Request)(nil), // 46: provisioner.Request + (*Response)(nil), // 47: provisioner.Response + (*Agent_Metadata)(nil), // 48: provisioner.Agent.Metadata + nil, // 49: provisioner.Agent.EnvEntry + (*Resource_Metadata)(nil), // 50: provisioner.Resource.Metadata + nil, // 51: provisioner.ParseComplete.WorkspaceTagsEntry + (*timestamppb.Timestamp)(nil), // 52: google.protobuf.Timestamp } var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ - 8, // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption - 11, // 1: provisioner.Prebuild.expiration_policy:type_name -> provisioner.ExpirationPolicy - 14, // 2: provisioner.Preset.parameters:type_name -> provisioner.PresetParameter - 12, // 3: provisioner.Preset.prebuild:type_name -> provisioner.Prebuild - 0, // 4: provisioner.Log.level:type_name -> provisioner.LogLevel - 48, // 5: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry - 29, // 6: provisioner.Agent.apps:type_name -> provisioner.App - 47, // 7: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata - 25, // 8: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps - 27, // 9: provisioner.Agent.scripts:type_name -> provisioner.Script - 26, // 10: provisioner.Agent.extra_envs:type_name -> provisioner.Env - 22, // 11: provisioner.Agent.resources_monitoring:type_name -> provisioner.ResourcesMonitoring - 28, // 12: provisioner.Agent.devcontainers:type_name -> provisioner.Devcontainer - 23, // 13: provisioner.ResourcesMonitoring.memory:type_name -> provisioner.MemoryResourceMonitor - 24, // 14: provisioner.ResourcesMonitoring.volumes:type_name -> provisioner.VolumeResourceMonitor - 30, // 15: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck - 1, // 16: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel - 2, // 17: provisioner.App.open_in:type_name -> provisioner.AppOpenIn - 21, // 18: provisioner.Resource.agents:type_name -> provisioner.Agent - 49, // 19: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata - 3, // 20: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 33, // 21: provisioner.Metadata.workspace_owner_rbac_roles:type_name -> provisioner.Role - 4, // 22: provisioner.Metadata.prebuilt_workspace_build_stage:type_name -> provisioner.PrebuiltWorkspaceBuildStage - 34, // 23: provisioner.Metadata.running_agent_auth_tokens:type_name -> provisioner.RunningAgentAuthToken - 7, // 24: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable - 50, // 25: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry - 35, // 26: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata - 10, // 27: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue - 16, // 28: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue - 20, // 29: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider - 10, // 30: provisioner.PlanRequest.previous_parameter_values:type_name -> provisioner.RichParameterValue - 31, // 31: provisioner.PlanComplete.resources:type_name -> provisioner.Resource - 9, // 32: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter - 19, // 33: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource - 43, // 34: provisioner.PlanComplete.timings:type_name -> provisioner.Timing - 32, // 35: provisioner.PlanComplete.modules:type_name -> provisioner.Module - 13, // 36: provisioner.PlanComplete.presets:type_name -> provisioner.Preset - 15, // 37: provisioner.PlanComplete.resource_replacements:type_name -> provisioner.ResourceReplacement - 35, // 38: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata - 31, // 39: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource - 9, // 40: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter - 19, // 41: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource - 43, // 42: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing - 51, // 43: provisioner.Timing.start:type_name -> google.protobuf.Timestamp - 51, // 44: provisioner.Timing.end:type_name -> google.protobuf.Timestamp - 5, // 45: provisioner.Timing.state:type_name -> provisioner.TimingState - 36, // 46: provisioner.Request.config:type_name -> provisioner.Config - 37, // 47: provisioner.Request.parse:type_name -> provisioner.ParseRequest - 39, // 48: provisioner.Request.plan:type_name -> provisioner.PlanRequest - 41, // 49: provisioner.Request.apply:type_name -> provisioner.ApplyRequest - 44, // 50: provisioner.Request.cancel:type_name -> provisioner.CancelRequest - 17, // 51: provisioner.Response.log:type_name -> provisioner.Log - 38, // 52: provisioner.Response.parse:type_name -> provisioner.ParseComplete - 40, // 53: provisioner.Response.plan:type_name -> provisioner.PlanComplete - 42, // 54: provisioner.Response.apply:type_name -> provisioner.ApplyComplete - 45, // 55: provisioner.Provisioner.Session:input_type -> provisioner.Request - 46, // 56: provisioner.Provisioner.Session:output_type -> provisioner.Response - 56, // [56:57] is the sub-list for method output_type - 55, // [55:56] is the sub-list for method input_type - 55, // [55:55] is the sub-list for extension type_name - 55, // [55:55] is the sub-list for extension extendee - 0, // [0:55] is the sub-list for field type_name + 9, // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption + 0, // 1: provisioner.RichParameter.form_type:type_name -> provisioner.ParameterFormType + 12, // 2: provisioner.Prebuild.expiration_policy:type_name -> provisioner.ExpirationPolicy + 15, // 3: provisioner.Preset.parameters:type_name -> provisioner.PresetParameter + 13, // 4: provisioner.Preset.prebuild:type_name -> provisioner.Prebuild + 1, // 5: provisioner.Log.level:type_name -> provisioner.LogLevel + 49, // 6: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry + 30, // 7: provisioner.Agent.apps:type_name -> provisioner.App + 48, // 8: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata + 26, // 9: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps + 28, // 10: provisioner.Agent.scripts:type_name -> provisioner.Script + 27, // 11: provisioner.Agent.extra_envs:type_name -> provisioner.Env + 23, // 12: provisioner.Agent.resources_monitoring:type_name -> provisioner.ResourcesMonitoring + 29, // 13: provisioner.Agent.devcontainers:type_name -> provisioner.Devcontainer + 24, // 14: provisioner.ResourcesMonitoring.memory:type_name -> provisioner.MemoryResourceMonitor + 25, // 15: provisioner.ResourcesMonitoring.volumes:type_name -> provisioner.VolumeResourceMonitor + 31, // 16: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck + 2, // 17: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel + 3, // 18: provisioner.App.open_in:type_name -> provisioner.AppOpenIn + 22, // 19: provisioner.Resource.agents:type_name -> provisioner.Agent + 50, // 20: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata + 4, // 21: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition + 34, // 22: provisioner.Metadata.workspace_owner_rbac_roles:type_name -> provisioner.Role + 5, // 23: provisioner.Metadata.prebuilt_workspace_build_stage:type_name -> provisioner.PrebuiltWorkspaceBuildStage + 35, // 24: provisioner.Metadata.running_agent_auth_tokens:type_name -> provisioner.RunningAgentAuthToken + 8, // 25: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable + 51, // 26: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry + 36, // 27: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata + 11, // 28: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue + 17, // 29: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue + 21, // 30: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider + 11, // 31: provisioner.PlanRequest.previous_parameter_values:type_name -> provisioner.RichParameterValue + 32, // 32: provisioner.PlanComplete.resources:type_name -> provisioner.Resource + 10, // 33: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter + 20, // 34: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource + 44, // 35: provisioner.PlanComplete.timings:type_name -> provisioner.Timing + 33, // 36: provisioner.PlanComplete.modules:type_name -> provisioner.Module + 14, // 37: provisioner.PlanComplete.presets:type_name -> provisioner.Preset + 16, // 38: provisioner.PlanComplete.resource_replacements:type_name -> provisioner.ResourceReplacement + 36, // 39: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata + 32, // 40: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource + 10, // 41: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter + 20, // 42: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource + 44, // 43: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing + 52, // 44: provisioner.Timing.start:type_name -> google.protobuf.Timestamp + 52, // 45: provisioner.Timing.end:type_name -> google.protobuf.Timestamp + 6, // 46: provisioner.Timing.state:type_name -> provisioner.TimingState + 37, // 47: provisioner.Request.config:type_name -> provisioner.Config + 38, // 48: provisioner.Request.parse:type_name -> provisioner.ParseRequest + 40, // 49: provisioner.Request.plan:type_name -> provisioner.PlanRequest + 42, // 50: provisioner.Request.apply:type_name -> provisioner.ApplyRequest + 45, // 51: provisioner.Request.cancel:type_name -> provisioner.CancelRequest + 18, // 52: provisioner.Response.log:type_name -> provisioner.Log + 39, // 53: provisioner.Response.parse:type_name -> provisioner.ParseComplete + 41, // 54: provisioner.Response.plan:type_name -> provisioner.PlanComplete + 43, // 55: provisioner.Response.apply:type_name -> provisioner.ApplyComplete + 46, // 56: provisioner.Provisioner.Session:input_type -> provisioner.Request + 47, // 57: provisioner.Provisioner.Session:output_type -> provisioner.Response + 57, // [57:58] is the sub-list for method output_type + 56, // [56:57] is the sub-list for method input_type + 56, // [56:56] is the sub-list for extension type_name + 56, // [56:56] is the sub-list for extension extendee + 0, // [0:56] is the sub-list for field type_name } func init() { file_provisionersdk_proto_provisioner_proto_init() } @@ -5038,7 +5135,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc, - NumEnums: 6, + NumEnums: 7, NumMessages: 45, NumExtensions: 0, NumServices: 1, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 9c238ee803873..4d75187aa42da 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -27,6 +27,20 @@ message RichParameterOption { string icon = 4; } +enum ParameterFormType { + DEFAULT = 0; + FORM_ERROR = 1; + RADIO = 2; + DROPDOWN = 3; + INPUT = 4; + TEXTAREA = 5; + SLIDER = 6; + CHECKBOX = 7; + SWITCH = 8; + TAGSELECT = 9; + MULTISELECT = 10; +} + // RichParameter represents a variable that is exposed. message RichParameter { reserved 14; @@ -49,6 +63,7 @@ message RichParameter { string display_name = 15; int32 order = 16; bool ephemeral = 17; + ParameterFormType form_type = 18; } // RichParameterValue holds the key/value mapping of a parameter. diff --git a/site/e2e/parameters.ts b/site/e2e/parameters.ts index c63ba06dfc5e2..3b672f334c039 100644 --- a/site/e2e/parameters.ts +++ b/site/e2e/parameters.ts @@ -1,4 +1,4 @@ -import type { RichParameter } from "./provisionerGenerated"; +import { ParameterFormType, type RichParameter } from "./provisionerGenerated"; // Rich parameters @@ -19,6 +19,7 @@ export const emptyParameter: RichParameter = { displayName: "", order: 0, ephemeral: false, + formType: ParameterFormType.DEFAULT, }; // firstParameter is mutable string with a default value (parameter value not required). @@ -149,6 +150,7 @@ export const firstBuildOption: RichParameter = { defaultValue: "ABCDEF", mutable: true, ephemeral: true, + options: [], }; export const secondBuildOption: RichParameter = { @@ -161,4 +163,5 @@ export const secondBuildOption: RichParameter = { defaultValue: "false", mutable: true, ephemeral: true, + options: [], }; diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index 3f1b31a94e9a5..f5f3d1f52c5c6 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -5,6 +5,21 @@ import { Timestamp } from "./google/protobuf/timestampGenerated"; export const protobufPackage = "provisioner"; +export enum ParameterFormType { + DEFAULT = 0, + FORM_ERROR = 1, + RADIO = 2, + DROPDOWN = 3, + INPUT = 4, + TEXTAREA = 5, + SLIDER = 6, + CHECKBOX = 7, + SWITCH = 8, + TAGSELECT = 9, + MULTISELECT = 10, + UNRECOGNIZED = -1, +} + /** LogLevel represents severity of the log. */ export enum LogLevel { TRACE = 0, @@ -96,6 +111,7 @@ export interface RichParameter { displayName: string; order: number; ephemeral: boolean; + formType: ParameterFormType; } /** RichParameterValue holds the key/value mapping of a parameter. */ @@ -539,6 +555,9 @@ export const RichParameter = { if (message.ephemeral === true) { writer.uint32(136).bool(message.ephemeral); } + if (message.formType !== 0) { + writer.uint32(144).int32(message.formType); + } return writer; }, }; diff --git a/site/src/api/queries/deployment.ts b/site/src/api/queries/deployment.ts index 4b65b20da82cc..4d1610bd57b46 100644 --- a/site/src/api/queries/deployment.ts +++ b/site/src/api/queries/deployment.ts @@ -1,4 +1,5 @@ import { API } from "api/api"; +import { disabledRefetchOptions } from "./util"; export const deploymentConfigQueryKey = ["deployment", "config"]; @@ -26,6 +27,7 @@ export const deploymentStats = () => { export const deploymentSSHConfig = () => { return { + ...disabledRefetchOptions, queryKey: ["deployment", "sshConfig"], queryFn: API.getDeploymentSSHConfig, }; diff --git a/site/src/api/queries/workspaces.ts b/site/src/api/queries/workspaces.ts index b1f7aad1d792f..61bc7f0e70c22 100644 --- a/site/src/api/queries/workspaces.ts +++ b/site/src/api/queries/workspaces.ts @@ -267,7 +267,7 @@ export const activate = (workspace: Workspace, queryClient: QueryClient) => { }, onSuccess: (updatedWorkspace: Workspace) => { queryClient.setQueryData( - workspaceByOwnerAndNameKey(workspace.owner_username, workspace.name), + workspaceByOwnerAndNameKey(workspace.owner_name, workspace.name), updatedWorkspace, ); }, @@ -316,12 +316,12 @@ export const toggleFavorite = ( }, onSuccess: async () => { queryClient.setQueryData( - workspaceByOwnerAndNameKey(workspace.owner_username, workspace.name), + workspaceByOwnerAndNameKey(workspace.owner_name, workspace.name), { ...workspace, favorite: !workspace.favorite }, ); await queryClient.invalidateQueries({ queryKey: workspaceByOwnerAndNameKey( - workspace.owner_username, + workspace.owner_name, workspace.name, ), }); diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d9008c4d78919..8465f3d02be38 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2819,6 +2819,7 @@ export interface TemplateVersionParameter { readonly description: string; readonly description_plaintext: string; readonly type: string; + readonly form_type: string; readonly mutable: boolean; readonly default_value: string; readonly icon: string; @@ -3277,8 +3278,7 @@ export interface Workspace { readonly created_at: string; readonly updated_at: string; readonly owner_id: string; - readonly owner_name?: string; - readonly owner_username: string; + readonly owner_name: string; readonly owner_avatar_url: string; readonly organization_id: string; readonly organization_name: string; diff --git a/site/src/components/Button/Button.tsx b/site/src/components/Button/Button.tsx index 908dacb8c5c3d..859aa10d0cf68 100644 --- a/site/src/components/Button/Button.tsx +++ b/site/src/components/Button/Button.tsx @@ -8,33 +8,45 @@ import { forwardRef } from "react"; import { cn } from "utils/cn"; const buttonVariants = cva( - `inline-flex items-center justify-center gap-1 whitespace-nowrap font-sans + ` + inline-flex items-center justify-center gap-1 whitespace-nowrap font-sans border-solid rounded-md transition-colors - text-sm font-semibold font-medium cursor-pointer no-underline + text-sm font-medium cursor-pointer no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link disabled:pointer-events-none disabled:text-content-disabled [&:is(a):not([href])]:pointer-events-none [&:is(a):not([href])]:text-content-disabled [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:p-0.5 - [&>img]:pointer-events-none [&>img]:shrink-0 [&>img]:p-0.5`, + [&_img]:pointer-events-none [&_img]:shrink-0 [&_img]:p-0.5 + `, { variants: { variant: { - default: - "bg-surface-invert-primary text-content-invert hover:bg-surface-invert-secondary border-none disabled:bg-surface-secondary font-semibold", - outline: - "border border-border-default text-content-primary bg-transparent hover:bg-surface-secondary", - subtle: - "border-none bg-transparent text-content-secondary hover:text-content-primary", - destructive: - "border border-border-destructive text-content-primary bg-surface-destructive hover:bg-transparent disabled:bg-transparent disabled:text-content-disabled font-semibold", + default: ` + border-none bg-surface-invert-primary font-semibold text-content-invert + hover:bg-surface-invert-secondary + disabled:bg-surface-secondary + `, + outline: ` + border border-border-default bg-transparent text-content-primary + hover:bg-surface-secondary + `, + subtle: ` + border-none bg-transparent text-content-secondary + hover:text-content-primary + `, + destructive: ` + border border-border-destructive font-semibold text-content-primary bg-surface-destructive + hover:bg-transparent + disabled:bg-transparent disabled:text-content-disabled + `, }, size: { - lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg [&>img]:size-icon-lg", - sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm [&>img]:size-icon-sm", + lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg [&_img]:size-icon-lg", + sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm [&_img]:size-icon-sm", xs: "min-w-8 py-1 px-2 text-2xs rounded-md", - icon: "size-8 px-1.5 [&_svg]:size-icon-sm [&>img]:size-icon-sm", - "icon-lg": "size-10 px-2 [&_svg]:size-icon-lg [&>img]:size-icon-lg", + icon: "size-8 px-1.5 [&_svg]:size-icon-sm [&_img]:size-icon-sm", + "icon-lg": "size-10 px-2 [&_svg]:size-icon-lg [&_img]:size-icon-lg", }, }, defaultVariants: { diff --git a/site/src/components/DropdownMenu/DropdownMenu.tsx b/site/src/components/DropdownMenu/DropdownMenu.tsx index 8d9fb12d774a3..01547c30b17a6 100644 --- a/site/src/components/DropdownMenu/DropdownMenu.tsx +++ b/site/src/components/DropdownMenu/DropdownMenu.tsx @@ -109,9 +109,15 @@ export const DropdownMenuItem = forwardRef< ref={ref} className={cn( [ - "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-2 text-sm text-content-secondary font-medium outline-none transition-colors", - "focus:bg-surface-secondary focus:text-content-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - "[&>svg]:size-4 [&>svg]:shrink-0 no-underline", + ` + relative flex cursor-default select-none items-center gap-2 rounded-sm + px-2 py-1.5 text-sm text-content-secondary font-medium outline-none + no-underline + focus:bg-surface-secondary focus:text-content-primary + data-[disabled]:pointer-events-none data-[disabled]:opacity-50 + [&_svg]:size-icon-sm [&>svg]:shrink-0 + [&_img]:size-icon-sm [&>img]:shrink-0 + `, inset && "pl-8", ], className, diff --git a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx index 80ed2c9c8111e..1ee5206ee43e6 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx @@ -19,6 +19,7 @@ const createTemplateVersionParameter = ( name: "first_parameter", description: "This is first parameter.", type: "string", + form_type: "input", mutable: true, default_value: "default string", icon: "/icon/folder.svg", diff --git a/site/src/modules/apps/apps.test.ts b/site/src/modules/apps/apps.test.ts index 517bc047ee815..e61b214a25385 100644 --- a/site/src/modules/apps/apps.test.ts +++ b/site/src/modules/apps/apps.test.ts @@ -81,7 +81,7 @@ describe("getAppHref", () => { path: "/path-base", }); expect(href).toBe( - `/path-base/@${MockWorkspace.owner_username}/Test-Workspace.a-workspace-agent/apps/${app.slug}/`, + `/path-base/@${MockWorkspace.owner_name}/Test-Workspace.a-workspace-agent/apps/${app.slug}/`, ); }); @@ -97,7 +97,7 @@ describe("getAppHref", () => { path: "", }); expect(href).toBe( - `/@${MockWorkspace.owner_username}/Test-Workspace.a-workspace-agent/terminal?command=ls%20-la`, + `/@${MockWorkspace.owner_name}/Test-Workspace.a-workspace-agent/terminal?command=ls%20-la`, ); }); @@ -129,7 +129,7 @@ describe("getAppHref", () => { path: "/path-base", }); expect(href).toBe( - `/path-base/@${MockWorkspace.owner_username}/Test-Workspace.a-workspace-agent/apps/${app.slug}/`, + `/path-base/@${MockWorkspace.owner_name}/Test-Workspace.a-workspace-agent/apps/${app.slug}/`, ); }); }); diff --git a/site/src/modules/apps/apps.ts b/site/src/modules/apps/apps.ts index 47e4447725481..d154b632dc1ca 100644 --- a/site/src/modules/apps/apps.ts +++ b/site/src/modules/apps/apps.ts @@ -105,7 +105,7 @@ export const getAppHref = ( // Terminal links are relative. The terminal page knows how // to select the correct workspace proxy for the websocket // connection. - return `/@${workspace.owner_username}/${workspace.name}.${ + return `/@${workspace.owner_name}/${workspace.name}.${ agent.name }/terminal?command=${encodeURIComponent(app.command)}`; } @@ -119,7 +119,7 @@ export const getAppHref = ( // The backend redirects if the trailing slash isn't included, so we add it // here to avoid extra roundtrips. - return `${path}/@${workspace.owner_username}/${workspace.name}.${ + return `${path}/@${workspace.owner_name}/${workspace.name}.${ agent.name }/apps/${encodeURIComponent(app.slug)}/`; }; diff --git a/site/src/modules/resources/AgentDevcontainerCard.tsx b/site/src/modules/resources/AgentDevcontainerCard.tsx index a3c3e02ac8d8a..4891c632bbc2a 100644 --- a/site/src/modules/resources/AgentDevcontainerCard.tsx +++ b/site/src/modules/resources/AgentDevcontainerCard.tsx @@ -142,7 +142,7 @@ export const AgentDevcontainerCard: FC = ({
= ({ workspaceName={workspace.name} agentName={agent.name} containerName={container.name} - userName={workspace.owner_username} + userName={workspace.owner_name} /> {wildcardHostname !== "" && container.ports.map((port) => { @@ -170,7 +170,7 @@ export const AgentDevcontainerCard: FC = ({ port.host_port, agent.name, workspace.name, - workspace.owner_username, + workspace.owner_name, location.protocol === "https" ? "https" : "http", ) : ""; diff --git a/site/src/modules/resources/AgentMetadata.tsx b/site/src/modules/resources/AgentMetadata.tsx index f8eba334f09ce..5c6986a4dc618 100644 --- a/site/src/modules/resources/AgentMetadata.tsx +++ b/site/src/modules/resources/AgentMetadata.tsx @@ -42,24 +42,24 @@ export const AgentMetadataView: FC = ({ metadata }) => { interface AgentMetadataProps { agent: WorkspaceAgent; - storybookMetadata?: WorkspaceAgentMetadata[]; + initialMetadata?: WorkspaceAgentMetadata[]; } const maxSocketErrorRetryCount = 3; export const AgentMetadata: FC = ({ agent, - storybookMetadata, + initialMetadata, }) => { - const [activeMetadata, setActiveMetadata] = useState(storybookMetadata); + const [activeMetadata, setActiveMetadata] = useState(initialMetadata); useEffect(() => { // This is an unfortunate pitfall with this component's testing setup, - // but even though we use the value of storybookMetadata as the initial + // but even though we use the value of initialMetadata as the initial // value of the activeMetadata, we cannot put activeMetadata itself into // the dependency array. If we did, we would destroy and rebuild each // connection every single time a new message comes in from the socket, // because the socket has to be wired up to the state setter - if (storybookMetadata !== undefined) { + if (initialMetadata !== undefined) { return; } @@ -118,7 +118,7 @@ export const AgentMetadata: FC = ({ window.clearTimeout(timeoutId); activeSocket?.close(); }; - }, [agent.id, storybookMetadata]); + }, [agent.id, initialMetadata]); if (activeMetadata === undefined) { return ( diff --git a/site/src/modules/resources/AgentRow.stories.tsx b/site/src/modules/resources/AgentRow.stories.tsx index c08d28f295cd9..4444dbbac1c77 100644 --- a/site/src/modules/resources/AgentRow.stories.tsx +++ b/site/src/modules/resources/AgentRow.stories.tsx @@ -1,4 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { spyOn, userEvent, within } from "@storybook/test"; +import { API } from "api/api"; import { getPreferredProxy } from "contexts/ProxyContext"; import { chromatic } from "testHelpers/chromatic"; import * as M from "testHelpers/entities"; @@ -95,8 +97,7 @@ const meta: Meta = { logs_length: logs.length, }, workspace: M.MockWorkspace, - showApps: true, - storybookAgentMetadata: defaultAgentMetadata, + initialMetadata: defaultAgentMetadata, }, decorators: [withProxyProvider(), withDashboardProvider, withWebSocket], parameters: { @@ -121,24 +122,6 @@ type Story = StoryObj; export const Example: Story = {}; -export const HideSSHButton: Story = { - args: { - hideSSHButton: true, - }, -}; - -export const HideVSCodeDesktopButton: Story = { - args: { - hideVSCodeDesktopButton: true, - }, -}; - -export const NotShowingApps: Story = { - args: { - showApps: false, - }, -}; - export const BunchOfApps: Story = { args: { agent: { @@ -155,14 +138,13 @@ export const BunchOfApps: Story = { ], }, workspace: M.MockWorkspace, - showApps: true, }, }; export const Connecting: Story = { args: { agent: M.MockWorkspaceAgentConnecting, - storybookAgentMetadata: [], + initialMetadata: [], }, }; @@ -190,7 +172,7 @@ export const Started: Story = { export const StartedNoMetadata: Story = { args: { ...Started.args, - storybookAgentMetadata: [], + initialMetadata: [], }, }; @@ -243,20 +225,30 @@ export const ShowingPortForward: Story = { }; export const Outdated: Story = { + beforeEach: () => { + spyOn(API, "getBuildInfo").mockResolvedValue({ + ...M.MockBuildInfo, + version: "v99.999.9999+c1cdf14", + agent_api_version: "1.0", + }); + }, args: { agent: M.MockWorkspaceAgentOutdated, workspace: M.MockWorkspace, - serverVersion: "v99.999.9999+c1cdf14", - serverAPIVersion: "1.0", }, }; export const Deprecated: Story = { + beforeEach: () => { + spyOn(API, "getBuildInfo").mockResolvedValue({ + ...M.MockBuildInfo, + version: "v99.999.9999+c1cdf14", + agent_api_version: "2.0", + }); + }, args: { agent: M.MockWorkspaceAgentDeprecated, workspace: M.MockWorkspace, - serverVersion: "v99.999.9999+c1cdf14", - serverAPIVersion: "2.0", }, }; @@ -273,3 +265,22 @@ export const HideApp: Story = { }, }, }; + +export const GroupApp: Story = { + args: { + agent: { + ...M.MockWorkspaceAgent, + apps: [ + { + ...M.MockWorkspaceApp, + group: "group", + }, + ], + }, + }, + + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByText("group")); + }, +}; diff --git a/site/src/modules/resources/AgentRow.test.tsx b/site/src/modules/resources/AgentRow.test.tsx index 55be57bbc2c2b..55b14704ad7a6 100644 --- a/site/src/modules/resources/AgentRow.test.tsx +++ b/site/src/modules/resources/AgentRow.test.tsx @@ -1,159 +1,31 @@ -import { screen } from "@testing-library/react"; -import { - MockTemplate, - MockWorkspace, - MockWorkspaceAgent, - MockWorkspaceApp, -} from "testHelpers/entities"; -import { - renderWithAuth, - waitForLoaderToBeRemoved, -} from "testHelpers/renderHelpers"; -import type { AgentRowProps } from "./AgentRow"; -import { AgentRow } from "./AgentRow"; -import { DisplayAppNameMap } from "./AppLink/AppLink"; +import { MockWorkspaceApp } from "testHelpers/entities"; +import { organizeAgentApps } from "./AgentRow"; -jest.mock("modules/resources/AgentMetadata", () => { - const AgentMetadata = () => <>; - return { AgentMetadata }; -}); - -describe.each<{ - result: "visible" | "hidden"; - props: Partial; -}>([ - { - result: "visible", - props: { - showApps: true, - agent: { - ...MockWorkspaceAgent, - display_apps: ["vscode", "vscode_insiders"], - status: "connected", - }, - hideVSCodeDesktopButton: false, - }, - }, - { - result: "hidden", - props: { - showApps: false, - agent: { - ...MockWorkspaceAgent, - display_apps: ["vscode", "vscode_insiders"], - status: "connected", - }, - hideVSCodeDesktopButton: false, - }, - }, - { - result: "hidden", - props: { - showApps: true, - agent: { - ...MockWorkspaceAgent, - display_apps: [], - status: "connected", - }, - hideVSCodeDesktopButton: false, - }, - }, - { - result: "hidden", - props: { - showApps: true, - agent: { - ...MockWorkspaceAgent, - display_apps: ["vscode", "vscode_insiders"], - status: "disconnected", - }, - hideVSCodeDesktopButton: false, - }, - }, - { - result: "hidden", - props: { - showApps: true, - agent: { - ...MockWorkspaceAgent, - display_apps: ["vscode", "vscode_insiders"], - status: "connected", - }, - hideVSCodeDesktopButton: true, - }, - }, -])("VSCode button visibility", ({ props: testProps, result }) => { - const props: AgentRowProps = { - agent: MockWorkspaceAgent, - workspace: MockWorkspace, - template: MockTemplate, - showApps: false, - serverVersion: "", - serverAPIVersion: "", - onUpdateAgent: () => { - throw new Error("Function not implemented."); - }, - ...testProps, - }; - - test(`visibility: ${result}, showApps: ${props.showApps}, hideVSCodeDesktopButton: ${props.hideVSCodeDesktopButton}, display apps: ${props.agent.display_apps}`, async () => { - renderWithAuth(); - await waitForLoaderToBeRemoved(); +describe("organizeAgentApps", () => { + test("returns one ungrouped app", () => { + const result = organizeAgentApps([{ ...MockWorkspaceApp }]); - if (result === "visible") { - expect(screen.getByText(DisplayAppNameMap.vscode)).toBeVisible(); - } else { - expect(screen.queryByText(DisplayAppNameMap.vscode)).toBeNull(); - } + expect(result).toEqual([{ apps: [MockWorkspaceApp] }]); }); -}); - -describe.each<{ - props: Partial; -}>([ - { - props: { - agent: { - ...MockWorkspaceAgent, - apps: [ - { - ...MockWorkspaceApp, - display_name: `${MockWorkspaceApp.display_name} Not Hidden`, - hidden: false, - }, - { - ...MockWorkspaceApp, - display_name: `${MockWorkspaceApp.display_name} Is Hidden`, - hidden: true, - }, - ], - }, - }, - }, -])("hidden hides App button", ({ props: testProps }) => { - const props: AgentRowProps = { - agent: MockWorkspaceAgent, - workspace: MockWorkspace, - template: MockTemplate, - showApps: true, - serverVersion: "", - serverAPIVersion: "", - onUpdateAgent: () => { - throw new Error("Function not implemented."); - }, - ...testProps, - }; - test(`apps: ${props.agent.apps}`, async () => { - renderWithAuth(); - await waitForLoaderToBeRemoved(); + test("handles ordering correctly", () => { + const bugApp = { ...MockWorkspaceApp, slug: "bug", group: "creatures" }; + const birdApp = { ...MockWorkspaceApp, slug: "bird", group: "creatures" }; + const fishApp = { ...MockWorkspaceApp, slug: "fish", group: "creatures" }; + const riderApp = { ...MockWorkspaceApp, slug: "rider" }; + const zedApp = { ...MockWorkspaceApp, slug: "zed" }; + const result = organizeAgentApps([ + bugApp, + riderApp, + birdApp, + zedApp, + fishApp, + ]); - for (const app of props.agent.apps) { - if (app.hidden) { - expect(screen.queryByText(app.display_name as string)).toBeNull(); - } else { - expect(screen.getByText(app.display_name as string)).toBeVisible(); - } - } + expect(result).toEqual([ + { group: "creatures", apps: [bugApp, birdApp, fishApp] }, + { apps: [riderApp] }, + { apps: [zedApp] }, + ]); }); }); diff --git a/site/src/modules/resources/AgentRow.tsx b/site/src/modules/resources/AgentRow.tsx index 40b6dcf3bea71..407c8c1bd84c6 100644 --- a/site/src/modules/resources/AgentRow.tsx +++ b/site/src/modules/resources/AgentRow.tsx @@ -9,12 +9,20 @@ import type { Workspace, WorkspaceAgent, WorkspaceAgentMetadata, + WorkspaceApp, } from "api/typesGenerated"; import { isAxiosError } from "axios"; import { DropdownArrow } from "components/DropdownArrow/DropdownArrow"; -import type { Line } from "components/Logs/LogLine"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "components/DropdownMenu/DropdownMenu"; import { Stack } from "components/Stack/Stack"; import { useProxy } from "contexts/ProxyContext"; +import { Folder } from "lucide-react"; +import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { AppStatuses } from "pages/WorkspacePage/AppStatuses"; import { type FC, @@ -28,6 +36,7 @@ import { import { useQuery } from "react-query"; import AutoSizer from "react-virtualized-auto-sizer"; import type { FixedSizeList as List, ListOnScrollProps } from "react-window"; +import { AgentButton } from "./AgentButton"; import { AgentDevcontainerCard } from "./AgentDevcontainerCard"; import { AgentLatency } from "./AgentLatency"; import { AGENT_LOG_LINE_HEIGHT } from "./AgentLogs/AgentLogLine"; @@ -43,46 +52,32 @@ import { TerminalLink } from "./TerminalLink/TerminalLink"; import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton"; import { useAgentLogs } from "./useAgentLogs"; -export interface AgentRowProps { +interface AgentRowProps { agent: WorkspaceAgent; workspace: Workspace; - showApps: boolean; - showBuiltinApps?: boolean; - sshPrefix?: string; - hideSSHButton?: boolean; - hideVSCodeDesktopButton?: boolean; - serverVersion: string; - serverAPIVersion: string; - onUpdateAgent: () => void; template: Template; - storybookAgentMetadata?: WorkspaceAgentMetadata[]; + initialMetadata?: WorkspaceAgentMetadata[]; + onUpdateAgent: () => void; } export const AgentRow: FC = ({ agent, workspace, template, - showApps, - showBuiltinApps = true, - hideSSHButton, - hideVSCodeDesktopButton, - serverVersion, - serverAPIVersion, onUpdateAgent, - storybookAgentMetadata, - sshPrefix, + initialMetadata, }) => { - // Apps visibility - const visibleApps = agent.apps.filter((app) => !app.hidden); - const hasAppsToDisplay = !hideVSCodeDesktopButton || visibleApps.length > 0; + const { browser_only } = useFeatureVisibility(); + const appSections = organizeAgentApps(agent.apps); + const hasAppsToDisplay = + !browser_only || appSections.some((it) => it.apps.length > 0); const shouldDisplayApps = - showApps && - ((agent.status === "connected" && hasAppsToDisplay) || - agent.status === "connecting"); + (agent.status === "connected" && hasAppsToDisplay) || + agent.status === "connecting"; const hasVSCodeApp = agent.display_apps.includes("vscode") || agent.display_apps.includes("vscode_insiders"); - const showVSCode = hasVSCodeApp && !hideVSCodeDesktopButton; + const showVSCode = hasVSCodeApp && !browser_only; const hasStartupFeatures = Boolean(agent.logs_length); const { proxy } = useProxy(); @@ -184,12 +179,7 @@ export const AgentRow: FC = ({
{agent.status === "connected" && ( <> - + )} @@ -201,28 +191,23 @@ export const AgentRow: FC = ({ )} - {showBuiltinApps && ( -
- {!hideSSHButton && agent.display_apps.includes("ssh_helper") && ( - + {!browser_only && agent.display_apps.includes("ssh_helper") && ( + + )} + {proxy.preferredWildcardHostname !== "" && + agent.display_apps.includes("port_forwarding_helper") && ( + )} - {proxy.preferredWildcardHostname !== "" && - agent.display_apps.includes("port_forwarding_helper") && ( - - )} -
- )} +
@@ -239,17 +224,17 @@ export const AgentRow: FC = ({ <> {showVSCode && ( )} - {visibleApps.map((app) => ( - ( + @@ -257,11 +242,11 @@ export const AgentRow: FC = ({ )} - {showBuiltinApps && agent.display_apps.includes("web_terminal") && ( + {agent.display_apps.includes("web_terminal") && ( )} @@ -300,10 +285,7 @@ export const AgentRow: FC = ({ )} - +
{hasStartupFeatures && ( @@ -322,7 +304,7 @@ export const AgentRow: FC = ({ width={width} css={styles.startupLogs} onScroll={handleLogScroll} - logs={startupLogs.map((l) => ({ + logs={startupLogs.map((l) => ({ id: l.id, level: l.level, output: l.output, @@ -353,6 +335,93 @@ export const AgentRow: FC = ({ ); }; +type AppSection = { + /** + * If there is no `group`, just render all of the apps inline. If there is a + * group name, show them all in a dropdown. + */ + group?: string; + + apps: WorkspaceApp[]; +}; + +/** + * organizeAgentApps returns an ordering of agent apps that accounts for + * grouping. When we receive the list of apps from the backend, they have + * already been "ordered" by their `order` attribute, but we are not given that + * value. We must be careful to preserve that ordering, while also properly + * grouping together all apps of any given group. + * + * The position of the group overall is determined by the `order` position of + * the first app in the group. There may be several sections returned without + * a group name, to allow placing grouped apps in between non-grouped apps. Not + * every ungrouped section is expected to have a group in between, to make the + * algorithm a little simpler to implement. + */ +export function organizeAgentApps(apps: readonly WorkspaceApp[]): AppSection[] { + let currentSection: AppSection | undefined = undefined; + const appGroups: AppSection[] = []; + const groupsByName = new Map(); + + for (const app of apps) { + if (app.hidden) { + continue; + } + + if (!currentSection || app.group !== currentSection.group) { + const existingSection = groupsByName.get(app.group!); + if (existingSection) { + currentSection = existingSection; + } else { + currentSection = { + group: app.group, + apps: [], + }; + appGroups.push(currentSection); + if (app.group) { + groupsByName.set(app.group, currentSection); + } + } + } + + currentSection.apps.push(app); + } + + return appGroups; +} + +type AppsProps = { + section: AppSection; + agent: WorkspaceAgent; + workspace: Workspace; +}; + +const Apps: FC = ({ section, agent, workspace }) => { + return section.group ? ( + + + + + {section.group} + + + + {section.apps.map((app) => ( + + + + ))} + + + ) : ( + <> + {section.apps.map((app) => ( + + ))} + + ); +}; + const styles = { agentRow: (theme) => ({ fontSize: 14, diff --git a/site/src/modules/resources/AgentVersion.tsx b/site/src/modules/resources/AgentVersion.tsx index 744bb7077a36e..0111bdde1f379 100644 --- a/site/src/modules/resources/AgentVersion.tsx +++ b/site/src/modules/resources/AgentVersion.tsx @@ -1,26 +1,27 @@ +import { buildInfo } from "api/queries/buildInfo"; import type { WorkspaceAgent } from "api/typesGenerated"; +import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import type { FC } from "react"; +import { useQuery } from "react-query"; import { agentVersionStatus, getDisplayVersionStatus } from "utils/workspace"; import { AgentOutdatedTooltip } from "./AgentOutdatedTooltip"; interface AgentVersionProps { agent: WorkspaceAgent; - serverVersion: string; - serverAPIVersion: string; onUpdate: () => void; } -export const AgentVersion: FC = ({ - agent, - serverVersion, - serverAPIVersion, - onUpdate, -}) => { +export const AgentVersion: FC = ({ agent, onUpdate }) => { + const { metadata } = useEmbeddedMetadata(); + const { data: build } = useQuery(buildInfo(metadata["build-info"])); + const serverVersion = build?.version ?? ""; + const apiServerVersion = build?.agent_api_version ?? ""; + const { status } = getDisplayVersionStatus( agent.version, serverVersion, agent.api_version, - serverAPIVersion, + apiServerVersion, ); if (status === agentVersionStatus.Updated) { diff --git a/site/src/modules/resources/AppLink/AppLink.tsx b/site/src/modules/resources/AppLink/AppLink.tsx index 74542abc710aa..637f0287a4088 100644 --- a/site/src/modules/resources/AppLink/AppLink.tsx +++ b/site/src/modules/resources/AppLink/AppLink.tsx @@ -1,5 +1,6 @@ import { useTheme } from "@emotion/react"; import type * as TypesGen from "api/typesGenerated"; +import { DropdownMenuItem } from "components/DropdownMenu/DropdownMenu"; import { Spinner } from "components/Spinner/Spinner"; import { Tooltip, @@ -28,9 +29,15 @@ interface AppLinkProps { workspace: TypesGen.Workspace; app: TypesGen.WorkspaceApp; agent: TypesGen.WorkspaceAgent; + grouped?: boolean; } -export const AppLink: FC = ({ app, workspace, agent }) => { +export const AppLink: FC = ({ + app, + workspace, + agent, + grouped, +}) => { const { proxy } = useProxy(); const host = proxy.preferredWildcardHostname; const [iconError, setIconError] = useState(false); @@ -90,7 +97,15 @@ export const AppLink: FC = ({ app, workspace, agent }) => { const canShare = app.sharing_level !== "owner"; - const button = ( + const button = grouped ? ( + + + {icon} + {link.label} + {canShare && } + + + ) : ( {icon} diff --git a/site/src/modules/resources/PortForwardButton.stories.tsx b/site/src/modules/resources/PortForwardButton.stories.tsx index 76de2903e83cf..09cfcc8796b51 100644 --- a/site/src/modules/resources/PortForwardButton.stories.tsx +++ b/site/src/modules/resources/PortForwardButton.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { MockListeningPortsResponse, MockSharedPortsResponse, + MockTemplate, MockWorkspace, MockWorkspaceAgent, } from "testHelpers/entities"; @@ -14,6 +15,8 @@ const meta: Meta = { decorators: [withDashboardProvider], args: { agent: MockWorkspaceAgent, + workspace: MockWorkspace, + template: MockTemplate, }, }; diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index 916c8f2338130..e9ca8b6757973 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -17,9 +17,10 @@ import { } from "api/queries/workspaceportsharing"; import { type Template, - type UpsertWorkspaceAgentPortShareRequest, + type Workspace, type WorkspaceAgent, type WorkspaceAgentListeningPort, + type WorkspaceAgentPortShare, type WorkspaceAgentPortShareLevel, type WorkspaceAgentPortShareProtocol, WorkspaceAppSharingLevels, @@ -42,7 +43,7 @@ import { PopoverContent, PopoverTrigger, } from "components/deprecated/Popover/Popover"; -import { type FormikContextType, useFormik } from "formik"; +import { useFormik } from "formik"; import { type ClassName, useClassName } from "hooks/useClassName"; import { ChevronDownIcon, @@ -64,31 +65,40 @@ import * as Yup from "yup"; interface PortForwardButtonProps { host: string; - username: string; - workspaceName: string; - workspaceID: string; + workspace: Workspace; agent: WorkspaceAgent; template: Template; } -export const PortForwardButton: FC = (props) => { - const { agent } = props; +export const PortForwardButton: FC = ({ + host, + workspace, + template, + agent, +}) => { const { entitlements } = useDashboard(); const paper = useClassName(classNames.paper, []); - const portsQuery = useQuery({ + const { data: listeningPorts } = useQuery({ queryKey: ["portForward", agent.id], queryFn: () => API.getAgentListeningPorts(agent.id), enabled: agent.status === "connected", refetchInterval: 5_000, + select: (res) => res.ports, + }); + + const { data: sharedPorts, refetch: refetchSharedPorts } = useQuery({ + ...workspacePortShares(workspace.id), + enabled: agent.status === "connected", + select: (res) => res.shares, }); return ( - {content} @@ -303,26 +323,67 @@ const TaskApps: FC = ({ task }) => { return src; }); + const embeddedApps = apps.filter((app) => !app.external); + const externalApps = apps.filter((app) => app.external); + return (
- {apps.map((app) => ( - { - if (app.external) { - return; - } - - e.preventDefault(); - setActiveAppId(app.id); - setIframeSrc(e.currentTarget.href); - }} - /> - ))} + {embeddedApps + .filter((app) => !app.external) + .map((app) => ( + { + if (app.external) { + return; + } + + e.preventDefault(); + setActiveAppId(app.id); + setIframeSrc(e.currentTarget.href); + }} + /> + ))} + + {externalApps.length > 0 && ( +
+ + + + + + {externalApps + .filter((app) => app.external) + .map((app) => { + const link = useAppLink(app, { + agent, + workspace: task.workspace, + }); + + return ( + + + {app.icon ? ( + + ) : ( + + )} + {link.label} + + + ); + })} + + +
+ )}
diff --git a/site/src/pages/TasksPage/TasksPage.tsx b/site/src/pages/TasksPage/TasksPage.tsx index 138c6d3322a18..c4a0ae897bd37 100644 --- a/site/src/pages/TasksPage/TasksPage.tsx +++ b/site/src/pages/TasksPage/TasksPage.tsx @@ -222,7 +222,7 @@ const TaskForm: FC = ({ templates }) => { return ( - {template.display_name ?? template.name} + {template.display_name || template.name} ); @@ -318,7 +318,7 @@ const TasksTable: FC = ({ templates }) => { {prompt} Access task @@ -341,7 +341,7 @@ const TasksTable: FC = ({ templates }) => { {relativeTime(new Date(workspace.created_at))} diff --git a/site/src/pages/TerminalPage/TerminalPage.stories.tsx b/site/src/pages/TerminalPage/TerminalPage.stories.tsx index cda8437042c70..298f890637042 100644 --- a/site/src/pages/TerminalPage/TerminalPage.stories.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.stories.tsx @@ -28,7 +28,7 @@ import TerminalPage from "./TerminalPage"; const createWorkspaceWithAgent = (lifecycle: WorkspaceAgentLifecycle) => { return { key: workspaceByOwnerAndNameKey( - MockWorkspace.owner_username, + MockWorkspace.owner_name, MockWorkspace.name, ), data: { @@ -54,7 +54,7 @@ const meta = { reactRouter: reactRouterParameters({ location: { pathParams: { - username: `@${MockWorkspace.owner_username}`, + username: `@${MockWorkspace.owner_name}`, workspace: MockWorkspace.name, }, }, diff --git a/site/src/pages/TerminalPage/TerminalPage.tsx b/site/src/pages/TerminalPage/TerminalPage.tsx index 4fd88556d7dbf..2023bdb0eeb29 100644 --- a/site/src/pages/TerminalPage/TerminalPage.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.tsx @@ -332,7 +332,7 @@ const TerminalPage: FC = () => { {workspace.data ? pageTitle( - `Terminal · ${workspace.data.owner_username}/${workspace.data.name}`, + `Terminal · ${workspace.data.owner_name}/${workspace.data.name}`, ) : ""} diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx index 20f79c0e389bc..f1f1edb54a5f7 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx @@ -21,12 +21,12 @@ describe("WorkspaceBuildPage", () => { .spyOn(API, "getWorkspaceBuildByNumber") .mockResolvedValue(MockWorkspaceBuild); renderWithAuth(, { - route: `/@${MockWorkspace.owner_username}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}`, + route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}`, path: "/:username/:workspace/builds/:buildNumber", }); await waitFor(() => expect(getWorkspaceBuildSpy).toBeCalledWith( - MockWorkspace.owner_username, + MockWorkspace.owner_name, MockWorkspace.name, MockWorkspaceBuild.build_number, ), @@ -52,7 +52,7 @@ describe("WorkspaceBuildPage", () => { client.onmessage = async () => { renderWithAuth(, { - route: `/@${MockWorkspace.owner_username}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}`, + route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}`, path: "/:username/:workspace/builds/:buildNumber", }); @@ -70,7 +70,7 @@ describe("WorkspaceBuildPage", () => { }/logs?follow&after=0`, ); renderWithAuth(, { - route: `/@${MockWorkspace.owner_username}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}?${LOGS_TAB_KEY}=${MockWorkspaceAgent.id}`, + route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}?${LOGS_TAB_KEY}=${MockWorkspaceAgent.id}`, path: "/:username/:workspace/builds/:buildNumber", }); await screen.findByText(`Build #${MockWorkspaceBuild.build_number}`); diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 1c508481d174b..978a58e8cb0e1 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -77,7 +77,6 @@ export const Running: Story = { }, handleStart: action("start"), handleStop: action("stop"), - buildInfo: Mocks.MockBuildInfo, template: Mocks.MockTemplate, }, }; @@ -219,7 +218,6 @@ export const RunningWithAppStatuses: Story = { }, handleStart: action("start"), handleStop: action("stop"), - buildInfo: Mocks.MockBuildInfo, template: Mocks.MockTemplate, }, }; diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index f68cafbe9e320..65c924354ceb0 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -31,10 +31,6 @@ interface WorkspaceProps { permissions: WorkspacePermissions; isUpdating: boolean; isRestarting: boolean; - hideSSHButton?: boolean; - hideVSCodeDesktopButton?: boolean; - buildInfo?: TypesGen.BuildInfoResponse; - sshPrefix?: string; buildLogs?: TypesGen.ProvisionerJobLog[]; latestVersion?: TypesGen.TemplateVersion; timings?: TypesGen.WorkspaceBuildTimings; @@ -56,10 +52,6 @@ export const Workspace: FC = ({ workspace, isUpdating, isRestarting, - hideSSHButton, - hideVSCodeDesktopButton, - buildInfo, - sshPrefix, template, buildLogs, latestVersion, @@ -252,13 +244,6 @@ export const Workspace: FC = ({ agent={agent} workspace={workspace} template={template} - sshPrefix={sshPrefix} - showApps={permissions.updateWorkspace} - showBuiltinApps={permissions.updateWorkspace} - hideSSHButton={hideSSHButton} - hideVSCodeDesktopButton={hideVSCodeDesktopButton} - serverVersion={buildInfo?.version || ""} - serverAPIVersion={buildInfo?.agent_api_version || ""} onUpdateAgent={handleUpdate} // On updating the workspace the agent version is also updated /> ))} diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index a71e6636845ee..3f217a86a3aad 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -54,7 +54,7 @@ const renderWorkspacePage = async ( renderWithAuth(, { ...options, - route: `/@${workspace.owner_username}/${workspace.name}`, + route: `/@${workspace.owner_name}/${workspace.name}`, path: "/:username/:workspace", }); diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 1b4625de9c4ee..7f12c70b8be54 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -1,7 +1,5 @@ import { API } from "api/api"; import { getErrorMessage } from "api/errors"; -import { buildInfo } from "api/queries/buildInfo"; -import { deploymentSSHConfig } from "api/queries/deployment"; import { templateVersion } from "api/queries/templates"; import { workspaceBuildTimings } from "api/queries/workspaceBuilds"; import { @@ -18,9 +16,7 @@ import { type ConfirmDialogProps, } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { displayError } from "components/GlobalSnackbar/utils"; -import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs"; -import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { WorkspaceUpdateDialogs, useWorkspaceUpdate, @@ -43,10 +39,7 @@ export const WorkspaceReadyPage: FC = ({ template, permissions, }) => { - const { metadata } = useEmbeddedMetadata(); - const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); const queryClient = useQueryClient(); - const featureVisibility = useFeatureVisibility(); // Build logs const shouldStreamBuildLogs = workspace.latest_build.status !== "running"; @@ -65,9 +58,6 @@ export const WorkspaceReadyPage: FC = ({ mutationFn: API.restartWorkspace, }); - // SSH Prefix - const sshPrefixQuery = useQuery(deploymentSSHConfig()); - // Favicon const favicon = getFaviconByStatus(workspace.latest_build); const [faviconTheme, setFaviconTheme] = useState<"light" | "dark">("dark"); @@ -184,9 +174,7 @@ export const WorkspaceReadyPage: FC = ({ return ( <> - - {pageTitle(`${workspace.owner_username}/${workspace.name}`)} - + {pageTitle(`${workspace.owner_name}/${workspace.name}`)} = ({ isRestarting={isRestarting} workspace={workspace} latestVersion={latestVersion} - hideSSHButton={featureVisibility.browser_only} - hideVSCodeDesktopButton={featureVisibility.browser_only} - buildInfo={buildInfoQuery.data} - sshPrefix={sshPrefixQuery.data?.hostname_prefix} template={template} buildLogs={buildLogs} timings={timingsQuery.data} diff --git a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx index cddc001b9ead3..225db7c8a44c0 100644 --- a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx @@ -13,7 +13,7 @@ import { WorkspaceScheduleControls } from "./WorkspaceScheduleControls"; const Wrapper: FC = () => { const { data: workspace } = useQuery( - workspaceByOwnerAndName(MockWorkspace.owner_username, MockWorkspace.name), + workspaceByOwnerAndName(MockWorkspace.owner_name, MockWorkspace.name), ); if (!workspace) { diff --git a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx index 4d117c39ee05a..5bced6f668d0f 100644 --- a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx @@ -123,7 +123,7 @@ const AutostopDisplay: FC = ({ const updateWorkspaceDeadlineQueryData = (deadline: Dayjs) => { queryClient.setQueryData( - workspaceByOwnerAndNameKey(workspace.owner_username, workspace.name), + workspaceByOwnerAndNameKey(workspace.owner_name, workspace.name), { ...workspace, latest_build: { diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx index 12d2f43f4fd04..943b967de92c6 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx @@ -73,7 +73,7 @@ export const WorkspaceTopbar: FC = ({ // Quota const hasDailyCost = workspace.latest_build.daily_cost > 0; const { data: quota } = useQuery({ - ...workspaceQuota(workspace.organization_name, workspace.owner_username), + ...workspaceQuota(workspace.organization_name, workspace.owner_name), // Don't need to tie the enabled condition to showOrganizations because // even if the customer hasn't enabled the orgs enterprise feature, all @@ -116,7 +116,7 @@ export const WorkspaceTopbar: FC = ({
diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index 67fe21c9d57c2..6dac6536b5bfb 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -39,7 +39,7 @@ const WorkspaceParametersPage: FC = () => { rich_parameter_values: buildParameters, }), onSuccess: () => { - navigate(`/${workspace.owner_username}/${workspace.name}`); + navigate(`/${workspace.owner_name}/${workspace.name}`); }, }); diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx index b0af993c3b921..e80542f3144da 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx @@ -26,6 +26,7 @@ import { useMutation, useQuery } from "react-query"; import { useNavigate } from "react-router-dom"; import { docs } from "utils/docs"; import { pageTitle } from "utils/page"; +import type { AutofillBuildParameter } from "utils/richParameters"; import { type WorkspacePermissions, workspaceChecks, @@ -39,11 +40,27 @@ const WorkspaceParametersPageExperimental: FC = () => { const navigate = useNavigate(); const experimentalFormContext = useContext(ExperimentalFormContext); + // autofill the form with the workspace build parameters from the latest build + const { + data: latestBuildParameters, + isLoading: latestBuildParametersLoading, + } = useQuery({ + queryKey: ["workspaceBuilds", workspace.latest_build.id, "parameters"], + queryFn: () => API.getWorkspaceBuildParameters(workspace.latest_build.id), + }); + const [latestResponse, setLatestResponse] = useState(null); const wsResponseId = useRef(-1); const ws = useRef(null); const [wsError, setWsError] = useState(null); + const initialParamsSentRef = useRef(false); + + const autofillParameters: AutofillBuildParameter[] = + latestBuildParameters?.map((p) => ({ + ...p, + source: "active_build", + })) ?? []; const sendMessage = useEffectEvent((formValues: Record) => { const request: DynamicParametersRequest = { @@ -57,11 +74,34 @@ const WorkspaceParametersPageExperimental: FC = () => { } }); + // On page load, sends initial workspace build parameters to the websocket. + // This ensures the backend has the form's complete initial state, + // vital for rendering dynamic UI elements dependent on initial parameter values. + const sendInitialParameters = useEffectEvent(() => { + if (initialParamsSentRef.current) return; + if (autofillParameters.length === 0) return; + + const initialParamsToSend: Record = {}; + for (const param of autofillParameters) { + if (param.name && param.value) { + initialParamsToSend[param.name] = param.value; + } + } + if (Object.keys(initialParamsToSend).length === 0) return; + + sendMessage(initialParamsToSend); + initialParamsSentRef.current = true; + }); + const onMessage = useEffectEvent((response: DynamicParametersResponse) => { if (latestResponse && latestResponse?.id >= response.id) { return; } + if (!initialParamsSentRef.current && response.parameters?.length > 0) { + sendInitialParameters(); + } + setLatestResponse(response); }); @@ -104,7 +144,7 @@ const WorkspaceParametersPageExperimental: FC = () => { rich_parameter_values: buildParameters, }), onSuccess: () => { - navigate(`/@${workspace.owner_username}/${workspace.name}`); + navigate(`/@${workspace.owner_name}/${workspace.name}`); }, }); @@ -149,6 +189,7 @@ const WorkspaceParametersPageExperimental: FC = () => { const error = wsError || updateParameters.error; if ( + latestBuildParametersLoading || !latestResponse || (ws.current && ws.current.readyState === WebSocket.CONNECTING) ) { @@ -202,13 +243,14 @@ const WorkspaceParametersPageExperimental: FC = () => { {sortedParams.length > 0 ? ( - navigate(`/@${workspace.owner_username}/${workspace.name}`) + navigate(`/@${workspace.owner_name}/${workspace.name}`) } sendMessage={sendMessage} /> diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx index a8679a82b8316..4fe9d19c956d1 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx @@ -16,9 +16,11 @@ import { } from "modules/workspaces/DynamicParameter/DynamicParameter"; import type { FC } from "react"; import { docs } from "utils/docs"; +import type { AutofillBuildParameter } from "utils/richParameters"; type WorkspaceParametersPageViewExperimentalProps = { workspace: Workspace; + autofillParameters: AutofillBuildParameter[]; parameters: PreviewParameter[]; diagnostics: PreviewParameter["diagnostics"]; canChangeVersions: boolean; @@ -34,6 +36,7 @@ export const WorkspaceParametersPageViewExperimental: FC< WorkspaceParametersPageViewExperimentalProps > = ({ workspace, + autofillParameters, parameters, diagnostics, canChangeVersions, @@ -42,17 +45,32 @@ export const WorkspaceParametersPageViewExperimental: FC< sendMessage, onCancel, }) => { + const autofillByName = Object.fromEntries( + autofillParameters.map((param) => [param.name, param]), + ); + const initialTouched = parameters.reduce( + (touched, parameter) => { + if (autofillByName[parameter.name] !== undefined) { + touched[parameter.name] = true; + } + return touched; + }, + {} as Record, + ); const form = useFormik({ onSubmit, initialValues: { - rich_parameter_values: getInitialParameterValues(parameters), + rich_parameter_values: getInitialParameterValues( + parameters, + autofillParameters, + ), }, + initialTouched, validationSchema: useValidationSchemaForDynamicParameters(parameters), enableReinitialize: false, validateOnChange: true, validateOnBlur: true, }); - // Group parameters by ephemeral status const ephemeralParameters = parameters.filter((p) => p.ephemeral); const standardParameters = parameters.filter((p) => !p.ephemeral); diff --git a/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx b/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx index becc3dcd46b3b..3abb069f05d7b 100644 --- a/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx +++ b/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx @@ -19,14 +19,14 @@ const meta: Meta = { name: "Test-Workspace-2", last_used_at: "2023-08-16T15:29:10.302441433Z", owner_id: MockUserMember.id, - owner_username: MockUserMember.username, + owner_name: MockUserMember.username, }, { ...MockWorkspace, name: "Test-Workspace-3", last_used_at: "2023-11-16T15:29:10.302441433Z", owner_id: MockUserMember.id, - owner_username: MockUserMember.username, + owner_name: MockUserMember.username, }, ], }, diff --git a/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.tsx b/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.tsx index 655abfbce62f9..587cecf25efdd 100644 --- a/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.tsx +++ b/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.tsx @@ -174,7 +174,7 @@ const Workspaces: FC = ({ workspaces }) => { - {workspace.owner_username} + {workspace.owner_name} diff --git a/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx b/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx index 6f05b025e3d45..140d433d3e860 100644 --- a/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx +++ b/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx @@ -16,7 +16,7 @@ import { type Update, } from "./BatchUpdateConfirmation"; -const workspaces = [ +const workspaces: Workspace[] = [ { ...MockRunningOutdatedWorkspace, id: "1" }, { ...MockDormantOutdatedWorkspace, id: "2" }, { ...MockOutdatedWorkspace, id: "3" }, @@ -26,7 +26,7 @@ const workspaces = [ ...MockRunningOutdatedWorkspace, id: "6", owner_id: MockUserMember.id, - owner_username: MockUserMember.username, + owner_name: MockUserMember.username, }, ]; diff --git a/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.tsx b/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.tsx index e54a04a6ce8b4..a6b0a27b374f4 100644 --- a/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.tsx +++ b/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.tsx @@ -287,7 +287,7 @@ const DormantWorkspaces: FC = ({ workspaces }) => { - {workspace.owner_username} + {workspace.owner_name} diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index d25170886db3f..bfb159ef81c86 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -253,13 +253,13 @@ export const WorkspacesTable: FC = ({ subtitle={
Owner: - {workspace.owner_username} + {workspace.owner_name}
} avatar={ } @@ -327,7 +327,7 @@ const WorkspacesRow: FC = ({ }) => { const navigate = useNavigate(); - const workspacePageLink = `/@${workspace.owner_username}/${workspace.name}`; + const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`; const openLinkInNewTab = () => window.open(workspacePageLink, "_blank"); const { role, hover, ...clickableProps } = useClickableTableRow({ onMiddleClick: openLinkInNewTab, @@ -647,7 +647,7 @@ const WorkspaceApps: FC = ({ workspace }) => { isLoading={!token} label="Open VSCode" href={getVSCodeHref("vscode", { - owner: workspace.owner_username, + owner: workspace.owner_name, workspace: workspace.name, agent: agent.name, token: token ?? "", @@ -666,7 +666,7 @@ const WorkspaceApps: FC = ({ workspace }) => { label="Open VSCode Insiders" isLoading={!token} href={getVSCodeHref("vscode-insiders", { - owner: workspace.owner_username, + owner: workspace.owner_name, workspace: workspace.name, agent: agent.name, token: token ?? "", @@ -691,7 +691,7 @@ const WorkspaceApps: FC = ({ workspace }) => { if (builtinApps.has("web_terminal")) { const href = getTerminalHref({ - username: workspace.owner_username, + username: workspace.owner_name, workspace: workspace.name, agent: agent.name, }); diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 76236c6ee2c6f..fa16d65dc8d5b 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -903,7 +903,6 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { health: "disabled", external: false, sharing_level: "owner", - group: "", hidden: false, open_in: "slim-window", statuses: [], @@ -1416,7 +1415,7 @@ export const MockWorkspace: TypesGen.Workspace = { owner_id: MockUserOwner.id, organization_id: MockOrganization.id, organization_name: "default", - owner_username: MockUserOwner.username, + owner_name: MockUserOwner.username, owner_avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4", autostart_schedule: MockWorkspaceAutostartEnabled.schedule, ttl_ms: 2 * 60 * 60 * 1000, @@ -1604,6 +1603,7 @@ export const MockTemplateVersionParameter1: TypesGen.TemplateVersionParameter = { name: "first_parameter", type: "string", + form_type: "input", description: "This is first parameter", description_plaintext: "Markdown: This is first parameter", default_value: "abc", @@ -1618,6 +1618,7 @@ export const MockTemplateVersionParameter2: TypesGen.TemplateVersionParameter = { name: "second_parameter", type: "number", + form_type: "input", description: "This is second parameter", description_plaintext: "Markdown: This is second parameter", default_value: "2", @@ -1635,6 +1636,7 @@ export const MockTemplateVersionParameter3: TypesGen.TemplateVersionParameter = { name: "third_parameter", type: "string", + form_type: "input", description: "This is third parameter", description_plaintext: "Markdown: This is third parameter", default_value: "aaa", @@ -1651,6 +1653,7 @@ export const MockTemplateVersionParameter4: TypesGen.TemplateVersionParameter = { name: "fourth_parameter", type: "string", + form_type: "input", description: "This is fourth parameter", description_plaintext: "Markdown: This is fourth parameter", default_value: "def", @@ -1664,6 +1667,7 @@ export const MockTemplateVersionParameter4: TypesGen.TemplateVersionParameter = const MockTemplateVersionParameter5: TypesGen.TemplateVersionParameter = { name: "fifth_parameter", type: "number", + form_type: "input", description: "This is fifth parameter", description_plaintext: "Markdown: This is fifth parameter", default_value: "5", diff --git a/site/src/utils/richParameters.test.ts b/site/src/utils/richParameters.test.ts index 13fe34af870a0..ef4819b1baec4 100644 --- a/site/src/utils/richParameters.test.ts +++ b/site/src/utils/richParameters.test.ts @@ -9,6 +9,7 @@ test("getInitialRichParameterValues return default value when default build para description: "The number of CPU cores", description_plaintext: "The number of CPU cores", type: "string", + form_type: "radio", mutable: true, default_value: "2", icon: "/icon/memory.svg",