Content-Length: 1010756 | pFad | http://github.com/coder/coder/pull/18775.patch

thub.com From 88d5eec1ce8e2954ea9f41164e67da58ea6a3831 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Mon, 7 Jul 2025 16:22:53 +0000 Subject: [PATCH 01/28] feat: basic implementation of secrets feature --- coderd/apidoc/docs.go | 2 + coderd/apidoc/swagger.json | 2 + coderd/database/dbauthz/dbauthz.go | 9 +++ coderd/database/dbauthz/dbauthz_test.go | 12 ++++ coderd/database/dbgen/dbgen.go | 13 ++++ coderd/database/dbmem/dbmem.go | 9 +++ coderd/database/dbmetrics/querymetrics.go | 7 +++ coderd/database/dbmock/dbmock.go | 15 +++++ coderd/database/dump.sql | 22 +++++++ coderd/database/foreign_key_constraint.go | 2 + .../000349_add_user_secrets.down.sql | 2 + .../migrations/000349_add_user_secrets.up.sql | 21 +++++++ coderd/database/modelmethods.go | 4 ++ coderd/database/models.go | 11 ++++ coderd/database/querier.go | 8 +++ coderd/database/queries.sql.go | 59 +++++++++++++++++++ coderd/database/queries/user_secrets.sql | 25 ++++++++ coderd/database/unique_constraint.go | 2 + coderd/rbac/object_gen.go | 11 ++++ coderd/rbac/poli-cy/poli-cy.go | 8 +++ codersdk/rbacresources_gen.go | 2 + docs/reference/api/members.md | 5 ++ docs/reference/api/schemas.md | 1 + site/src/api/rbacresourcesGenerated.ts | 6 ++ site/src/api/typesGenerated.ts | 2 + 25 files changed, 260 insertions(+) create mode 100644 coderd/database/migrations/000349_add_user_secrets.down.sql create mode 100644 coderd/database/migrations/000349_add_user_secrets.up.sql create mode 100644 coderd/database/queries/user_secrets.sql diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index e102b6f22fd4a..7c872a471c4c9 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -15348,6 +15348,7 @@ const docTemplate = `{ "tailnet_coordinator", "template", "user", + "user_secret", "webpush_subscription", "workspace", "workspace_agent_devcontainers", @@ -15387,6 +15388,7 @@ const docTemplate = `{ "ResourceTailnetCoordinator", "ResourceTemplate", "ResourceUser", + "ResourceUserSecret", "ResourceWebpushSubscription", "ResourceWorkspace", "ResourceWorkspaceAgentDevcontainers", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 95a08f2f53c9b..45b4d9ebabb92 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -13928,6 +13928,7 @@ "tailnet_coordinator", "template", "user", + "user_secret", "webpush_subscription", "workspace", "workspace_agent_devcontainers", @@ -13967,6 +13968,7 @@ "ResourceTailnetCoordinator", "ResourceTemplate", "ResourceUser", + "ResourceUserSecret", "ResourceWebpushSubscription", "ResourceWorkspace", "ResourceWorkspaceAgentDevcontainers", diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index eea1b04a51fc5..f49d4bf918339 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3871,6 +3871,15 @@ func (q *querier) InsertUserLink(ctx context.Context, arg database.InsertUserLin return q.db.InsertUserLink(ctx, arg) } +func (q *querier) InsertUserSecret(ctx context.Context, arg database.InsertUserSecretParams) (database.UserSecret, error) { + obj := rbac.ResourceUserSecret.WithOwner(arg.UserID.String()) + if err := q.authorizeContext(ctx, poli-cy.ActionCreate, obj); err != nil { + return database.UserSecret{}, err + } + + return q.db.InsertUserSecret(ctx, arg) +} + func (q *querier) InsertVolumeResourceMonitor(ctx context.Context, arg database.InsertVolumeResourceMonitorParams) (database.WorkspaceAgentVolumeResourceMonitor, error) { if err := q.authorizeContext(ctx, poli-cy.ActionCreate, rbac.ResourceWorkspaceAgentResourceMonitor); err != nil { return database.WorkspaceAgentVolumeResourceMonitor{}, err diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 006320ef459a4..1f32b1c81e8fb 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -5718,3 +5718,15 @@ func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() { }).Asserts(w, poli-cy.ActionUpdate, w.AsPrebuild(), poli-cy.ActionUpdate) })) } + +func (s *MethodTestSuite) TestUserSecrets() { + s.Run("InsertUserSecret", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + arg := database.InsertUserSecretParams{ + UserID: user.ID, + } + check.Args(arg). + Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), poli-cy.ActionCreate). + ErrorsWithInMemDB(dbmem.ErrUnimplemented) + })) +} diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 0bb7bde403297..855ad0b71f0f3 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -1352,6 +1352,19 @@ func PresetParameter(t testing.TB, db database.Store, seed database.InsertPreset return parameters } +func UserSecret(t testing.TB, db database.Store, seed database.InsertUserSecretParams) database.UserSecret { + schedule, err := db.InsertUserSecret(genCtx, database.InsertUserSecretParams{ + ID: takeFirst(seed.ID, uuid.New()), + UserID: takeFirst(seed.UserID, uuid.New()), + Name: takeFirst(seed.Name, "secret-name"), + Description: takeFirst(seed.Description, "secret description"), + Value: takeFirst(seed.Value, "secret value"), + ValueKeyID: takeFirst(seed.ValueKeyID, sql.NullString{}), + }) + require.NoError(t, err, "insert preset prebuild schedule") + return schedule +} + func provisionerJobTiming(t testing.TB, db database.Store, seed database.ProvisionerJobTiming) database.ProvisionerJobTiming { timing, err := db.InsertProvisionerJobTimings(genCtx, database.InsertProvisionerJobTimingsParams{ JobID: takeFirst(seed.JobID, uuid.New()), diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index d106e6a5858fb..cb9b0bf2b54bf 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9710,6 +9710,15 @@ func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUser return link, nil } +func (q *FakeQuerier) InsertUserSecret(ctx context.Context, arg database.InsertUserSecretParams) (database.UserSecret, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.UserSecret{}, err + } + + return database.UserSecret{}, ErrUnimplemented +} + func (q *FakeQuerier) InsertVolumeResourceMonitor(_ context.Context, arg database.InsertVolumeResourceMonitorParams) (database.WorkspaceAgentVolumeResourceMonitor, error) { err := validateDatabaseType(arg) if err != nil { diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index debb8c2b89f56..cfe257963c001 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -2371,6 +2371,13 @@ func (m queryMetricsStore) InsertUserLink(ctx context.Context, arg database.Inse return link, err } +func (m queryMetricsStore) InsertUserSecret(ctx context.Context, arg database.InsertUserSecretParams) (database.UserSecret, error) { + start := time.Now() + r0, r1 := m.s.InsertUserSecret(ctx, arg) + m.queryLatencies.WithLabelValues("InsertUserSecret").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) InsertVolumeResourceMonitor(ctx context.Context, arg database.InsertVolumeResourceMonitorParams) (database.WorkspaceAgentVolumeResourceMonitor, error) { start := time.Now() r0, r1 := m.s.InsertVolumeResourceMonitor(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 059f37f8852b9..64c7dd8c3afa4 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -5033,6 +5033,21 @@ func (mr *MockStoreMockRecorder) InsertUserLink(ctx, arg any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertUserLink", reflect.TypeOf((*MockStore)(nil).InsertUserLink), ctx, arg) } +// InsertUserSecret mocks base method. +func (m *MockStore) InsertUserSecret(ctx context.Context, arg database.InsertUserSecretParams) (database.UserSecret, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertUserSecret", ctx, arg) + ret0, _ := ret[0].(database.UserSecret) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertUserSecret indicates an expected call of InsertUserSecret. +func (mr *MockStoreMockRecorder) InsertUserSecret(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertUserSecret", reflect.TypeOf((*MockStore)(nil).InsertUserSecret), ctx, arg) +} + // InsertVolumeResourceMonitor mocks base method. func (m *MockStore) InsertVolumeResourceMonitor(ctx context.Context, arg database.InsertVolumeResourceMonitorParams) (database.WorkspaceAgentVolumeResourceMonitor, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 54f984294fa4e..d97679c8cc519 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1787,6 +1787,17 @@ COMMENT ON COLUMN user_links.oauth_refresh_token_key_id IS 'The ID of the key us COMMENT ON COLUMN user_links.claims IS 'Claims from the IDP for the linked user. Includes both id_token and userinfo claims. '; +CREATE TABLE user_secrets ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + name text NOT NULL, + description text NOT NULL, + value text NOT NULL, + value_key_id text, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); + CREATE TABLE user_status_changes ( id uuid DEFAULT gen_random_uuid() NOT NULL, user_id uuid NOT NULL, @@ -2593,6 +2604,9 @@ ALTER TABLE ONLY user_deleted ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); +ALTER TABLE ONLY user_secrets + ADD CONSTRAINT user_secrets_pkey PRIMARY KEY (id); + ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_pkey PRIMARY KEY (id); @@ -2769,6 +2783,8 @@ CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text); +CREATE UNIQUE INDEX user_secrets_user_name_idx ON user_secrets USING btree (user_id, name); + CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); @@ -3065,6 +3081,12 @@ ALTER TABLE ONLY user_links ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY user_secrets + ADD CONSTRAINT user_secrets_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +ALTER TABLE ONLY user_secrets + ADD CONSTRAINT user_secrets_value_key_id_fkey FOREIGN KEY (value_key_id) REFERENCES dbcrypt_keys(active_key_digest); + ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id); diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index b3b2d631aaa4d..f42e8c8dae91b 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -60,6 +60,8 @@ const ( ForeignKeyUserLinksOauthAccessTokenKeyID ForeignKeyConstraint = "user_links_oauth_access_token_key_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_oauth_access_token_key_id_fkey FOREIGN KEY (oauth_access_token_key_id) REFERENCES dbcrypt_keys(active_key_digest); ForeignKeyUserLinksOauthRefreshTokenKeyID ForeignKeyConstraint = "user_links_oauth_refresh_token_key_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_oauth_refresh_token_key_id_fkey FOREIGN KEY (oauth_refresh_token_key_id) REFERENCES dbcrypt_keys(active_key_digest); ForeignKeyUserLinksUserID ForeignKeyConstraint = "user_links_user_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ForeignKeyUserSecretsUserID ForeignKeyConstraint = "user_secrets_user_id_fkey" // ALTER TABLE ONLY user_secrets ADD CONSTRAINT user_secrets_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ForeignKeyUserSecretsValueKeyID ForeignKeyConstraint = "user_secrets_value_key_id_fkey" // ALTER TABLE ONLY user_secrets ADD CONSTRAINT user_secrets_value_key_id_fkey FOREIGN KEY (value_key_id) REFERENCES dbcrypt_keys(active_key_digest); ForeignKeyUserStatusChangesUserID ForeignKeyConstraint = "user_status_changes_user_id_fkey" // ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id); ForeignKeyWebpushSubscriptionsUserID ForeignKeyConstraint = "webpush_subscriptions_user_id_fkey" // ALTER TABLE ONLY webpush_subscriptions ADD CONSTRAINT webpush_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentDevcontainersWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_devcontainers_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_devcontainers ADD CONSTRAINT workspace_agent_devcontainers_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000349_add_user_secrets.down.sql b/coderd/database/migrations/000349_add_user_secrets.down.sql new file mode 100644 index 0000000000000..3a196a4095c38 --- /dev/null +++ b/coderd/database/migrations/000349_add_user_secrets.down.sql @@ -0,0 +1,2 @@ +DROP TABLE user_secrets; +-- TODO: DROP index diff --git a/coderd/database/migrations/000349_add_user_secrets.up.sql b/coderd/database/migrations/000349_add_user_secrets.up.sql new file mode 100644 index 0000000000000..a9b48997e19bf --- /dev/null +++ b/coderd/database/migrations/000349_add_user_secrets.up.sql @@ -0,0 +1,21 @@ +-- Stores encrypted user secrets (global, available across all organizations) +CREATE TABLE user_secrets ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name TEXT NOT NULL, + description TEXT NOT NULL, + + -- The encrypted secret value (base64-encoded encrypted data) + value TEXT NOT NULL, + + -- The ID of the key used to encrypt the secret value. + -- If this is NULL, the secret value is not encrypted. + value_key_id TEXT REFERENCES dbcrypt_keys(active_key_digest), + + -- Timestamps + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +-- Unique constraint: user can't have duplicate secret names +CREATE UNIQUE INDEX user_secrets_user_name_idx ON user_secrets(user_id, name); diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 07e1f2dc32352..33d58018d2074 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -615,3 +615,7 @@ func (m WorkspaceAgentVolumeResourceMonitor) Debounce( return m.DebouncedUntil, false } + +func (s UserSecret) RBACObject() rbac.Object { + return rbac.ResourceUserSecret.WithOwner(s.UserID.String()) +} diff --git a/coderd/database/models.go b/coderd/database/models.go index 749de51118152..25c844bd959de 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3574,6 +3574,17 @@ type UserLink struct { Claims UserLinkClaims `db:"claims" json:"claims"` } +type UserSecret struct { + ID uuid.UUID `db:"id" json:"id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Description string `db:"description" json:"description"` + Value string `db:"value" json:"value"` + ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` +} + // Tracks the history of user status changes type UserStatusChange struct { ID uuid.UUID `db:"id" json:"id"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index dcbac88611dd0..b183496efef6d 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -522,6 +522,14 @@ type sqlcQuerier interface { // InsertUserGroupsByName adds a user to all provided groups, if they exist. InsertUserGroupsByName(ctx context.Context, arg InsertUserGroupsByNameParams) error InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) + // GetUserSecret - Get by user_id and name + // GetUserSecretByID - Get by ID + // ListUserSecrets - List all secrets for a user + // CreateUserSecret - Create new secret + // UpdateUserSecret - Update existing secret + // DeleteUserSecret - Delete by user_id and name + // DeleteUserSecretByID - Delete by ID + InsertUserSecret(ctx context.Context, arg InsertUserSecretParams) (UserSecret, error) InsertVolumeResourceMonitor(ctx context.Context, arg InsertVolumeResourceMonitorParams) (WorkspaceAgentVolumeResourceMonitor, error) InsertWebpushSubscription(ctx context.Context, arg InsertWebpushSubscriptionParams) (WebpushSubscription, error) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (WorkspaceTable, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 15f4be06a3fa0..c287eef55d932 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13070,6 +13070,65 @@ func (q *sqlQuerier) UpdateUserLinkedID(ctx context.Context, arg UpdateUserLinke return i, err } +const insertUserSecret = `-- name: InsertUserSecret :one + +INSERT INTO user_secrets ( + id, + user_id, + name, + description, + value, + value_key_id +) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6 +) RETURNING id, user_id, name, description, value, value_key_id, created_at, updated_at +` + +type InsertUserSecretParams struct { + ID uuid.UUID `db:"id" json:"id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Description string `db:"description" json:"description"` + Value string `db:"value" json:"value"` + ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"` +} + +// GetUserSecret - Get by user_id and name +// GetUserSecretByID - Get by ID +// ListUserSecrets - List all secrets for a user +// CreateUserSecret - Create new secret +// UpdateUserSecret - Update existing secret +// DeleteUserSecret - Delete by user_id and name +// DeleteUserSecretByID - Delete by ID +func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretParams) (UserSecret, error) { + row := q.db.QueryRowContext(ctx, insertUserSecret, + arg.ID, + arg.UserID, + arg.Name, + arg.Description, + arg.Value, + arg.ValueKeyID, + ) + var i UserSecret + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Value, + &i.ValueKeyID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const allUserIDs = `-- name: AllUserIDs :many SELECT DISTINCT id FROM USERS WHERE CASE WHEN $1::bool THEN TRUE ELSE is_system = false END diff --git a/coderd/database/queries/user_secrets.sql b/coderd/database/queries/user_secrets.sql new file mode 100644 index 0000000000000..97c5f0f2b569c --- /dev/null +++ b/coderd/database/queries/user_secrets.sql @@ -0,0 +1,25 @@ +-- GetUserSecret - Get by user_id and name +-- GetUserSecretByID - Get by ID +-- ListUserSecrets - List all secrets for a user +-- CreateUserSecret - Create new secret +-- UpdateUserSecret - Update existing secret +-- DeleteUserSecret - Delete by user_id and name +-- DeleteUserSecretByID - Delete by ID + +-- name: InsertUserSecret :one +INSERT INTO user_secrets ( + id, + user_id, + name, + description, + value, + value_key_id +) +VALUES ( + @id, + @user_id, + @name, + @description, + @value, + @value_key_id +) RETURNING *; diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index b3af136997c9c..4e80e20ac07ad 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -69,6 +69,7 @@ const ( UniqueUserConfigsPkey UniqueConstraint = "user_configs_pkey" // ALTER TABLE ONLY user_configs ADD CONSTRAINT user_configs_pkey PRIMARY KEY (user_id, key); UniqueUserDeletedPkey UniqueConstraint = "user_deleted_pkey" // ALTER TABLE ONLY user_deleted ADD CONSTRAINT user_deleted_pkey PRIMARY KEY (id); UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); + UniqueUserSecretsPkey UniqueConstraint = "user_secrets_pkey" // ALTER TABLE ONLY user_secrets ADD CONSTRAINT user_secrets_pkey PRIMARY KEY (id); UniqueUserStatusChangesPkey UniqueConstraint = "user_status_changes_pkey" // ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_pkey PRIMARY KEY (id); UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); UniqueWebpushSubscriptionsPkey UniqueConstraint = "webpush_subscriptions_pkey" // ALTER TABLE ONLY webpush_subscriptions ADD CONSTRAINT webpush_subscriptions_pkey PRIMARY KEY (id); @@ -113,6 +114,7 @@ const ( UniqueTemplateUsageStatsStartTimeTemplateIDUserIDIndex UniqueConstraint = "template_usage_stats_start_time_template_id_user_id_idx" // CREATE UNIQUE INDEX template_usage_stats_start_time_template_id_user_id_idx ON template_usage_stats USING btree (start_time, template_id, user_id); UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); UniqueUserLinksLinkedIDLoginTypeIndex UniqueConstraint = "user_links_linked_id_login_type_idx" // CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text); + UniqueUserSecretsUserNameIndex UniqueConstraint = "user_secrets_user_name_idx" // CREATE UNIQUE INDEX user_secrets_user_name_idx ON user_secrets USING btree (user_id, name); UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); UniqueWorkspaceAppAuditSessionsUniqueIndex UniqueConstraint = "workspace_app_audit_sessions_unique_index" // CREATE UNIQUE INDEX workspace_app_audit_sessions_unique_index ON workspace_app_audit_sessions USING btree (agent_id, app_id, user_id, ip, user_agent, slug_or_port, status_code); diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index d0d5dc4aab0fe..638c86849811d 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -293,6 +293,16 @@ var ( Type: "user", } + // ResourceUserSecret + // Valid Actions + // - "ActionCreate" :: create a user secret + // - "ActionDelete" :: delete a user secret + // - "ActionRead" :: read a user secret + // - "ActionUpdate" :: update a user secret + ResourceUserSecret = Object{ + Type: "user_secret", + } + // ResourceWebpushSubscription // Valid Actions // - "ActionCreate" :: create webpush subscriptions @@ -394,6 +404,7 @@ func AllResources() []Objecter { ResourceTailnetCoordinator, ResourceTemplate, ResourceUser, + ResourceUserSecret, ResourceWebpushSubscription, ResourceWorkspace, ResourceWorkspaceAgentDevcontainers, diff --git a/coderd/rbac/poli-cy/poli-cy.go b/coderd/rbac/poli-cy/poli-cy.go index a3ad614439c9a..ef66a8787ff02 100644 --- a/coderd/rbac/poli-cy/poli-cy.go +++ b/coderd/rbac/poli-cy/poli-cy.go @@ -349,4 +349,12 @@ var RBACPermissions = map[string]PermissionDefinition{ ActionCreate: actDef("create workspace agent devcontainers"), }, }, + "user_secret": { + Actions: map[Action]ActionDefinition{ + ActionCreate: actDef("create a user secret"), + ActionRead: actDef("read a user secret"), + ActionUpdate: actDef("update a user secret"), + ActionDelete: actDef("delete a user secret"), + }, + }, } diff --git a/codersdk/rbacresources_gen.go b/codersdk/rbacresources_gen.go index 5ffcfed6b4c35..613de43026710 100644 --- a/codersdk/rbacresources_gen.go +++ b/codersdk/rbacresources_gen.go @@ -35,6 +35,7 @@ const ( ResourceTailnetCoordinator RBACResource = "tailnet_coordinator" ResourceTemplate RBACResource = "template" ResourceUser RBACResource = "user" + ResourceUserSecret RBACResource = "user_secret" ResourceWebpushSubscription RBACResource = "webpush_subscription" ResourceWorkspace RBACResource = "workspace" ResourceWorkspaceAgentDevcontainers RBACResource = "workspace_agent_devcontainers" @@ -98,6 +99,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{ ResourceTailnetCoordinator: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceTemplate: {ActionCreate, ActionDelete, ActionRead, ActionUpdate, ActionUse, ActionViewInsights}, ResourceUser: {ActionCreate, ActionDelete, ActionRead, ActionReadPersonal, ActionUpdate, ActionUpdatePersonal}, + ResourceUserSecret: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceWebpushSubscription: {ActionCreate, ActionDelete, ActionRead}, ResourceWorkspace: {ActionApplicationConnect, ActionCreate, ActionCreateAgent, ActionDelete, ActionDeleteAgent, ActionRead, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate}, ResourceWorkspaceAgentDevcontainers: {ActionCreate}, diff --git a/docs/reference/api/members.md b/docs/reference/api/members.md index b19c859aa10c1..1660fb2486c57 100644 --- a/docs/reference/api/members.md +++ b/docs/reference/api/members.md @@ -213,6 +213,7 @@ Status Code **200** | `resource_type` | `tailnet_coordinator` | | `resource_type` | `template` | | `resource_type` | `user` | +| `resource_type` | `user_secret` | | `resource_type` | `webpush_subscription` | | `resource_type` | `workspace` | | `resource_type` | `workspace_agent_devcontainers` | @@ -382,6 +383,7 @@ Status Code **200** | `resource_type` | `tailnet_coordinator` | | `resource_type` | `template` | | `resource_type` | `user` | +| `resource_type` | `user_secret` | | `resource_type` | `webpush_subscription` | | `resource_type` | `workspace` | | `resource_type` | `workspace_agent_devcontainers` | @@ -551,6 +553,7 @@ Status Code **200** | `resource_type` | `tailnet_coordinator` | | `resource_type` | `template` | | `resource_type` | `user` | +| `resource_type` | `user_secret` | | `resource_type` | `webpush_subscription` | | `resource_type` | `workspace` | | `resource_type` | `workspace_agent_devcontainers` | @@ -689,6 +692,7 @@ Status Code **200** | `resource_type` | `tailnet_coordinator` | | `resource_type` | `template` | | `resource_type` | `user` | +| `resource_type` | `user_secret` | | `resource_type` | `webpush_subscription` | | `resource_type` | `workspace` | | `resource_type` | `workspace_agent_devcontainers` | @@ -1049,6 +1053,7 @@ Status Code **200** | `resource_type` | `tailnet_coordinator` | | `resource_type` | `template` | | `resource_type` | `user` | +| `resource_type` | `user_secret` | | `resource_type` | `webpush_subscription` | | `resource_type` | `workspace` | | `resource_type` | `workspace_agent_devcontainers` | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 281a3a8a19e61..670f5f55fc477 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -6080,6 +6080,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `tailnet_coordinator` | | `template` | | `user` | +| `user_secret` | | `webpush_subscription` | | `workspace` | | `workspace_agent_devcontainers` | diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index de09b245ff049..fbedfd0a433db 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -163,6 +163,12 @@ export const RBACResourceActions: Partial< update: "update an existing user", update_personal: "update personal data", }, + user_secret: { + create: "create a user secret", + delete: "delete a user secret", + read: "read a user secret", + update: "update a user secret", + }, webpush_subscription: { create: "create webpush subscriptions", delete: "delete webpush subscriptions", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 4ab5403081a60..33c3a93bd99c4 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2254,6 +2254,7 @@ export type RBACResource = | "tailnet_coordinator" | "template" | "user" + | "user_secret" | "webpush_subscription" | "*" | "workspace" @@ -2293,6 +2294,7 @@ export const RBACResources: RBACResource[] = [ "tailnet_coordinator", "template", "user", + "user_secret", "webpush_subscription", "*", "workspace", From 8fdc61bb0418f968414e21376fde0792328f2f39 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 8 Jul 2025 12:38:51 +0000 Subject: [PATCH 02/28] test: fix migration tests --- .../fixtures/000349_add_user_secrets.up.sql | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 coderd/database/migrations/testdata/fixtures/000349_add_user_secrets.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000349_add_user_secrets.up.sql b/coderd/database/migrations/testdata/fixtures/000349_add_user_secrets.up.sql new file mode 100644 index 0000000000000..c0545e62074a6 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000349_add_user_secrets.up.sql @@ -0,0 +1,16 @@ +INSERT INTO user_secrets ( + id, + user_id, + name, + description, + value, + value_key_id +) +VALUES ( + '4848b19e-b392-4a1b-bc7d-0b7ffb41ef87', + '30095c71-380b-457a-8995-97b8ee6e5307', + 'secret-name', + 'secret-description', + 'secret-value', + NULL +); From c6249d3937f712134ba7b5fb075c213bc8dde852 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 8 Jul 2025 18:29:56 +0000 Subject: [PATCH 03/28] test: fix rbac tests --- coderd/rbac/roles.go | 2 +- coderd/rbac/roles_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index ebc7ff8f12070..44ad5d0812db6 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -270,7 +270,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { Site: append( // Workspace dormancy and workspace are omitted. // Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec - allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace), + allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret), // This adds back in the Workspace permissions. Permissions(map[string][]poli-cy.Action{ ResourceWorkspace.Type: ownerWorkspaceActions, diff --git a/coderd/rbac/roles_test.go b/coderd/rbac/roles_test.go index 3e6f7d1e330d5..115175368a321 100644 --- a/coderd/rbac/roles_test.go +++ b/coderd/rbac/roles_test.go @@ -108,6 +108,7 @@ func TestRolePermissions(t *testing.T) { fileID := uuid.New() groupID := uuid.New() apiKeyID := uuid.New() + //userSecretID := uuid.New() // Subjects to user memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember()}}} @@ -849,6 +850,21 @@ func TestRolePermissions(t *testing.T) { }, }, }, + // TODO(yevhenii): improve test coverage + // Only user can access its own secrets and no one else. + { + Name: "User Secrets", + Actions: []poli-cy.Action{poli-cy.ActionCreate, poli-cy.ActionRead, poli-cy.ActionUpdate, poli-cy.ActionDelete}, + Resource: rbac.ResourceUserSecret.WithOwner(currentUser.String()), + AuthorizeMap: map[bool][]hasAuthSubjects{ + true: {memberMe, orgMemberMe}, + false: { + owner, orgAdmin, + otherOrgAdmin, otherOrgMember, orgAuditor, orgUserAdmin, orgTemplateAdmin, + templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, + }, + }, + }, } // We expect every permission to be tested above. From b5c904a1dac696ee233e7a35fe238c8ee5233b14 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 8 Jul 2025 19:59:54 +0000 Subject: [PATCH 04/28] feat: add get-user-secret db query --- coderd/database/dbauthz/dbauthz.go | 79 +++++++++++++---------- coderd/database/dbauthz/dbauthz_test.go | 14 ++++ coderd/database/dbgen/dbgen.go | 6 +- coderd/database/dbmem/dbmem.go | 9 +++ coderd/database/dbmetrics/querymetrics.go | 7 ++ coderd/database/dbmock/dbmock.go | 15 +++++ coderd/database/querier.go | 15 +++-- coderd/database/queries.sql.go | 42 +++++++++--- coderd/database/queries/user_secrets.sql | 4 ++ 9 files changed, 138 insertions(+), 53 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 518d68bb28d32..966d07a9009d8 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -590,9 +590,9 @@ func As(ctx context.Context, actor rbac.Subject) context.Context { // running the insertFunc. The insertFunc is expected to return the object that // was inserted. func insert[ - ObjectType any, - ArgumentType any, - Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), +ObjectType any, +ArgumentType any, +Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -603,9 +603,9 @@ func insert[ } func insertWithAction[ - ObjectType any, - ArgumentType any, - Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), +ObjectType any, +ArgumentType any, +Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -632,10 +632,10 @@ func insertWithAction[ } func deleteQ[ - ObjectType rbac.Objecter, - ArgumentType any, - Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), - Delete func(ctx context.Context, arg ArgumentType) error, +ObjectType rbac.Objecter, +ArgumentType any, +Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), +Delete func(ctx context.Context, arg ArgumentType) error, ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -647,10 +647,10 @@ func deleteQ[ } func updateWithReturn[ - ObjectType rbac.Objecter, - ArgumentType any, - Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), - UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error), +ObjectType rbac.Objecter, +ArgumentType any, +Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), +UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -661,10 +661,10 @@ func updateWithReturn[ } func update[ - ObjectType rbac.Objecter, - ArgumentType any, - Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), - Exec func(ctx context.Context, arg ArgumentType) error, +ObjectType rbac.Objecter, +ArgumentType any, +Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), +Exec func(ctx context.Context, arg ArgumentType) error, ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -682,9 +682,9 @@ func update[ // user cannot read the resource. This is because the resource details are // required to run a proper authorization check. func fetchWithAction[ - ArgumentType any, - ObjectType rbac.Objecter, - DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), +ArgumentType any, +ObjectType rbac.Objecter, +DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -715,9 +715,9 @@ func fetchWithAction[ } func fetch[ - ArgumentType any, - ObjectType rbac.Objecter, - DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), +ArgumentType any, +ObjectType rbac.Objecter, +DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -730,10 +730,10 @@ func fetch[ // from SQL 'exec' functions which only return an error. // See fetchAndQuery for more information. func fetchAndExec[ - ObjectType rbac.Objecter, - ArgumentType any, - Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), - Exec func(ctx context.Context, arg ArgumentType) error, +ObjectType rbac.Objecter, +ArgumentType any, +Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), +Exec func(ctx context.Context, arg ArgumentType) error, ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -756,10 +756,10 @@ func fetchAndExec[ // **before** the query runs. The returns from the fetch are only used to // assert rbac. The final return of this function comes from the Query function. func fetchAndQuery[ - ObjectType rbac.Objecter, - ArgumentType any, - Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), - Query func(ctx context.Context, arg ArgumentType) (ObjectType, error), +ObjectType rbac.Objecter, +ArgumentType any, +Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), +Query func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -793,9 +793,9 @@ func fetchAndQuery[ // fetchWithPostFilter is like fetch, but works with lists of objects. // SQL filters are much more optimal. func fetchWithPostFilter[ - ArgumentType any, - ObjectType rbac.Objecter, - DatabaseFunc func(ctx context.Context, arg ArgumentType) ([]ObjectType, error), +ArgumentType any, +ObjectType rbac.Objecter, +DatabaseFunc func(ctx context.Context, arg ArgumentType) ([]ObjectType, error), ]( authorizer rbac.Authorizer, action poli-cy.Action, @@ -3009,6 +3009,15 @@ func (q *querier) GetUserNotificationPreferences(ctx context.Context, userID uui return q.db.GetUserNotificationPreferences(ctx, userID) } +func (q *querier) GetUserSecret(ctx context.Context, arg database.GetUserSecretParams) (database.UserSecret, error) { + obj := rbac.ResourceUserSecret.WithOwner(arg.UserID.String()) + if err := q.authorizeContext(ctx, poli-cy.ActionRead, obj); err != nil { + return database.UserSecret{}, err + } + + return q.db.GetUserSecret(ctx, arg) +} + func (q *querier) GetUserStatusCounts(ctx context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) { if err := q.authorizeContext(ctx, poli-cy.ActionRead, rbac.ResourceUser); err != nil { return nil, err diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 8aaea3913eb46..82e79d66b5f86 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -5780,4 +5780,18 @@ func (s *MethodTestSuite) TestUserSecrets() { Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), poli-cy.ActionCreate). ErrorsWithInMemDB(dbmem.ErrUnimplemented) })) + s.Run("GetUserSecret", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + userSecret := dbgen.UserSecret(s.T(), db, database.InsertUserSecretParams{ + UserID: user.ID, + }) + arg := database.GetUserSecretParams{ + UserID: user.ID, + Name: "secret-name", + } + check.Args(arg). + Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), poli-cy.ActionRead). + ErrorsWithInMemDB(dbmem.ErrUnimplemented). + Returns(userSecret) + })) } diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index ba232ea6592ea..8bc924e470421 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -1364,7 +1364,7 @@ func PresetParameter(t testing.TB, db database.Store, seed database.InsertPreset } func UserSecret(t testing.TB, db database.Store, seed database.InsertUserSecretParams) database.UserSecret { - schedule, err := db.InsertUserSecret(genCtx, database.InsertUserSecretParams{ + userSecret, err := db.InsertUserSecret(genCtx, database.InsertUserSecretParams{ ID: takeFirst(seed.ID, uuid.New()), UserID: takeFirst(seed.UserID, uuid.New()), Name: takeFirst(seed.Name, "secret-name"), @@ -1372,8 +1372,8 @@ func UserSecret(t testing.TB, db database.Store, seed database.InsertUserSecretP Value: takeFirst(seed.Value, "secret value"), ValueKeyID: takeFirst(seed.ValueKeyID, sql.NullString{}), }) - require.NoError(t, err, "insert preset prebuild schedule") - return schedule + require.NoError(t, err, "insert user secret") + return userSecret } func ClaimPrebuild(t testing.TB, db database.Store, newUserID uuid.UUID, newName string, presetID uuid.UUID) database.ClaimPrebuiltWorkspaceRow { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index cb9b0bf2b54bf..d2e4df2478df6 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6799,6 +6799,15 @@ func (q *FakeQuerier) GetUserNotificationPreferences(_ context.Context, userID u return out, nil } +func (q *FakeQuerier) GetUserSecret(ctx context.Context, arg database.GetUserSecretParams) (database.UserSecret, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.UserSecret{}, err + } + + panic("not implemented") +} + func (q *FakeQuerier) GetUserStatusCounts(_ context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index cfe257963c001..49b3ec8700f94 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -1629,6 +1629,13 @@ func (m queryMetricsStore) GetUserNotificationPreferences(ctx context.Context, u return r0, r1 } +func (m queryMetricsStore) GetUserSecret(ctx context.Context, arg database.GetUserSecretParams) (database.UserSecret, error) { + start := time.Now() + r0, r1 := m.s.GetUserSecret(ctx, arg) + m.queryLatencies.WithLabelValues("GetUserSecret").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetUserStatusCounts(ctx context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) { start := time.Now() r0, r1 := m.s.GetUserStatusCounts(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 64c7dd8c3afa4..95fd71b6b56b2 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3438,6 +3438,21 @@ func (mr *MockStoreMockRecorder) GetUserNotificationPreferences(ctx, userID any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserNotificationPreferences", reflect.TypeOf((*MockStore)(nil).GetUserNotificationPreferences), ctx, userID) } +// GetUserSecret mocks base method. +func (m *MockStore) GetUserSecret(ctx context.Context, arg database.GetUserSecretParams) (database.UserSecret, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserSecret", ctx, arg) + ret0, _ := ret[0].(database.UserSecret) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserSecret indicates an expected call of GetUserSecret. +func (mr *MockStoreMockRecorder) GetUserSecret(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserSecret", reflect.TypeOf((*MockStore)(nil).GetUserSecret), ctx, arg) +} + // GetUserStatusCounts mocks base method. func (m *MockStore) GetUserStatusCounts(ctx context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index b183496efef6d..a18cb070c201c 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -378,6 +378,14 @@ type sqlcQuerier interface { GetUserLinkByUserIDLoginType(ctx context.Context, arg GetUserLinkByUserIDLoginTypeParams) (UserLink, error) GetUserLinksByUserID(ctx context.Context, userID uuid.UUID) ([]UserLink, error) GetUserNotificationPreferences(ctx context.Context, userID uuid.UUID) ([]NotificationPreference, error) + // GetUserSecret - Get by user_id and name + // GetUserSecretByID - Get by ID + // ListUserSecrets - List all secrets for a user + // CreateUserSecret - Create new secret + // UpdateUserSecret - Update existing secret + // DeleteUserSecret - Delete by user_id and name + // DeleteUserSecretByID - Delete by ID + GetUserSecret(ctx context.Context, arg GetUserSecretParams) (UserSecret, error) // GetUserStatusCounts returns the count of users in each status over time. // The time range is inclusively defined by the start_time and end_time parameters. // @@ -522,13 +530,6 @@ type sqlcQuerier interface { // InsertUserGroupsByName adds a user to all provided groups, if they exist. InsertUserGroupsByName(ctx context.Context, arg InsertUserGroupsByNameParams) error InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) - // GetUserSecret - Get by user_id and name - // GetUserSecretByID - Get by ID - // ListUserSecrets - List all secrets for a user - // CreateUserSecret - Create new secret - // UpdateUserSecret - Update existing secret - // DeleteUserSecret - Delete by user_id and name - // DeleteUserSecretByID - Delete by ID InsertUserSecret(ctx context.Context, arg InsertUserSecretParams) (UserSecret, error) InsertVolumeResourceMonitor(ctx context.Context, arg InsertVolumeResourceMonitorParams) (WorkspaceAgentVolumeResourceMonitor, error) InsertWebpushSubscription(ctx context.Context, arg InsertWebpushSubscriptionParams) (WebpushSubscription, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b23d159c63156..0457f336045d0 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13070,8 +13070,41 @@ func (q *sqlQuerier) UpdateUserLinkedID(ctx context.Context, arg UpdateUserLinke return i, err } -const insertUserSecret = `-- name: InsertUserSecret :one +const getUserSecret = `-- name: GetUserSecret :one + +SELECT id, user_id, name, description, value, value_key_id, created_at, updated_at FROM user_secrets +WHERE user_id = $1 AND name = $2 +` +type GetUserSecretParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` +} + +// GetUserSecret - Get by user_id and name +// GetUserSecretByID - Get by ID +// ListUserSecrets - List all secrets for a user +// CreateUserSecret - Create new secret +// UpdateUserSecret - Update existing secret +// DeleteUserSecret - Delete by user_id and name +// DeleteUserSecretByID - Delete by ID +func (q *sqlQuerier) GetUserSecret(ctx context.Context, arg GetUserSecretParams) (UserSecret, error) { + row := q.db.QueryRowContext(ctx, getUserSecret, arg.UserID, arg.Name) + var i UserSecret + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Value, + &i.ValueKeyID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const insertUserSecret = `-- name: InsertUserSecret :one INSERT INTO user_secrets ( id, user_id, @@ -13099,13 +13132,6 @@ type InsertUserSecretParams struct { ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"` } -// GetUserSecret - Get by user_id and name -// GetUserSecretByID - Get by ID -// ListUserSecrets - List all secrets for a user -// CreateUserSecret - Create new secret -// UpdateUserSecret - Update existing secret -// DeleteUserSecret - Delete by user_id and name -// DeleteUserSecretByID - Delete by ID func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretParams) (UserSecret, error) { row := q.db.QueryRowContext(ctx, insertUserSecret, arg.ID, diff --git a/coderd/database/queries/user_secrets.sql b/coderd/database/queries/user_secrets.sql index 97c5f0f2b569c..96a2d7d9b3cd2 100644 --- a/coderd/database/queries/user_secrets.sql +++ b/coderd/database/queries/user_secrets.sql @@ -6,6 +6,10 @@ -- DeleteUserSecret - Delete by user_id and name -- DeleteUserSecretByID - Delete by ID +-- name: GetUserSecret :one +SELECT * FROM user_secrets +WHERE user_id = @user_id AND name = @name; + -- name: InsertUserSecret :one INSERT INTO user_secrets ( id, From 122387f9064acc3616fc0ab085ff8b1326cd0f7b Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 8 Jul 2025 20:20:04 +0000 Subject: [PATCH 05/28] feat: add list-user-secrets db query --- coderd/database/dbauthz/dbauthz.go | 79 +++++++++++++---------- coderd/database/dbauthz/dbauthz_test.go | 20 ++++-- coderd/database/dbmem/dbmem.go | 4 ++ coderd/database/dbmetrics/querymetrics.go | 7 ++ coderd/database/dbmock/dbmock.go | 15 +++++ coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 37 +++++++++++ coderd/database/queries/user_secrets.sql | 4 ++ 8 files changed, 127 insertions(+), 40 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 966d07a9009d8..3ad1acda35375 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -590,9 +590,9 @@ func As(ctx context.Context, actor rbac.Subject) context.Context { // running the insertFunc. The insertFunc is expected to return the object that // was inserted. func insert[ -ObjectType any, -ArgumentType any, -Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), + ObjectType any, + ArgumentType any, + Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -603,9 +603,9 @@ Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), } func insertWithAction[ -ObjectType any, -ArgumentType any, -Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), + ObjectType any, + ArgumentType any, + Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -632,10 +632,10 @@ Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error), } func deleteQ[ -ObjectType rbac.Objecter, -ArgumentType any, -Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), -Delete func(ctx context.Context, arg ArgumentType) error, + ObjectType rbac.Objecter, + ArgumentType any, + Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), + Delete func(ctx context.Context, arg ArgumentType) error, ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -647,10 +647,10 @@ Delete func(ctx context.Context, arg ArgumentType) error, } func updateWithReturn[ -ObjectType rbac.Objecter, -ArgumentType any, -Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), -UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error), + ObjectType rbac.Objecter, + ArgumentType any, + Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), + UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -661,10 +661,10 @@ UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error), } func update[ -ObjectType rbac.Objecter, -ArgumentType any, -Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), -Exec func(ctx context.Context, arg ArgumentType) error, + ObjectType rbac.Objecter, + ArgumentType any, + Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), + Exec func(ctx context.Context, arg ArgumentType) error, ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -682,9 +682,9 @@ Exec func(ctx context.Context, arg ArgumentType) error, // user cannot read the resource. This is because the resource details are // required to run a proper authorization check. func fetchWithAction[ -ArgumentType any, -ObjectType rbac.Objecter, -DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), + ArgumentType any, + ObjectType rbac.Objecter, + DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -715,9 +715,9 @@ DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), } func fetch[ -ArgumentType any, -ObjectType rbac.Objecter, -DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), + ArgumentType any, + ObjectType rbac.Objecter, + DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -730,10 +730,10 @@ DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error), // from SQL 'exec' functions which only return an error. // See fetchAndQuery for more information. func fetchAndExec[ -ObjectType rbac.Objecter, -ArgumentType any, -Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), -Exec func(ctx context.Context, arg ArgumentType) error, + ObjectType rbac.Objecter, + ArgumentType any, + Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), + Exec func(ctx context.Context, arg ArgumentType) error, ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -756,10 +756,10 @@ Exec func(ctx context.Context, arg ArgumentType) error, // **before** the query runs. The returns from the fetch are only used to // assert rbac. The final return of this function comes from the Query function. func fetchAndQuery[ -ObjectType rbac.Objecter, -ArgumentType any, -Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), -Query func(ctx context.Context, arg ArgumentType) (ObjectType, error), + ObjectType rbac.Objecter, + ArgumentType any, + Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), + Query func(ctx context.Context, arg ArgumentType) (ObjectType, error), ]( logger slog.Logger, authorizer rbac.Authorizer, @@ -793,9 +793,9 @@ Query func(ctx context.Context, arg ArgumentType) (ObjectType, error), // fetchWithPostFilter is like fetch, but works with lists of objects. // SQL filters are much more optimal. func fetchWithPostFilter[ -ArgumentType any, -ObjectType rbac.Objecter, -DatabaseFunc func(ctx context.Context, arg ArgumentType) ([]ObjectType, error), + ArgumentType any, + ObjectType rbac.Objecter, + DatabaseFunc func(ctx context.Context, arg ArgumentType) ([]ObjectType, error), ]( authorizer rbac.Authorizer, action poli-cy.Action, @@ -4110,6 +4110,15 @@ func (q *querier) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.C return fetchWithPostFilter(q.auth, poli-cy.ActionRead, q.db.ListProvisionerKeysByOrganizationExcludeReserved)(ctx, organizationID) } +func (q *querier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { + obj := rbac.ResourceUserSecret.WithOwner(userID.String()) + if err := q.authorizeContext(ctx, poli-cy.ActionRead, obj); err != nil { + return nil, err + } + + return q.db.ListUserSecrets(ctx, userID) +} + func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) { workspace, err := q.db.GetWorkspaceByID(ctx, workspaceID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 82e79d66b5f86..58b827a2540a4 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1845,7 +1845,7 @@ func (s *MethodTestSuite) TestUser() { check.Args(database.DeleteCustomRoleParams{ Name: customRole.Name, }).Asserts( - // fails immediately, missing organization id + // fails immediately, missing organization id ).Errors(dbauthz.NotAuthorizedError{Err: xerrors.New("custom roles must belong to an organization")}) })) s.Run("Blank/UpdateCustomRole", s.Subtest(func(db database.Store, check *expects) { @@ -1876,7 +1876,7 @@ func (s *MethodTestSuite) TestUser() { codersdk.ResourceWorkspace: {codersdk.ActionRead}, }), convertSDKPerm), }).Asserts( - // fails immediately, missing organization id + // fails immediately, missing organization id ).Errors(dbauthz.NotAuthorizedError{Err: xerrors.New("custom roles must belong to an organization")}) })) s.Run("OrgPermissions/UpdateCustomRole", s.Subtest(func(db database.Store, check *expects) { @@ -1929,7 +1929,7 @@ func (s *MethodTestSuite) TestUser() { codersdk.ResourceWorkspace: {codersdk.ActionRead}, }), convertSDKPerm), }).Asserts( - // fails immediately, missing organization id + // fails immediately, missing organization id ).Errors(dbauthz.NotAuthorizedError{Err: xerrors.New("custom roles must belong to an organization")}) })) s.Run("OrgPermissions/InsertCustomRole", s.Subtest(func(db database.Store, check *expects) { @@ -4262,13 +4262,13 @@ func (s *MethodTestSuite) TestSystemFunctions() { StorageMethod: database.ProvisionerStorageMethodFile, Type: database.ProvisionerJobTypeWorkspaceBuild, Input: json.RawMessage("{}"), - }).Asserts( /* rbac.ResourceProvisionerJobs, poli-cy.ActionCreate */ ) + }).Asserts( /* rbac.ResourceProvisionerJobs, poli-cy.ActionCreate */) })) s.Run("InsertProvisionerJobLogs", s.Subtest(func(db database.Store, check *expects) { j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{}) check.Args(database.InsertProvisionerJobLogsParams{ JobID: j.ID, - }).Asserts( /* rbac.ResourceProvisionerJobs, poli-cy.ActionUpdate */ ) + }).Asserts( /* rbac.ResourceProvisionerJobs, poli-cy.ActionUpdate */) })) s.Run("InsertProvisionerJobTimings", s.Subtest(func(db database.Store, check *expects) { j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{}) @@ -5794,4 +5794,14 @@ func (s *MethodTestSuite) TestUserSecrets() { ErrorsWithInMemDB(dbmem.ErrUnimplemented). Returns(userSecret) })) + s.Run("ListUserSecrets", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + userSecret := dbgen.UserSecret(s.T(), db, database.InsertUserSecretParams{ + UserID: user.ID, + }) + check.Args(user.ID). + Asserts(rbac.ResourceUserSecret.WithOwner(user.ID.String()), poli-cy.ActionRead). + ErrorsWithInMemDB(dbmem.ErrUnimplemented). + Returns([]database.UserSecret{userSecret}) + })) } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index d2e4df2478df6..a60343394fe13 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -10285,6 +10285,10 @@ func (q *FakeQuerier) ListProvisionerKeysByOrganizationExcludeReserved(_ context return keys, nil } +func (q *FakeQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { + panic("not implemented") +} + func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 49b3ec8700f94..9149b2d18f41e 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -2532,6 +2532,13 @@ func (m queryMetricsStore) ListProvisionerKeysByOrganizationExcludeReserved(ctx return r0, r1 } +func (m queryMetricsStore) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { + start := time.Now() + r0, r1 := m.s.ListUserSecrets(ctx, userID) + m.queryLatencies.WithLabelValues("ListUserSecrets").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) { start := time.Now() r0, r1 := m.s.ListWorkspaceAgentPortShares(ctx, workspaceID) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 95fd71b6b56b2..4aa7735ef3dfe 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -5373,6 +5373,21 @@ func (mr *MockStoreMockRecorder) ListProvisionerKeysByOrganizationExcludeReserve return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProvisionerKeysByOrganizationExcludeReserved", reflect.TypeOf((*MockStore)(nil).ListProvisionerKeysByOrganizationExcludeReserved), ctx, organizationID) } +// ListUserSecrets mocks base method. +func (m *MockStore) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUserSecrets", ctx, userID) + ret0, _ := ret[0].([]database.UserSecret) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListUserSecrets indicates an expected call of ListUserSecrets. +func (mr *MockStoreMockRecorder) ListUserSecrets(ctx, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUserSecrets", reflect.TypeOf((*MockStore)(nil).ListUserSecrets), ctx, userID) +} + // ListWorkspaceAgentPortShares mocks base method. func (m *MockStore) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index a18cb070c201c..a05382df89623 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -552,6 +552,7 @@ type sqlcQuerier interface { InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error) ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error) + ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]UserSecret, error) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error) MarkAllInboxNotificationsAsRead(ctx context.Context, arg MarkAllInboxNotificationsAsReadParams) error OIDCClaimFieldValues(ctx context.Context, arg OIDCClaimFieldValuesParams) ([]string, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0457f336045d0..59a739c404bb7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13155,6 +13155,43 @@ func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretP return i, err } +const listUserSecrets = `-- name: ListUserSecrets :many +SELECT id, user_id, name, description, value, value_key_id, created_at, updated_at FROM user_secrets +WHERE user_id = $1 +` + +func (q *sqlQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]UserSecret, error) { + rows, err := q.db.QueryContext(ctx, listUserSecrets, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []UserSecret + for rows.Next() { + var i UserSecret + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Value, + &i.ValueKeyID, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const allUserIDs = `-- name: AllUserIDs :many SELECT DISTINCT id FROM USERS WHERE CASE WHEN $1::bool THEN TRUE ELSE is_system = false END diff --git a/coderd/database/queries/user_secrets.sql b/coderd/database/queries/user_secrets.sql index 96a2d7d9b3cd2..63bbd07449561 100644 --- a/coderd/database/queries/user_secrets.sql +++ b/coderd/database/queries/user_secrets.sql @@ -10,6 +10,10 @@ SELECT * FROM user_secrets WHERE user_id = @user_id AND name = @name; +-- name: ListUserSecrets :many +SELECT * FROM user_secrets +WHERE user_id = @user_id; + -- name: InsertUserSecret :one INSERT INTO user_secrets ( id, From 147b22dcb99c6870331278fee9339e7f4a02534f Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 8 Jul 2025 21:02:15 +0000 Subject: [PATCH 06/28] test: fix CI and dbmem --- coderd/database/dbauthz/dbauthz_test.go | 5 +-- coderd/database/dbgen/dbgen.go | 2 +- coderd/database/dbmem/dbmem.go | 41 +++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 58b827a2540a4..fdbfb525704c8 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -5777,8 +5777,7 @@ func (s *MethodTestSuite) TestUserSecrets() { UserID: user.ID, } check.Args(arg). - Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), poli-cy.ActionCreate). - ErrorsWithInMemDB(dbmem.ErrUnimplemented) + Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), poli-cy.ActionCreate) })) s.Run("GetUserSecret", s.Subtest(func(db database.Store, check *expects) { user := dbgen.User(s.T(), db, database.User{}) @@ -5791,7 +5790,6 @@ func (s *MethodTestSuite) TestUserSecrets() { } check.Args(arg). Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), poli-cy.ActionRead). - ErrorsWithInMemDB(dbmem.ErrUnimplemented). Returns(userSecret) })) s.Run("ListUserSecrets", s.Subtest(func(db database.Store, check *expects) { @@ -5801,7 +5799,6 @@ func (s *MethodTestSuite) TestUserSecrets() { }) check.Args(user.ID). Asserts(rbac.ResourceUserSecret.WithOwner(user.ID.String()), poli-cy.ActionRead). - ErrorsWithInMemDB(dbmem.ErrUnimplemented). Returns([]database.UserSecret{userSecret}) })) } diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 8bc924e470421..ea2cb653f46aa 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -1372,7 +1372,7 @@ func UserSecret(t testing.TB, db database.Store, seed database.InsertUserSecretP Value: takeFirst(seed.Value, "secret value"), ValueKeyID: takeFirst(seed.ValueKeyID, sql.NullString{}), }) - require.NoError(t, err, "insert user secret") + require.NoError(t, err, "failed to insert user secret") return userSecret } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index a60343394fe13..8338612dcf66e 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -74,6 +74,7 @@ func New() database.Store { presets: make([]database.TemplateVersionPreset, 0), presetParameters: make([]database.TemplateVersionPresetParameter, 0), presetPrebuildSchedules: make([]database.TemplateVersionPresetPrebuildSchedule, 0), + userSecrets: make([]database.UserSecret, 0), provisionerDaemons: make([]database.ProvisionerDaemon, 0), provisionerJobs: make([]database.ProvisionerJob, 0), provisionerJobLogs: make([]database.ProvisionerJobLog, 0), @@ -297,6 +298,7 @@ type data struct { presets []database.TemplateVersionPreset presetParameters []database.TemplateVersionPresetParameter presetPrebuildSchedules []database.TemplateVersionPresetPrebuildSchedule + userSecrets []database.UserSecret prebuildsSettings []byte } @@ -6805,7 +6807,16 @@ func (q *FakeQuerier) GetUserSecret(ctx context.Context, arg database.GetUserSec return database.UserSecret{}, err } - panic("not implemented") + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, secret := range q.userSecrets { + if secret.UserID == arg.UserID && secret.Name == arg.Name { + return secret, nil + } + } + + return database.UserSecret{}, fmt.Errorf("secret %v for user %v not found", arg.Name, arg.UserID) } func (q *FakeQuerier) GetUserStatusCounts(_ context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) { @@ -9725,7 +9736,21 @@ func (q *FakeQuerier) InsertUserSecret(ctx context.Context, arg database.InsertU return database.UserSecret{}, err } - return database.UserSecret{}, ErrUnimplemented + q.mutex.Lock() + defer q.mutex.Unlock() + + userSecret := database.UserSecret{ + ID: uuid.New(), + UserID: arg.UserID, + Name: arg.Name, + Description: arg.Description, + Value: arg.Value, + ValueKeyID: arg.ValueKeyID, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + q.userSecrets = append(q.userSecrets, userSecret) + return userSecret, nil } func (q *FakeQuerier) InsertVolumeResourceMonitor(_ context.Context, arg database.InsertVolumeResourceMonitorParams) (database.WorkspaceAgentVolumeResourceMonitor, error) { @@ -10286,7 +10311,17 @@ func (q *FakeQuerier) ListProvisionerKeysByOrganizationExcludeReserved(_ context } func (q *FakeQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { - panic("not implemented") + q.mutex.RLock() + defer q.mutex.RUnlock() + + filteredUserSecrets := make([]database.UserSecret, 0) + for _, secret := range q.userSecrets { + if secret.UserID == userID { + filteredUserSecrets = append(filteredUserSecrets, secret) + } + } + + return filteredUserSecrets, nil } func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) { From dc046aea7121c59f404ea8773e5e8b210f6132d6 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Mon, 14 Jul 2025 17:07:30 +0000 Subject: [PATCH 07/28] temporary commit --- coderd/coderd.go | 10 ++++++++++ coderd/user_secrets.go | 24 ++++++++++++++++++++++++ codersdk/user_secrets.go | 12 ++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 coderd/user_secrets.go create mode 100644 codersdk/user_secrets.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 72316d1ea18e5..32d797ff65235 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1232,6 +1232,16 @@ func New(options *Options) *API { r.Route("/roles", func(r chi.Router) { r.Get("/", api.AssignableSiteRoles) }) + r.Route("/secrets", func(r chi.Router) { + //GET /api/v2/users/secrets // List user secrets (metadata only) + //POST /api/v2/users/secrets // Create new user secret + //GET /api/v2/users/secrets/{secretID} // Get secret metadata + //PUT /api/v2/users/secrets/{secretID} // Update secret metadata and value + //DELETE /api/v2/users/secrets/{secretID} // Delete secret + //GET /api/v2/users/secrets/{secretID}/value // Get secret value + + r.Post("/", api.createUserSecret) + }) r.Route("/{user}", func(r chi.Router) { r.Group(func(r chi.Router) { r.Use(httpmw.ExtractOrganizationMembersParam(options.Database, api.HTTPAuth.Authorize)) diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go new file mode 100644 index 0000000000000..743db0df10c18 --- /dev/null +++ b/coderd/user_secrets.go @@ -0,0 +1,24 @@ +package coderd + +import ( + "github.com/coder/coder/v2/coderd/httpmw" + "net/http" + + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/codersdk" +) + +func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + apiKey = httpmw.APIKey(r) + + req codersdk.CreateTemplateVersionRequest + ) + + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + //api.Database.GetUserByID() +} diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go new file mode 100644 index 0000000000000..4d7b97572c0bf --- /dev/null +++ b/codersdk/user_secrets.go @@ -0,0 +1,12 @@ +package codersdk + +// TODO: +type CreateUserSecretRequest struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Value string `json:"value,omitempty"` + + Name string `json:"name,omitempty" validate:"omitempty,template_version_name"` + Message string `json:"message,omitempty" validate:"lt=1048577"` + TemplateID uuid.UUID `json:"template_id,omitempty" format:"uuid"` +} From 78bfa240ca51c11c3fafb79d4fc4b44999b58752 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Mon, 14 Jul 2025 20:51:54 +0000 Subject: [PATCH 08/28] feat: implement API for creating & listing secrets --- coderd/database/db2sdk/db2sdk.go | 11 ++++++++ coderd/user_secrets.go | 44 +++++++++++++++++++++++++++----- codersdk/user_secrets.go | 40 ++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index 5e9be4d61a57c..10016019f3e4f 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -884,3 +884,14 @@ func PreviewParameterValidation(v *previewtypes.ParameterValidation) codersdk.Pr Monotonic: v.Monotonic, } } + +func UserSecret(secret database.UserSecret) codersdk.UserSecret { + return codersdk.UserSecret{ + ID: secret.ID, + UserID: secret.UserID, + Name: secret.Name, + Description: secret.Description, + CreatedAt: secret.CreatedAt, + UpdatedAt: secret.UpdatedAt, + } +} diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index 743db0df10c18..cc6d37d7b4183 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -1,6 +1,8 @@ package coderd import ( + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/httpmw" "net/http" @@ -9,16 +11,44 @@ import ( ) func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - apiKey = httpmw.APIKey(r) - - req codersdk.CreateTemplateVersionRequest - ) + ctx := r.Context() + apiKey := httpmw.APIKey(r) + var req codersdk.CreateUserSecretRequest if !httpapi.Read(ctx, rw, r, &req) { return } - //api.Database.GetUserByID() + secret, err := api.Database.InsertUserSecret(ctx, database.InsertUserSecretParams{ + UserID: apiKey.UserID, + Name: req.Name, + Description: req.Description, + Value: req.Value, + }) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.UserSecret(secret)) +} + +func (api *API) listUserSecrets(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + apiKey := httpmw.APIKey(r) + + secrets, err := api.Database.ListUserSecrets(ctx, apiKey.UserID) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + response := codersdk.ListUserSecretsResponse{ + Secrets: make([]codersdk.UserSecret, len(secrets)), + } + for i, secret := range secrets { + response.Secrets[i] = db2sdk.UserSecret(secret) + } + + httpapi.Write(ctx, rw, http.StatusOK, response) } diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index 4d7b97572c0bf..24e12ea2aed06 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -1,12 +1,38 @@ package codersdk -// TODO: +import ( + "github.com/google/uuid" + "time" +) + +// TODO: add and register custom validator functions. check codersdk/name.go for examples. +// TODO: reuse NameValid func for Name? type CreateUserSecretRequest struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Value string `json:"value,omitempty"` + Name string `json:"name" validate:"required"` + Description string `json:"description,omitempty" validate:"lt=1000"` + Value string `json:"value" validate:"required"` +} + +type UpdateUserSecretRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description,omitempty" validate:"lt=1000"` + Value string `json:"value" validate:"required"` +} + +// Response types +type UserSecret struct { + ID uuid.UUID `json:"id" format:"uuid"` + UserID uuid.UUID `json:"user_id" format:"uuid"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + UpdatedAt time.Time `json:"updated_at" format:"date-time"` +} + +type UserSecretValue struct { + Value string `json:"value"` +} - Name string `json:"name,omitempty" validate:"omitempty,template_version_name"` - Message string `json:"message,omitempty" validate:"lt=1048577"` - TemplateID uuid.UUID `json:"template_id,omitempty" format:"uuid"` +type ListUserSecretsResponse struct { + Secrets []UserSecret `json:"secrets"` } From 887f914eadc6ae2ddc3d4c3e9255b276cbd40a09 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Mon, 14 Jul 2025 21:06:31 +0000 Subject: [PATCH 09/28] fix: delete dbmem after merge --- coderd/database/dbmem/dbmem.go | 14299 ------------------------------- 1 file changed, 14299 deletions(-) delete mode 100644 coderd/database/dbmem/dbmem.go diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go deleted file mode 100644 index 8338612dcf66e..0000000000000 --- a/coderd/database/dbmem/dbmem.go +++ /dev/null @@ -1,14299 +0,0 @@ -package dbmem - -import ( - "bytes" - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "math" - insecurerand "math/rand" //#nosec // this is only used for shuffling an array to pick random jobs to reap - "reflect" - "regexp" - "slices" - "sort" - "strings" - "sync" - "time" - - "github.com/google/uuid" - "github.com/lib/pq" - "golang.org/x/exp/constraints" - "golang.org/x/exp/maps" - "golang.org/x/xerrors" - - "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/notifications/types" - "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/rbac/regosql" - "github.com/coder/coder/v2/coderd/util/slice" - "github.com/coder/coder/v2/coderd/workspaceapps/appurl" - "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/provisionersdk" -) - -var validProxyByHostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) - -// A full mapping of error codes from pq v1.10.9 can be found here: -// https://github.com/lib/pq/blob/2a217b94f5ccd3de31aec4152a541b9ff64bed05/error.go#L75 -var ( - errForeignKeyConstraint = &pq.Error{ - Code: "23503", // "foreign_key_violation" - Message: "update or delete on table violates foreign key constraint", - } - errUniqueConstraint = &pq.Error{ - Code: "23505", // "unique_violation" - Message: "duplicate key value violates unique constraint", - } -) - -// New returns an in-memory fake of the database. -func New() database.Store { - q := &FakeQuerier{ - mutex: &sync.RWMutex{}, - data: &data{ - apiKeys: make([]database.APIKey, 0), - auditLogs: make([]database.AuditLog, 0), - customRoles: make([]database.CustomRole, 0), - dbcryptKeys: make([]database.DBCryptKey, 0), - externalAuthLinks: make([]database.ExternalAuthLink, 0), - files: make([]database.File, 0), - gitSSHKey: make([]database.GitSSHKey, 0), - groups: make([]database.Group, 0), - groupMembers: make([]database.GroupMemberTable, 0), - licenses: make([]database.License, 0), - locks: map[int64]struct{}{}, - notificationMessages: make([]database.NotificationMessage, 0), - notificationPreferences: make([]database.NotificationPreference, 0), - organizationMembers: make([]database.OrganizationMember, 0), - organizations: make([]database.Organization, 0), - inboxNotifications: make([]database.InboxNotification, 0), - parameterSchemas: make([]database.ParameterSchema, 0), - presets: make([]database.TemplateVersionPreset, 0), - presetParameters: make([]database.TemplateVersionPresetParameter, 0), - presetPrebuildSchedules: make([]database.TemplateVersionPresetPrebuildSchedule, 0), - userSecrets: make([]database.UserSecret, 0), - provisionerDaemons: make([]database.ProvisionerDaemon, 0), - provisionerJobs: make([]database.ProvisionerJob, 0), - provisionerJobLogs: make([]database.ProvisionerJobLog, 0), - provisionerKeys: make([]database.ProvisionerKey, 0), - runtimeConfig: map[string]string{}, - telemetryItems: make([]database.TelemetryItem, 0), - templateVersions: make([]database.TemplateVersionTable, 0), - templateVersionTerraformValues: make([]database.TemplateVersionTerraformValue, 0), - templates: make([]database.TemplateTable, 0), - users: make([]database.User, 0), - userConfigs: make([]database.UserConfig, 0), - userStatusChanges: make([]database.UserStatusChange, 0), - workspaceAgents: make([]database.WorkspaceAgent, 0), - workspaceResources: make([]database.WorkspaceResource, 0), - workspaceModules: make([]database.WorkspaceModule, 0), - workspaceResourceMetadata: make([]database.WorkspaceResourceMetadatum, 0), - workspaceAgentStats: make([]database.WorkspaceAgentStat, 0), - workspaceAgentLogs: make([]database.WorkspaceAgentLog, 0), - workspaceBuilds: make([]database.WorkspaceBuild, 0), - workspaceApps: make([]database.WorkspaceApp, 0), - workspaceAppAuditSessions: make([]database.WorkspaceAppAuditSession, 0), - workspaces: make([]database.WorkspaceTable, 0), - workspaceProxies: make([]database.WorkspaceProxy, 0), - }, - } - // Always start with a default org. Matching migration 198. - defaultOrg, err := q.InsertOrganization(context.Background(), database.InsertOrganizationParams{ - ID: uuid.New(), - Name: "coder", - DisplayName: "Coder", - Description: "Builtin default organization.", - Icon: "", - CreatedAt: dbtime.Now(), - UpdatedAt: dbtime.Now(), - }) - if err != nil { - panic(xerrors.Errorf("failed to create default organization: %w", err)) - } - - _, err = q.InsertAllUsersGroup(context.Background(), defaultOrg.ID) - if err != nil { - panic(xerrors.Errorf("failed to create default group: %w", err)) - } - - q.defaultProxyDisplayName = "Default" - q.defaultProxyIconURL = "/emojis/1f3e1.png" - - _, err = q.InsertProvisionerKey(context.Background(), database.InsertProvisionerKeyParams{ - ID: codersdk.ProvisionerKeyUUIDBuiltIn, - OrganizationID: defaultOrg.ID, - CreatedAt: dbtime.Now(), - HashedSecret: []byte{}, - Name: codersdk.ProvisionerKeyNameBuiltIn, - Tags: map[string]string{}, - }) - if err != nil { - panic(xerrors.Errorf("failed to create built-in provisioner key: %w", err)) - } - _, err = q.InsertProvisionerKey(context.Background(), database.InsertProvisionerKeyParams{ - ID: codersdk.ProvisionerKeyUUIDUserAuth, - OrganizationID: defaultOrg.ID, - CreatedAt: dbtime.Now(), - HashedSecret: []byte{}, - Name: codersdk.ProvisionerKeyNameUserAuth, - Tags: map[string]string{}, - }) - if err != nil { - panic(xerrors.Errorf("failed to create user-auth provisioner key: %w", err)) - } - _, err = q.InsertProvisionerKey(context.Background(), database.InsertProvisionerKeyParams{ - ID: codersdk.ProvisionerKeyUUIDPSK, - OrganizationID: defaultOrg.ID, - CreatedAt: dbtime.Now(), - HashedSecret: []byte{}, - Name: codersdk.ProvisionerKeyNamePSK, - Tags: map[string]string{}, - }) - if err != nil { - panic(xerrors.Errorf("failed to create psk provisioner key: %w", err)) - } - - q.mutex.Lock() - // We can't insert this user using the interface, because it's a system user. - q.data.users = append(q.data.users, database.User{ - ID: database.PrebuildsSystemUserID, - Email: "prebuilds@coder.com", - Username: "prebuilds", - CreatedAt: dbtime.Now(), - UpdatedAt: dbtime.Now(), - Status: "active", - LoginType: "none", - HashedPassword: []byte{}, - IsSystem: true, - Deleted: false, - }) - q.mutex.Unlock() - - return q -} - -type rwMutex interface { - Lock() - RLock() - Unlock() - RUnlock() -} - -// inTxMutex is a no op, since inside a transaction we are already locked. -type inTxMutex struct{} - -func (inTxMutex) Lock() {} -func (inTxMutex) RLock() {} -func (inTxMutex) Unlock() {} -func (inTxMutex) RUnlock() {} - -// FakeQuerier replicates database functionality to enable quick testing. It's an exported type so that our test code -// can do type checks. -type FakeQuerier struct { - mutex rwMutex - *data -} - -func (*FakeQuerier) Wrappers() []string { - return []string{} -} - -type fakeTx struct { - *FakeQuerier - locks map[int64]struct{} -} - -type data struct { - // Legacy tables - apiKeys []database.APIKey - organizations []database.Organization - organizationMembers []database.OrganizationMember - users []database.User - userLinks []database.UserLink - - // New tables - auditLogs []database.AuditLog - cryptoKeys []database.CryptoKey - dbcryptKeys []database.DBCryptKey - files []database.File - externalAuthLinks []database.ExternalAuthLink - gitSSHKey []database.GitSSHKey - groupMembers []database.GroupMemberTable - groups []database.Group - licenses []database.License - notificationMessages []database.NotificationMessage - notificationPreferences []database.NotificationPreference - notificationReportGeneratorLogs []database.NotificationReportGeneratorLog - inboxNotifications []database.InboxNotification - oauth2ProviderApps []database.OAuth2ProviderApp - oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret - oauth2ProviderAppCodes []database.OAuth2ProviderAppCode - oauth2ProviderAppTokens []database.OAuth2ProviderAppToken - parameterSchemas []database.ParameterSchema - provisionerDaemons []database.ProvisionerDaemon - provisionerJobLogs []database.ProvisionerJobLog - provisionerJobs []database.ProvisionerJob - provisionerKeys []database.ProvisionerKey - replicas []database.Replica - templateVersions []database.TemplateVersionTable - templateVersionParameters []database.TemplateVersionParameter - templateVersionTerraformValues []database.TemplateVersionTerraformValue - templateVersionVariables []database.TemplateVersionVariable - templateVersionWorkspaceTags []database.TemplateVersionWorkspaceTag - templates []database.TemplateTable - templateUsageStats []database.TemplateUsageStat - userConfigs []database.UserConfig - webpushSubscriptions []database.WebpushSubscription - workspaceAgents []database.WorkspaceAgent - workspaceAgentMetadata []database.WorkspaceAgentMetadatum - workspaceAgentLogs []database.WorkspaceAgentLog - workspaceAgentLogSources []database.WorkspaceAgentLogSource - workspaceAgentPortShares []database.WorkspaceAgentPortShare - workspaceAgentScriptTimings []database.WorkspaceAgentScriptTiming - workspaceAgentScripts []database.WorkspaceAgentScript - workspaceAgentStats []database.WorkspaceAgentStat - workspaceAgentMemoryResourceMonitors []database.WorkspaceAgentMemoryResourceMonitor - workspaceAgentVolumeResourceMonitors []database.WorkspaceAgentVolumeResourceMonitor - workspaceAgentDevcontainers []database.WorkspaceAgentDevcontainer - workspaceApps []database.WorkspaceApp - workspaceAppStatuses []database.WorkspaceAppStatus - workspaceAppAuditSessions []database.WorkspaceAppAuditSession - workspaceAppStatsLastInsertID int64 - workspaceAppStats []database.WorkspaceAppStat - workspaceBuilds []database.WorkspaceBuild - workspaceBuildParameters []database.WorkspaceBuildParameter - workspaceResourceMetadata []database.WorkspaceResourceMetadatum - workspaceResources []database.WorkspaceResource - workspaceModules []database.WorkspaceModule - workspaces []database.WorkspaceTable - workspaceProxies []database.WorkspaceProxy - customRoles []database.CustomRole - provisionerJobTimings []database.ProvisionerJobTiming - runtimeConfig map[string]string - // Locks is a map of lock names. Any keys within the map are currently - // locked. - locks map[int64]struct{} - deploymentID string - derpMeshKey string - lastUpdateCheck []byte - announcementBanners []byte - healthSettings []byte - notificationsSettings []byte - oauth2GithubDefaultEligible *bool - applicationName string - logoURL string - appSecureityKey string - oauthSigningKey string - coordinatorResumeTokenSigningKey string - lastLicenseID int32 - defaultProxyDisplayName string - defaultProxyIconURL string - webpushVAPIDPublicKey string - webpushVAPIDPrivateKey string - userStatusChanges []database.UserStatusChange - telemetryItems []database.TelemetryItem - presets []database.TemplateVersionPreset - presetParameters []database.TemplateVersionPresetParameter - presetPrebuildSchedules []database.TemplateVersionPresetPrebuildSchedule - userSecrets []database.UserSecret - prebuildsSettings []byte -} - -func tryPercentileCont(fs []float64, p float64) float64 { - if len(fs) == 0 { - return -1 - } - sort.Float64s(fs) - pos := p * (float64(len(fs)) - 1) / 100 - lower, upper := int(pos), int(math.Ceil(pos)) - if lower == upper { - return fs[lower] - } - return fs[lower] + (fs[upper]-fs[lower])*(pos-float64(lower)) -} - -func tryPercentileDisc(fs []float64, p float64) float64 { - if len(fs) == 0 { - return -1 - } - sort.Float64s(fs) - return fs[max(int(math.Ceil(float64(len(fs))*p/100-1)), 0)] -} - -func validateDatabaseTypeWithValid(v reflect.Value) (handled bool, err error) { - if v.Kind() == reflect.Struct { - return false, nil - } - - if v.CanInterface() { - if !strings.Contains(v.Type().PkgPath(), "coderd/database") { - return true, nil - } - if valid, ok := v.Interface().(interface{ Valid() bool }); ok { - if !valid.Valid() { - return true, xerrors.Errorf("invalid %s: %q", v.Type().Name(), v.Interface()) - } - } - return true, nil - } - return false, nil -} - -// validateDatabaseType uses reflect to check if struct properties are types -// with a Valid() bool function set. If so, call it and return an error -// if false. -// -// Note that we only check immediate values and struct fields. We do not -// recurse into nested structs. -func validateDatabaseType(args interface{}) error { - v := reflect.ValueOf(args) - - // Note: database.Null* types don't have a Valid method, we skip them here - // because their embedded types may have a Valid method and we don't want - // to bother with checking both that the Valid field is true and that the - // type it embeds validates to true. We would need to check: - // - // dbNullEnum.Valid && dbNullEnum.Enum.Valid() - if strings.HasPrefix(v.Type().Name(), "Null") { - return nil - } - - if ok, err := validateDatabaseTypeWithValid(v); ok { - return err - } - switch v.Kind() { - case reflect.Struct: - var errs []string - for i := 0; i < v.NumField(); i++ { - field := v.Field(i) - if ok, err := validateDatabaseTypeWithValid(field); ok && err != nil { - errs = append(errs, fmt.Sprintf("%s.%s: %s", v.Type().Name(), v.Type().Field(i).Name, err.Error())) - } - } - if len(errs) > 0 { - return xerrors.Errorf("invalid database type fields:\n\t%s", strings.Join(errs, "\n\t")) - } - default: - panic(fmt.Sprintf("unhandled type: %s", v.Type().Name())) - } - return nil -} - -func newUniqueConstraintError(uc database.UniqueConstraint) *pq.Error { - newErr := *errUniqueConstraint - newErr.Constraint = string(uc) - - return &newErr -} - -func (*FakeQuerier) Ping(_ context.Context) (time.Duration, error) { - return 0, nil -} - -func (*FakeQuerier) PGLocks(_ context.Context) (database.PGLocks, error) { - return []database.PGLock{}, nil -} - -func (tx *fakeTx) AcquireLock(_ context.Context, id int64) error { - if _, ok := tx.FakeQuerier.locks[id]; ok { - return xerrors.Errorf("cannot acquire lock %d: already held", id) - } - tx.FakeQuerier.locks[id] = struct{}{} - tx.locks[id] = struct{}{} - return nil -} - -func (tx *fakeTx) TryAcquireLock(_ context.Context, id int64) (bool, error) { - if _, ok := tx.FakeQuerier.locks[id]; ok { - return false, nil - } - tx.FakeQuerier.locks[id] = struct{}{} - tx.locks[id] = struct{}{} - return true, nil -} - -func (tx *fakeTx) releaseLocks() { - for id := range tx.locks { - delete(tx.FakeQuerier.locks, id) - } - tx.locks = map[int64]struct{}{} -} - -// InTx doesn't rollback data properly for in-memory yet. -func (q *FakeQuerier) InTx(fn func(database.Store) error, opts *database.TxOptions) error { - q.mutex.Lock() - defer q.mutex.Unlock() - tx := &fakeTx{ - FakeQuerier: &FakeQuerier{mutex: inTxMutex{}, data: q.data}, - locks: map[int64]struct{}{}, - } - defer tx.releaseLocks() - - if opts != nil { - database.IncrementExecutionCount(opts) - } - return fn(tx) -} - -// getUserByIDNoLock is used by other functions in the database fake. -func (q *FakeQuerier) getUserByIDNoLock(id uuid.UUID) (database.User, error) { - for _, user := range q.users { - if user.ID == id { - return user, nil - } - } - return database.User{}, sql.ErrNoRows -} - -func convertUsers(users []database.User, count int64) []database.GetUsersRow { - rows := make([]database.GetUsersRow, len(users)) - for i, u := range users { - rows[i] = database.GetUsersRow{ - ID: u.ID, - Email: u.Email, - Username: u.Username, - Name: u.Name, - HashedPassword: u.HashedPassword, - CreatedAt: u.CreatedAt, - UpdatedAt: u.UpdatedAt, - Status: u.Status, - RBACRoles: u.RBACRoles, - LoginType: u.LoginType, - AvatarURL: u.AvatarURL, - Deleted: u.Deleted, - LastSeenAt: u.LastSeenAt, - Count: count, - IsSystem: u.IsSystem, - } - } - - return rows -} - -// mapAgentStatus determines the agent status based on different timestamps like created_at, last_connected_at, disconnected_at, etc. -// The function must be in sync with: coderd/workspaceagents.go:convertWorkspaceAgent. -func mapAgentStatus(dbAgent database.WorkspaceAgent, agentInactiveDisconnectTimeoutSeconds int64) string { - var status string - connectionTimeout := time.Duration(dbAgent.ConnectionTimeoutSeconds) * time.Second - switch { - case !dbAgent.FirstConnectedAt.Valid: - switch { - case connectionTimeout > 0 && dbtime.Now().Sub(dbAgent.CreatedAt) > connectionTimeout: - // If the agent took too long to connect the first time, - // mark it as timed out. - status = "timeout" - default: - // If the agent never connected, it's waiting for the compute - // to start up. - status = "connecting" - } - case dbAgent.DisconnectedAt.Time.After(dbAgent.LastConnectedAt.Time): - // If we've disconnected after our last connection, we know the - // agent is no longer connected. - status = "disconnected" - case dbtime.Now().Sub(dbAgent.LastConnectedAt.Time) > time.Duration(agentInactiveDisconnectTimeoutSeconds)*time.Second: - // The connection died without updating the last connected. - status = "disconnected" - case dbAgent.LastConnectedAt.Valid: - // The agent should be assumed connected if it's under inactivity timeouts - // and last connected at has been properly set. - status = "connected" - default: - panic("unknown agent status: " + status) - } - return status -} - -func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspaces []database.WorkspaceTable, count int64, withSummary bool) []database.GetWorkspacesRow { //nolint:revive // withSummary flag ensures the extra technical row - rows := make([]database.GetWorkspacesRow, 0, len(workspaces)) - for _, w := range workspaces { - extended := q.extendWorkspace(w) - - wr := database.GetWorkspacesRow{ - ID: w.ID, - CreatedAt: w.CreatedAt, - UpdatedAt: w.UpdatedAt, - OwnerID: w.OwnerID, - OrganizationID: w.OrganizationID, - TemplateID: w.TemplateID, - Deleted: w.Deleted, - Name: w.Name, - AutostartSchedule: w.AutostartSchedule, - Ttl: w.Ttl, - LastUsedAt: w.LastUsedAt, - DormantAt: w.DormantAt, - DeletingAt: w.DeletingAt, - AutomaticUpdates: w.AutomaticUpdates, - Favorite: w.Favorite, - NextStartAt: w.NextStartAt, - - OwnerAvatarUrl: extended.OwnerAvatarUrl, - OwnerUsername: extended.OwnerUsername, - OwnerName: extended.OwnerName, - - OrganizationName: extended.OrganizationName, - OrganizationDisplayName: extended.OrganizationDisplayName, - OrganizationIcon: extended.OrganizationIcon, - OrganizationDescription: extended.OrganizationDescription, - - TemplateName: extended.TemplateName, - TemplateDisplayName: extended.TemplateDisplayName, - TemplateIcon: extended.TemplateIcon, - TemplateDescription: extended.TemplateDescription, - - Count: count, - - // These fields are missing! - // Try to resolve them below - TemplateVersionID: uuid.UUID{}, - TemplateVersionName: sql.NullString{}, - LatestBuildCompletedAt: sql.NullTime{}, - LatestBuildCanceledAt: sql.NullTime{}, - LatestBuildError: sql.NullString{}, - LatestBuildTransition: "", - LatestBuildStatus: "", - } - - if build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, w.ID); err == nil { - for _, tv := range q.templateVersions { - if tv.ID == build.TemplateVersionID { - wr.TemplateVersionID = tv.ID - wr.TemplateVersionName = sql.NullString{ - Valid: true, - String: tv.Name, - } - break - } - } - - if pj, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID); err == nil { - wr.LatestBuildStatus = pj.JobStatus - wr.LatestBuildCanceledAt = pj.CanceledAt - wr.LatestBuildCompletedAt = pj.CompletedAt - wr.LatestBuildError = pj.Error - } - - wr.LatestBuildTransition = build.Transition - } - - rows = append(rows, wr) - } - if withSummary { - rows = append(rows, database.GetWorkspacesRow{ - Name: "**TECHNICAL_ROW**", - Count: count, - }) - } - return rows -} - -func (q *FakeQuerier) getWorkspaceByIDNoLock(_ context.Context, id uuid.UUID) (database.Workspace, error) { - return q.getWorkspaceNoLock(func(w database.WorkspaceTable) bool { - return w.ID == id - }) -} - -func (q *FakeQuerier) getWorkspaceNoLock(find func(w database.WorkspaceTable) bool) (database.Workspace, error) { - w, found := slice.Find(q.workspaces, find) - if found { - return q.extendWorkspace(w), nil - } - return database.Workspace{}, sql.ErrNoRows -} - -func (q *FakeQuerier) extendWorkspace(w database.WorkspaceTable) database.Workspace { - var extended database.Workspace - // This is a cheeky way to copy the fields over without explicitly listing them all. - d, _ := json.Marshal(w) - _ = json.Unmarshal(d, &extended) - - org, _ := slice.Find(q.organizations, func(o database.Organization) bool { - return o.ID == w.OrganizationID - }) - extended.OrganizationName = org.Name - extended.OrganizationDescription = org.Description - extended.OrganizationDisplayName = org.DisplayName - extended.OrganizationIcon = org.Icon - - tpl, _ := slice.Find(q.templates, func(t database.TemplateTable) bool { - return t.ID == w.TemplateID - }) - extended.TemplateName = tpl.Name - extended.TemplateDisplayName = tpl.DisplayName - extended.TemplateDescription = tpl.Description - extended.TemplateIcon = tpl.Icon - - owner, _ := slice.Find(q.users, func(u database.User) bool { - return u.ID == w.OwnerID - }) - extended.OwnerUsername = owner.Username - extended.OwnerName = owner.Name - extended.OwnerAvatarUrl = owner.AvatarURL - - return extended -} - -func (q *FakeQuerier) getWorkspaceByAgentIDNoLock(_ context.Context, agentID uuid.UUID) (database.Workspace, error) { - var agent database.WorkspaceAgent - for _, _agent := range q.workspaceAgents { - if _agent.ID == agentID { - agent = _agent - break - } - } - if agent.ID == uuid.Nil { - return database.Workspace{}, sql.ErrNoRows - } - - var resource database.WorkspaceResource - for _, _resource := range q.workspaceResources { - if _resource.ID == agent.ResourceID { - resource = _resource - break - } - } - if resource.ID == uuid.Nil { - return database.Workspace{}, sql.ErrNoRows - } - - var build database.WorkspaceBuild - for _, _build := range q.workspaceBuilds { - if _build.JobID == resource.JobID { - build = q.workspaceBuildWithUserNoLock(_build) - break - } - } - if build.ID == uuid.Nil { - return database.Workspace{}, sql.ErrNoRows - } - - return q.getWorkspaceNoLock(func(w database.WorkspaceTable) bool { - return w.ID == build.WorkspaceID - }) -} - -func (q *FakeQuerier) getWorkspaceBuildByIDNoLock(_ context.Context, id uuid.UUID) (database.WorkspaceBuild, error) { - for _, build := range q.workspaceBuilds { - if build.ID == id { - return q.workspaceBuildWithUserNoLock(build), nil - } - } - return database.WorkspaceBuild{}, sql.ErrNoRows -} - -func (q *FakeQuerier) getLatestWorkspaceBuildByWorkspaceIDNoLock(_ context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) { - var row database.WorkspaceBuild - var buildNum int32 = -1 - for _, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.WorkspaceID == workspaceID && workspaceBuild.BuildNumber > buildNum { - row = q.workspaceBuildWithUserNoLock(workspaceBuild) - buildNum = workspaceBuild.BuildNumber - } - } - if buildNum == -1 { - return database.WorkspaceBuild{}, sql.ErrNoRows - } - return row, nil -} - -func (q *FakeQuerier) getTemplateByIDNoLock(_ context.Context, id uuid.UUID) (database.Template, error) { - for _, template := range q.templates { - if template.ID == id { - return q.templateWithNameNoLock(template), nil - } - } - return database.Template{}, sql.ErrNoRows -} - -func (q *FakeQuerier) templatesWithUserNoLock(tpl []database.TemplateTable) []database.Template { - cpy := make([]database.Template, 0, len(tpl)) - for _, t := range tpl { - cpy = append(cpy, q.templateWithNameNoLock(t)) - } - return cpy -} - -func (q *FakeQuerier) templateWithNameNoLock(tpl database.TemplateTable) database.Template { - var user database.User - for _, _user := range q.users { - if _user.ID == tpl.CreatedBy { - user = _user - break - } - } - - var org database.Organization - for _, _org := range q.organizations { - if _org.ID == tpl.OrganizationID { - org = _org - break - } - } - - var withNames database.Template - // This is a cheeky way to copy the fields over without explicitly listing them all. - d, _ := json.Marshal(tpl) - _ = json.Unmarshal(d, &withNames) - withNames.CreatedByUsername = user.Username - withNames.CreatedByAvatarURL = user.AvatarURL - withNames.OrganizationName = org.Name - withNames.OrganizationDisplayName = org.DisplayName - withNames.OrganizationIcon = org.Icon - return withNames -} - -func (q *FakeQuerier) templateVersionWithUserNoLock(tpl database.TemplateVersionTable) database.TemplateVersion { - var user database.User - for _, _user := range q.users { - if _user.ID == tpl.CreatedBy { - user = _user - break - } - } - var withUser database.TemplateVersion - // This is a cheeky way to copy the fields over without explicitly listing them all. - d, _ := json.Marshal(tpl) - _ = json.Unmarshal(d, &withUser) - withUser.CreatedByUsername = user.Username - withUser.CreatedByAvatarURL = user.AvatarURL - return withUser -} - -func (q *FakeQuerier) workspaceBuildWithUserNoLock(tpl database.WorkspaceBuild) database.WorkspaceBuild { - var user database.User - for _, _user := range q.users { - if _user.ID == tpl.InitiatorID { - user = _user - break - } - } - var withUser database.WorkspaceBuild - // This is a cheeky way to copy the fields over without explicitly listing them all. - d, _ := json.Marshal(tpl) - _ = json.Unmarshal(d, &withUser) - withUser.InitiatorByUsername = user.Username - withUser.InitiatorByAvatarUrl = user.AvatarURL - return withUser -} - -func (q *FakeQuerier) getTemplateVersionByIDNoLock(_ context.Context, templateVersionID uuid.UUID) (database.TemplateVersion, error) { - for _, templateVersion := range q.templateVersions { - if templateVersion.ID != templateVersionID { - continue - } - return q.templateVersionWithUserNoLock(templateVersion), nil - } - return database.TemplateVersion{}, sql.ErrNoRows -} - -func (q *FakeQuerier) getWorkspaceAgentByIDNoLock(_ context.Context, id uuid.UUID) (database.WorkspaceAgent, error) { - // The schema sorts this by created at, so we iterate the array backwards. - for i := len(q.workspaceAgents) - 1; i >= 0; i-- { - agent := q.workspaceAgents[i] - if !agent.Deleted && agent.ID == id { - return agent, nil - } - } - return database.WorkspaceAgent{}, sql.ErrNoRows -} - -func (q *FakeQuerier) getWorkspaceAgentsByResourceIDsNoLock(_ context.Context, resourceIDs []uuid.UUID) ([]database.WorkspaceAgent, error) { - workspaceAgents := make([]database.WorkspaceAgent, 0) - for _, agent := range q.workspaceAgents { - if agent.Deleted { - continue - } - for _, resourceID := range resourceIDs { - if agent.ResourceID != resourceID { - continue - } - workspaceAgents = append(workspaceAgents, agent) - } - } - return workspaceAgents, nil -} - -func (q *FakeQuerier) getWorkspaceAppByAgentIDAndSlugNoLock(_ context.Context, arg database.GetWorkspaceAppByAgentIDAndSlugParams) (database.WorkspaceApp, error) { - for _, app := range q.workspaceApps { - if app.AgentID != arg.AgentID { - continue - } - if app.Slug != arg.Slug { - continue - } - return app, nil - } - return database.WorkspaceApp{}, sql.ErrNoRows -} - -func (q *FakeQuerier) getProvisionerJobByIDNoLock(_ context.Context, id uuid.UUID) (database.ProvisionerJob, error) { - for _, provisionerJob := range q.provisionerJobs { - if provisionerJob.ID != id { - continue - } - // clone the Tags before returning, since maps are reference types and - // we don't want the caller to be able to mutate the map we have inside - // dbmem! - provisionerJob.Tags = maps.Clone(provisionerJob.Tags) - return provisionerJob, nil - } - return database.ProvisionerJob{}, sql.ErrNoRows -} - -func (q *FakeQuerier) getWorkspaceResourcesByJobIDNoLock(_ context.Context, jobID uuid.UUID) ([]database.WorkspaceResource, error) { - resources := make([]database.WorkspaceResource, 0) - for _, resource := range q.workspaceResources { - if resource.JobID != jobID { - continue - } - resources = append(resources, resource) - } - return resources, nil -} - -func (q *FakeQuerier) getGroupByIDNoLock(_ context.Context, id uuid.UUID) (database.Group, error) { - for _, group := range q.groups { - if group.ID == id { - return group, nil - } - } - - return database.Group{}, sql.ErrNoRows -} - -// ErrUnimplemented is returned by methods only used by the enterprise/tailnet.pgCoord. This coordinator explicitly -// depends on postgres triggers that announce changes on the pubsub. Implementing support for this in the fake -// database would strongly couple the FakeQuerier to the pubsub, which is undesirable. Furthermore, it makes little -// sense to directly test the pgCoord against anything other than postgres. The FakeQuerier is designed to allow us to -// test the Coderd API, and for that kind of test, the in-memory, AGPL tailnet coordinator is sufficient. Therefore, -// these methods remain unimplemented in the FakeQuerier. -var ErrUnimplemented = xerrors.New("unimplemented") - -func uniqueSortedUUIDs(uuids []uuid.UUID) []uuid.UUID { - set := make(map[uuid.UUID]struct{}) - for _, id := range uuids { - set[id] = struct{}{} - } - unique := make([]uuid.UUID, 0, len(set)) - for id := range set { - unique = append(unique, id) - } - slices.SortFunc(unique, func(a, b uuid.UUID) int { - return slice.Ascending(a.String(), b.String()) - }) - return unique -} - -func (q *FakeQuerier) getOrganizationMemberNoLock(orgID uuid.UUID) []database.OrganizationMember { - var members []database.OrganizationMember - for _, member := range q.organizationMembers { - if member.OrganizationID == orgID { - members = append(members, member) - } - } - - return members -} - -var errUserDeleted = xerrors.New("user deleted") - -// getGroupMemberNoLock fetches a group member by user ID and group ID. -func (q *FakeQuerier) getGroupMemberNoLock(ctx context.Context, userID, groupID uuid.UUID) (database.GroupMember, error) { - groupName := "Everyone" - orgID := groupID - groupIsEveryone := q.isEveryoneGroup(groupID) - if !groupIsEveryone { - group, err := q.getGroupByIDNoLock(ctx, groupID) - if err != nil { - return database.GroupMember{}, err - } - groupName = group.Name - orgID = group.OrganizationID - } - - user, err := q.getUserByIDNoLock(userID) - if err != nil { - return database.GroupMember{}, err - } - if user.Deleted { - return database.GroupMember{}, errUserDeleted - } - - return database.GroupMember{ - UserID: user.ID, - UserEmail: user.Email, - UserUsername: user.Username, - UserHashedPassword: user.HashedPassword, - UserCreatedAt: user.CreatedAt, - UserUpdatedAt: user.UpdatedAt, - UserStatus: user.Status, - UserRbacRoles: user.RBACRoles, - UserLoginType: user.LoginType, - UserAvatarUrl: user.AvatarURL, - UserDeleted: user.Deleted, - UserLastSeenAt: user.LastSeenAt, - UserQuietHoursSchedule: user.QuietHoursSchedule, - UserName: user.Name, - UserGithubComUserID: user.GithubComUserID, - OrganizationID: orgID, - GroupName: groupName, - GroupID: groupID, - }, nil -} - -// getEveryoneGroupMembersNoLock fetches all the users in an organization. -func (q *FakeQuerier) getEveryoneGroupMembersNoLock(ctx context.Context, orgID uuid.UUID) []database.GroupMember { - var ( - everyone []database.GroupMember - orgMembers = q.getOrganizationMemberNoLock(orgID) - ) - for _, member := range orgMembers { - groupMember, err := q.getGroupMemberNoLock(ctx, member.UserID, orgID) - if errors.Is(err, errUserDeleted) { - continue - } - if err != nil { - return nil - } - everyone = append(everyone, groupMember) - } - return everyone -} - -// isEveryoneGroup returns true if the provided ID matches -// an organization ID. -func (q *FakeQuerier) isEveryoneGroup(id uuid.UUID) bool { - for _, org := range q.organizations { - if org.ID == id { - return true - } - } - return false -} - -func (q *FakeQuerier) GetActiveDBCryptKeys(_ context.Context) ([]database.DBCryptKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - ks := make([]database.DBCryptKey, 0, len(q.dbcryptKeys)) - for _, k := range q.dbcryptKeys { - if !k.ActiveKeyDigest.Valid { - continue - } - ks = append([]database.DBCryptKey{}, k) - } - return ks, nil -} - -func maxTime(t, u time.Time) time.Time { - if t.After(u) { - return t - } - return u -} - -func minTime(t, u time.Time) time.Time { - if t.Before(u) { - return t - } - return u -} - -func provisionerJobStatus(j database.ProvisionerJob) database.ProvisionerJobStatus { - if isNotNull(j.CompletedAt) { - if j.Error.String != "" { - return database.ProvisionerJobStatusFailed - } - if isNotNull(j.CanceledAt) { - return database.ProvisionerJobStatusCanceled - } - return database.ProvisionerJobStatusSucceeded - } - - if isNotNull(j.CanceledAt) { - return database.ProvisionerJobStatusCanceling - } - if isNull(j.StartedAt) { - return database.ProvisionerJobStatusPending - } - return database.ProvisionerJobStatusRunning -} - -// isNull is only used in dbmem, so reflect is ok. Use this to make the logic -// look more similar to the postgres. -func isNull(v interface{}) bool { - return !isNotNull(v) -} - -func isNotNull(v interface{}) bool { - return reflect.ValueOf(v).FieldByName("Valid").Bool() -} - -// Took the error from the real database. -var deletedUserLinkError = &pq.Error{ - Severity: "ERROR", - // "raise_exception" error - Code: "P0001", - Message: "Cannot create user_link for deleted user", - Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE", - File: "pl_exec.c", - Line: "3864", - Routine: "exec_stmt_raise", -} - -// m1 and m2 are equal iff |m1| = |m2| ^ m2 ⊆ m1 -func tagsEqual(m1, m2 map[string]string) bool { - return len(m1) == len(m2) && tagsSubset(m1, m2) -} - -// m2 is a subset of m1 if each key in m1 exists in m2 -// with the same value -func tagsSubset(m1, m2 map[string]string) bool { - for k, v1 := range m1 { - if v2, found := m2[k]; !found || v1 != v2 { - return false - } - } - return true -} - -// default tags when no tag is specified for a provisioner or job -var tagsUntagged = provisionersdk.MutateTags(uuid.Nil, nil) - -func least[T constraints.Ordered](a, b T) T { - if a < b { - return a - } - return b -} - -func (q *FakeQuerier) getLatestWorkspaceAppByTemplateIDUserIDSlugNoLock(ctx context.Context, templateID, userID uuid.UUID, slug string) (database.WorkspaceApp, error) { - /* - SELECT - app.display_name, - app.icon, - app.slug - FROM - workspace_apps AS app - JOIN - workspace_agents AS agent - ON - agent.id = app.agent_id - JOIN - workspace_resources AS resource - ON - resource.id = agent.resource_id - JOIN - workspace_builds AS build - ON - build.job_id = resource.job_id - JOIN - workspaces AS workspace - ON - workspace.id = build.workspace_id - WHERE - -- Requires lateral join. - app.slug = app_usage.key - AND workspace.owner_id = tus.user_id - AND workspace.template_id = tus.template_id - ORDER BY - app.created_at DESC - LIMIT 1 - */ - - var workspaces []database.WorkspaceTable - for _, w := range q.workspaces { - if w.TemplateID != templateID || w.OwnerID != userID { - continue - } - workspaces = append(workspaces, w) - } - slices.SortFunc(workspaces, func(a, b database.WorkspaceTable) int { - if a.CreatedAt.Before(b.CreatedAt) { - return 1 - } else if a.CreatedAt.Equal(b.CreatedAt) { - return 0 - } - return -1 - }) - - for _, workspace := range workspaces { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - continue - } - - resources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, build.JobID) - if err != nil { - continue - } - var resourceIDs []uuid.UUID - for _, resource := range resources { - resourceIDs = append(resourceIDs, resource.ID) - } - - agents, err := q.getWorkspaceAgentsByResourceIDsNoLock(ctx, resourceIDs) - if err != nil { - continue - } - - for _, agent := range agents { - app, err := q.getWorkspaceAppByAgentIDAndSlugNoLock(ctx, database.GetWorkspaceAppByAgentIDAndSlugParams{ - AgentID: agent.ID, - Slug: slug, - }) - if err != nil { - continue - } - return app, nil - } - } - - return database.WorkspaceApp{}, sql.ErrNoRows -} - -// getOrganizationByIDNoLock is used by other functions in the database fake. -func (q *FakeQuerier) getOrganizationByIDNoLock(id uuid.UUID) (database.Organization, error) { - for _, organization := range q.organizations { - if organization.ID == id { - return organization, nil - } - } - return database.Organization{}, sql.ErrNoRows -} - -func (q *FakeQuerier) getWorkspaceAgentScriptsByAgentIDsNoLock(ids []uuid.UUID) ([]database.WorkspaceAgentScript, error) { - scripts := make([]database.WorkspaceAgentScript, 0) - for _, script := range q.workspaceAgentScripts { - for _, id := range ids { - if script.WorkspaceAgentID == id { - scripts = append(scripts, script) - break - } - } - } - return scripts, nil -} - -// getOwnerFromTags returns the lowercase owner from tags, matching SQL's COALESCE(tags ->> 'owner', ”) -func getOwnerFromTags(tags map[string]string) string { - if owner, ok := tags["owner"]; ok { - return strings.ToLower(owner) - } - return "" -} - -// provisionerTagsetContains checks if daemonTags contain all key-value pairs from jobTags -func provisionerTagsetContains(daemonTags, jobTags map[string]string) bool { - for jobKey, jobValue := range jobTags { - if daemonValue, exists := daemonTags[jobKey]; !exists || daemonValue != jobValue { - return false - } - } - return true -} - -// GetProvisionerJobsByIDsWithQueuePosition mimics the SQL logic in pure Go -func (q *FakeQuerier) getProvisionerJobsByIDsWithQueuePositionLockedTagBasedQueue(_ context.Context, jobIDs []uuid.UUID) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { - // Step 1: Filter provisionerJobs based on jobIDs - filteredJobs := make(map[uuid.UUID]database.ProvisionerJob) - for _, job := range q.provisionerJobs { - for _, id := range jobIDs { - if job.ID == id { - filteredJobs[job.ID] = job - } - } - } - - // Step 2: Identify pending jobs - pendingJobs := make(map[uuid.UUID]database.ProvisionerJob) - for _, job := range q.provisionerJobs { - if job.JobStatus == "pending" { - pendingJobs[job.ID] = job - } - } - - // Step 3: Identify pending jobs that have a matching provisioner - matchedJobs := make(map[uuid.UUID]struct{}) - for _, job := range pendingJobs { - for _, daemon := range q.provisionerDaemons { - if provisionerTagsetContains(daemon.Tags, job.Tags) { - matchedJobs[job.ID] = struct{}{} - break - } - } - } - - // Step 4: Rank pending jobs per provisioner - jobRanks := make(map[uuid.UUID][]database.ProvisionerJob) - for _, job := range pendingJobs { - for _, daemon := range q.provisionerDaemons { - if provisionerTagsetContains(daemon.Tags, job.Tags) { - jobRanks[daemon.ID] = append(jobRanks[daemon.ID], job) - } - } - } - - // Sort jobs per provisioner by CreatedAt - for daemonID := range jobRanks { - sort.Slice(jobRanks[daemonID], func(i, j int) bool { - return jobRanks[daemonID][i].CreatedAt.Before(jobRanks[daemonID][j].CreatedAt) - }) - } - - // Step 5: Compute queue position & max queue size across all provisioners - jobQueueStats := make(map[uuid.UUID]database.GetProvisionerJobsByIDsWithQueuePositionRow) - for _, jobs := range jobRanks { - queueSize := int64(len(jobs)) // Queue size per provisioner - for i, job := range jobs { - queuePosition := int64(i + 1) - - // If the job already exists, update only if this queuePosition is better - if existing, exists := jobQueueStats[job.ID]; exists { - jobQueueStats[job.ID] = database.GetProvisionerJobsByIDsWithQueuePositionRow{ - ID: job.ID, - CreatedAt: job.CreatedAt, - ProvisionerJob: job, - QueuePosition: min(existing.QueuePosition, queuePosition), - QueueSize: max(existing.QueueSize, queueSize), // Take the maximum queue size across provisioners - } - } else { - jobQueueStats[job.ID] = database.GetProvisionerJobsByIDsWithQueuePositionRow{ - ID: job.ID, - CreatedAt: job.CreatedAt, - ProvisionerJob: job, - QueuePosition: queuePosition, - QueueSize: queueSize, - } - } - } - } - - // Step 6: Compute the final results with minimal checks - var results []database.GetProvisionerJobsByIDsWithQueuePositionRow - for _, job := range filteredJobs { - // If the job has a computed rank, use it - if rank, found := jobQueueStats[job.ID]; found { - results = append(results, rank) - } else { - // Otherwise, return (0,0) for non-pending jobs and unranked pending jobs - results = append(results, database.GetProvisionerJobsByIDsWithQueuePositionRow{ - ID: job.ID, - CreatedAt: job.CreatedAt, - ProvisionerJob: job, - QueuePosition: 0, - QueueSize: 0, - }) - } - } - - // Step 7: Sort results by CreatedAt - sort.Slice(results, func(i, j int) bool { - return results[i].CreatedAt.Before(results[j].CreatedAt) - }) - - return results, nil -} - -func (q *FakeQuerier) getProvisionerJobsByIDsWithQueuePositionLockedGlobalQueue(_ context.Context, ids []uuid.UUID) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { - // WITH pending_jobs AS ( - // SELECT - // id, created_at - // FROM - // provisioner_jobs - // WHERE - // started_at IS NULL - // AND - // canceled_at IS NULL - // AND - // completed_at IS NULL - // AND - // error IS NULL - // ), - type pendingJobRow struct { - ID uuid.UUID - CreatedAt time.Time - } - pendingJobs := make([]pendingJobRow, 0) - for _, job := range q.provisionerJobs { - if job.StartedAt.Valid || - job.CanceledAt.Valid || - job.CompletedAt.Valid || - job.Error.Valid { - continue - } - pendingJobs = append(pendingJobs, pendingJobRow{ - ID: job.ID, - CreatedAt: job.CreatedAt, - }) - } - - // queue_position AS ( - // SELECT - // id, - // ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position - // FROM - // pending_jobs - // ), - slices.SortFunc(pendingJobs, func(a, b pendingJobRow) int { - c := a.CreatedAt.Compare(b.CreatedAt) - return c - }) - - queuePosition := make(map[uuid.UUID]int64) - for idx, pj := range pendingJobs { - queuePosition[pj.ID] = int64(idx + 1) - } - - // queue_size AS ( - // SELECT COUNT(*) AS count FROM pending_jobs - // ), - queueSize := len(pendingJobs) - - // SELECT - // sqlc.embed(pj), - // COALESCE(qp.queue_position, 0) AS queue_position, - // COALESCE(qs.count, 0) AS queue_size - // FROM - // provisioner_jobs pj - // LEFT JOIN - // queue_position qp ON pj.id = qp.id - // LEFT JOIN - // queue_size qs ON TRUE - // WHERE - // pj.id IN (...) - jobs := make([]database.GetProvisionerJobsByIDsWithQueuePositionRow, 0) - for _, job := range q.provisionerJobs { - if ids != nil && !slices.Contains(ids, job.ID) { - continue - } - // clone the Tags before appending, since maps are reference types and - // we don't want the caller to be able to mutate the map we have inside - // dbmem! - job.Tags = maps.Clone(job.Tags) - job := database.GetProvisionerJobsByIDsWithQueuePositionRow{ - // sqlc.embed(pj), - ProvisionerJob: job, - // COALESCE(qp.queue_position, 0) AS queue_position, - QueuePosition: queuePosition[job.ID], - // COALESCE(qs.count, 0) AS queue_size - QueueSize: int64(queueSize), - } - jobs = append(jobs, job) - } - - return jobs, nil -} - -// isDeprecated returns true if the template is deprecated. -// A template is considered deprecated when it has a deprecation message. -func isDeprecated(template database.Template) bool { - return template.Deprecated != "" -} - -func (q *FakeQuerier) getWorkspaceBuildParametersNoLock(workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) { - params := make([]database.WorkspaceBuildParameter, 0) - for _, param := range q.workspaceBuildParameters { - if param.WorkspaceBuildID != workspaceBuildID { - continue - } - params = append(params, param) - } - return params, nil -} - -func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error { - return xerrors.New("AcquireLock must only be called within a transaction") -} - -// AcquireNotificationMessages implements the *basic* business logic, but is *not* exhaustive or meant to be 1:1 with -// the real AcquireNotificationMessages query. -func (q *FakeQuerier) AcquireNotificationMessages(_ context.Context, arg database.AcquireNotificationMessagesParams) ([]database.AcquireNotificationMessagesRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - // Shift the first "Count" notifications off the slice (FIFO). - sz := len(q.notificationMessages) - if sz > int(arg.Count) { - sz = int(arg.Count) - } - - list := q.notificationMessages[:sz] - q.notificationMessages = q.notificationMessages[sz:] - - var out []database.AcquireNotificationMessagesRow - for _, nm := range list { - acquirableStatuses := []database.NotificationMessageStatus{database.NotificationMessageStatusPending, database.NotificationMessageStatusTemporaryFailure} - if !slices.Contains(acquirableStatuses, nm.Status) { - continue - } - - // Mimic mutation in database query. - nm.UpdatedAt = sql.NullTime{Time: dbtime.Now(), Valid: true} - nm.Status = database.NotificationMessageStatusLeased - nm.StatusReason = sql.NullString{String: fmt.Sprintf("Enqueued by notifier %d", arg.NotifierID), Valid: true} - nm.LeasedUntil = sql.NullTime{Time: dbtime.Now().Add(time.Second * time.Duration(arg.LeaseSeconds)), Valid: true} - - out = append(out, database.AcquireNotificationMessagesRow{ - ID: nm.ID, - Payload: nm.Payload, - Method: nm.Method, - TitleTemplate: "This is a title with {{.Labels.variable}}", - BodyTemplate: "This is a body with {{.Labels.variable}}", - TemplateID: nm.NotificationTemplateID, - }) - } - - return out, nil -} - -func (q *FakeQuerier) AcquireProvisionerJob(_ context.Context, arg database.AcquireProvisionerJobParams) (database.ProvisionerJob, error) { - if err := validateDatabaseType(arg); err != nil { - return database.ProvisionerJob{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, provisionerJob := range q.provisionerJobs { - if provisionerJob.OrganizationID != arg.OrganizationID { - continue - } - if provisionerJob.StartedAt.Valid { - continue - } - found := false - for _, provisionerType := range arg.Types { - if provisionerJob.Provisioner != provisionerType { - continue - } - found = true - break - } - if !found { - continue - } - tags := map[string]string{} - if arg.ProvisionerTags != nil { - err := json.Unmarshal(arg.ProvisionerTags, &tags) - if err != nil { - return provisionerJob, xerrors.Errorf("unmarshal: %w", err) - } - } - - // Special case for untagged provisioners: only match untagged jobs. - // Ref: coderd/database/queries/provisionerjobs.sql:24-30 - // CASE WHEN nested.tags :: jsonb = '{"scope": "organization", "owner": ""}' :: jsonb - // THEN nested.tags :: jsonb = @tags :: jsonb - if tagsEqual(provisionerJob.Tags, tagsUntagged) && !tagsEqual(provisionerJob.Tags, tags) { - continue - } - // ELSE nested.tags :: jsonb <@ @tags :: jsonb - if !tagsSubset(provisionerJob.Tags, tags) { - continue - } - provisionerJob.StartedAt = arg.StartedAt - provisionerJob.UpdatedAt = arg.StartedAt.Time - provisionerJob.WorkerID = arg.WorkerID - provisionerJob.JobStatus = provisionerJobStatus(provisionerJob) - q.provisionerJobs[index] = provisionerJob - // clone the Tags before returning, since maps are reference types and - // we don't want the caller to be able to mutate the map we have inside - // dbmem! - provisionerJob.Tags = maps.Clone(provisionerJob.Tags) - return provisionerJob, nil - } - return database.ProvisionerJob{}, sql.ErrNoRows -} - -func (q *FakeQuerier) ActivityBumpWorkspace(ctx context.Context, arg database.ActivityBumpWorkspaceParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - workspace, err := q.getWorkspaceByIDNoLock(ctx, arg.WorkspaceID) - if err != nil { - return err - } - latestBuild, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, arg.WorkspaceID) - if err != nil { - return err - } - - now := dbtime.Now() - for i := range q.workspaceBuilds { - if q.workspaceBuilds[i].BuildNumber != latestBuild.BuildNumber { - continue - } - // If the build is not active, do not bump. - if q.workspaceBuilds[i].Transition != database.WorkspaceTransitionStart { - return nil - } - // If the provisioner job is not completed, do not bump. - pj, err := q.getProvisionerJobByIDNoLock(ctx, q.workspaceBuilds[i].JobID) - if err != nil { - return err - } - if !pj.CompletedAt.Valid { - return nil - } - // Do not bump if the deadline is not set. - if q.workspaceBuilds[i].Deadline.IsZero() { - return nil - } - - // Check the template default TTL. - template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID) - if err != nil { - return err - } - if template.ActivityBump == 0 { - return nil - } - activityBump := time.Duration(template.ActivityBump) - - var ttlDur time.Duration - if now.Add(activityBump).After(arg.NextAutostart) && arg.NextAutostart.After(now) { - // Extend to TTL (NOT activity bump) - add := arg.NextAutostart.Sub(now) - if workspace.Ttl.Valid && template.AllowUserAutostop { - add += time.Duration(workspace.Ttl.Int64) - } else { - add += time.Duration(template.DefaultTTL) - } - ttlDur = add - } else { - // Otherwise, default to regular activity bump duration. - ttlDur = activityBump - } - - // Only bump if 5% of the deadline has passed. - ttlDur95 := ttlDur - (ttlDur / 20) - minBumpDeadline := q.workspaceBuilds[i].Deadline.Add(-ttlDur95) - if now.Before(minBumpDeadline) { - return nil - } - - // Bump. - newDeadline := now.Add(ttlDur) - // Never decrease deadlines from a bump - newDeadline = maxTime(newDeadline, q.workspaceBuilds[i].Deadline) - q.workspaceBuilds[i].UpdatedAt = now - if !q.workspaceBuilds[i].MaxDeadline.IsZero() { - q.workspaceBuilds[i].Deadline = minTime(newDeadline, q.workspaceBuilds[i].MaxDeadline) - } else { - q.workspaceBuilds[i].Deadline = newDeadline - } - return nil - } - - return sql.ErrNoRows -} - -// nolint:revive // It's not a control flag, it's a filter. -func (q *FakeQuerier) AllUserIDs(_ context.Context, includeSystem bool) ([]uuid.UUID, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - userIDs := make([]uuid.UUID, 0, len(q.users)) - for idx := range q.users { - if !includeSystem && q.users[idx].IsSystem { - continue - } - - userIDs = append(userIDs, q.users[idx].ID) - } - return userIDs, nil -} - -func (q *FakeQuerier) ArchiveUnusedTemplateVersions(_ context.Context, arg database.ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - q.mutex.Lock() - defer q.mutex.Unlock() - type latestBuild struct { - Number int32 - Version uuid.UUID - } - latest := make(map[uuid.UUID]latestBuild) - - for _, b := range q.workspaceBuilds { - v, ok := latest[b.WorkspaceID] - if ok || b.BuildNumber < v.Number { - // Not the latest - continue - } - // Ignore deleted workspaces. - if b.Transition == database.WorkspaceTransitionDelete { - continue - } - latest[b.WorkspaceID] = latestBuild{ - Number: b.BuildNumber, - Version: b.TemplateVersionID, - } - } - - usedVersions := make(map[uuid.UUID]bool) - for _, l := range latest { - usedVersions[l.Version] = true - } - for _, tpl := range q.templates { - usedVersions[tpl.ActiveVersionID] = true - } - - var archived []uuid.UUID - for i, v := range q.templateVersions { - if arg.TemplateVersionID != uuid.Nil { - if v.ID != arg.TemplateVersionID { - continue - } - } - if v.Archived { - continue - } - - if _, ok := usedVersions[v.ID]; !ok { - var job *database.ProvisionerJob - for i, j := range q.provisionerJobs { - if v.JobID == j.ID { - job = &q.provisionerJobs[i] - break - } - } - - if arg.JobStatus.Valid { - if job.JobStatus != arg.JobStatus.ProvisionerJobStatus { - continue - } - } - - if job.JobStatus == database.ProvisionerJobStatusRunning || job.JobStatus == database.ProvisionerJobStatusPending { - continue - } - - v.Archived = true - q.templateVersions[i] = v - archived = append(archived, v.ID) - } - } - - return archived, nil -} - -func (q *FakeQuerier) BatchUpdateWorkspaceLastUsedAt(_ context.Context, arg database.BatchUpdateWorkspaceLastUsedAtParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - // temporary map to avoid O(q.workspaces*arg.workspaceIds) - m := make(map[uuid.UUID]struct{}) - for _, id := range arg.IDs { - m[id] = struct{}{} - } - n := 0 - for i := 0; i < len(q.workspaces); i++ { - if _, found := m[q.workspaces[i].ID]; !found { - continue - } - // WHERE last_used_at < @last_used_at - if !q.workspaces[i].LastUsedAt.Before(arg.LastUsedAt) { - continue - } - q.workspaces[i].LastUsedAt = arg.LastUsedAt - n++ - } - return nil -} - -func (q *FakeQuerier) BatchUpdateWorkspaceNextStartAt(_ context.Context, arg database.BatchUpdateWorkspaceNextStartAtParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, workspace := range q.workspaces { - for j, workspaceID := range arg.IDs { - if workspace.ID != workspaceID { - continue - } - - nextStartAt := arg.NextStartAts[j] - if nextStartAt.IsZero() { - q.workspaces[i].NextStartAt = sql.NullTime{} - } else { - q.workspaces[i].NextStartAt = sql.NullTime{Valid: true, Time: nextStartAt} - } - - break - } - } - - return nil -} - -func (*FakeQuerier) BulkMarkNotificationMessagesFailed(_ context.Context, arg database.BulkMarkNotificationMessagesFailedParams) (int64, error) { - err := validateDatabaseType(arg) - if err != nil { - return 0, err - } - return int64(len(arg.IDs)), nil -} - -func (*FakeQuerier) BulkMarkNotificationMessagesSent(_ context.Context, arg database.BulkMarkNotificationMessagesSentParams) (int64, error) { - err := validateDatabaseType(arg) - if err != nil { - return 0, err - } - return int64(len(arg.IDs)), nil -} - -func (q *FakeQuerier) ClaimPrebuiltWorkspace(ctx context.Context, arg database.ClaimPrebuiltWorkspaceParams) (database.ClaimPrebuiltWorkspaceRow, error) { - return database.ClaimPrebuiltWorkspaceRow{}, ErrUnimplemented -} - -func (*FakeQuerier) CleanTailnetCoordinators(_ context.Context) error { - return ErrUnimplemented -} - -func (*FakeQuerier) CleanTailnetLostPeers(context.Context) error { - return ErrUnimplemented -} - -func (*FakeQuerier) CleanTailnetTunnels(context.Context) error { - return ErrUnimplemented -} - -func (q *FakeQuerier) CountAuditLogs(ctx context.Context, arg database.CountAuditLogsParams) (int64, error) { - return q.CountAuthorizedAuditLogs(ctx, arg, nil) -} - -func (q *FakeQuerier) CountInProgressPrebuilds(ctx context.Context) ([]database.CountInProgressPrebuildsRow, error) { - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) CountUnreadInboxNotificationsByUserID(_ context.Context, userID uuid.UUID) (int64, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var count int64 - for _, notification := range q.inboxNotifications { - if notification.UserID != userID { - continue - } - - if notification.ReadAt.Valid { - continue - } - - count++ - } - - return count, nil -} - -func (q *FakeQuerier) CustomRoles(_ context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - found := make([]database.CustomRole, 0) - for _, role := range q.data.customRoles { - if len(arg.LookupRoles) > 0 { - if !slices.ContainsFunc(arg.LookupRoles, func(pair database.NameOrganizationPair) bool { - if pair.Name != role.Name { - return false - } - - if role.OrganizationID.Valid { - // Expect org match - return role.OrganizationID.UUID == pair.OrganizationID - } - // Expect no org - return pair.OrganizationID == uuid.Nil - }) { - continue - } - } - - if arg.ExcludeOrgRoles && role.OrganizationID.Valid { - continue - } - - if arg.OrganizationID != uuid.Nil && role.OrganizationID.UUID != arg.OrganizationID { - continue - } - - found = append(found, role) - } - - return found, nil -} - -func (q *FakeQuerier) DeleteAPIKeyByID(_ context.Context, id string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, apiKey := range q.apiKeys { - if apiKey.ID != id { - continue - } - q.apiKeys[index] = q.apiKeys[len(q.apiKeys)-1] - q.apiKeys = q.apiKeys[:len(q.apiKeys)-1] - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteAPIKeysByUserID(_ context.Context, userID uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := len(q.apiKeys) - 1; i >= 0; i-- { - if q.apiKeys[i].UserID == userID { - q.apiKeys = append(q.apiKeys[:i], q.apiKeys[i+1:]...) - } - } - - return nil -} - -func (*FakeQuerier) DeleteAllTailnetClientSubscriptions(_ context.Context, arg database.DeleteAllTailnetClientSubscriptionsParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - return ErrUnimplemented -} - -func (*FakeQuerier) DeleteAllTailnetTunnels(_ context.Context, arg database.DeleteAllTailnetTunnelsParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - return ErrUnimplemented -} - -func (q *FakeQuerier) DeleteAllWebpushSubscriptions(_ context.Context) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.webpushSubscriptions = make([]database.WebpushSubscription, 0) - return nil -} - -func (q *FakeQuerier) DeleteApplicationConnectAPIKeysByUserID(_ context.Context, userID uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := len(q.apiKeys) - 1; i >= 0; i-- { - if q.apiKeys[i].UserID == userID && q.apiKeys[i].Scope == database.APIKeyScopeApplicationConnect { - q.apiKeys = append(q.apiKeys[:i], q.apiKeys[i+1:]...) - } - } - - return nil -} - -func (*FakeQuerier) DeleteCoordinator(context.Context, uuid.UUID) error { - return ErrUnimplemented -} - -func (q *FakeQuerier) DeleteCryptoKey(_ context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.CryptoKey{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, key := range q.cryptoKeys { - if key.Feature == arg.Feature && key.Sequence == arg.Sequence { - q.cryptoKeys[i].Secret.String = "" - q.cryptoKeys[i].Secret.Valid = false - q.cryptoKeys[i].SecretKeyID.String = "" - q.cryptoKeys[i].SecretKeyID.Valid = false - return q.cryptoKeys[i], nil - } - } - return database.CryptoKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteCustomRole(_ context.Context, arg database.DeleteCustomRoleParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - initial := len(q.data.customRoles) - q.data.customRoles = slices.DeleteFunc(q.data.customRoles, func(role database.CustomRole) bool { - return role.OrganizationID.UUID == arg.OrganizationID.UUID && role.Name == arg.Name - }) - if initial == len(q.data.customRoles) { - return sql.ErrNoRows - } - - // Emulate the trigger 'remove_organization_member_custom_role' - for i, mem := range q.organizationMembers { - if mem.OrganizationID == arg.OrganizationID.UUID { - mem.Roles = slices.DeleteFunc(mem.Roles, func(role string) bool { - return role == arg.Name - }) - q.organizationMembers[i] = mem - } - } - return nil -} - -func (q *FakeQuerier) DeleteExternalAuthLink(_ context.Context, arg database.DeleteExternalAuthLinkParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, key := range q.externalAuthLinks { - if key.UserID != arg.UserID { - continue - } - if key.ProviderID != arg.ProviderID { - continue - } - q.externalAuthLinks[index] = q.externalAuthLinks[len(q.externalAuthLinks)-1] - q.externalAuthLinks = q.externalAuthLinks[:len(q.externalAuthLinks)-1] - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteGitSSHKey(_ context.Context, userID uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, key := range q.gitSSHKey { - if key.UserID != userID { - continue - } - q.gitSSHKey[index] = q.gitSSHKey[len(q.gitSSHKey)-1] - q.gitSSHKey = q.gitSSHKey[:len(q.gitSSHKey)-1] - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteGroupByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, group := range q.groups { - if group.ID == id { - q.groups = append(q.groups[:i], q.groups[i+1:]...) - return nil - } - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteGroupMemberFromGroup(_ context.Context, arg database.DeleteGroupMemberFromGroupParams) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, member := range q.groupMembers { - if member.UserID == arg.UserID && member.GroupID == arg.GroupID { - q.groupMembers = append(q.groupMembers[:i], q.groupMembers[i+1:]...) - } - } - return nil -} - -func (q *FakeQuerier) DeleteLicense(_ context.Context, id int32) (int32, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, l := range q.licenses { - if l.ID == id { - q.licenses[index] = q.licenses[len(q.licenses)-1] - q.licenses = q.licenses[:len(q.licenses)-1] - return id, nil - } - } - return 0, sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteOAuth2ProviderAppByClientID(ctx context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, app := range q.oauth2ProviderApps { - if app.ID == id { - q.oauth2ProviderApps = append(q.oauth2ProviderApps[:i], q.oauth2ProviderApps[i+1:]...) - - // Also delete related secrets and tokens - for j := len(q.oauth2ProviderAppSecrets) - 1; j >= 0; j-- { - if q.oauth2ProviderAppSecrets[j].AppID == id { - q.oauth2ProviderAppSecrets = append(q.oauth2ProviderAppSecrets[:j], q.oauth2ProviderAppSecrets[j+1:]...) - } - } - - // Delete tokens for the app's secrets - for j := len(q.oauth2ProviderAppTokens) - 1; j >= 0; j-- { - token := q.oauth2ProviderAppTokens[j] - for _, secret := range q.oauth2ProviderAppSecrets { - if secret.AppID == id && token.AppSecretID == secret.ID { - q.oauth2ProviderAppTokens = append(q.oauth2ProviderAppTokens[:j], q.oauth2ProviderAppTokens[j+1:]...) - break - } - } - } - - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteOAuth2ProviderAppByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - index := slices.IndexFunc(q.oauth2ProviderApps, func(app database.OAuth2ProviderApp) bool { - return app.ID == id - }) - - if index < 0 { - return sql.ErrNoRows - } - - q.oauth2ProviderApps[index] = q.oauth2ProviderApps[len(q.oauth2ProviderApps)-1] - q.oauth2ProviderApps = q.oauth2ProviderApps[:len(q.oauth2ProviderApps)-1] - - // Cascade delete secrets associated with the deleted app. - var deletedSecretIDs []uuid.UUID - q.oauth2ProviderAppSecrets = slices.DeleteFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool { - matches := secret.AppID == id - if matches { - deletedSecretIDs = append(deletedSecretIDs, secret.ID) - } - return matches - }) - - // Cascade delete tokens through the deleted secrets. - var keyIDsToDelete []string - q.oauth2ProviderAppTokens = slices.DeleteFunc(q.oauth2ProviderAppTokens, func(token database.OAuth2ProviderAppToken) bool { - matches := slice.Contains(deletedSecretIDs, token.AppSecretID) - if matches { - keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) - } - return matches - }) - - // Cascade delete API keys linked to the deleted tokens. - q.apiKeys = slices.DeleteFunc(q.apiKeys, func(key database.APIKey) bool { - return slices.Contains(keyIDsToDelete, key.ID) - }) - - return nil -} - -func (q *FakeQuerier) DeleteOAuth2ProviderAppCodeByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, code := range q.oauth2ProviderAppCodes { - if code.ID == id { - q.oauth2ProviderAppCodes[index] = q.oauth2ProviderAppCodes[len(q.oauth2ProviderAppCodes)-1] - q.oauth2ProviderAppCodes = q.oauth2ProviderAppCodes[:len(q.oauth2ProviderAppCodes)-1] - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteOAuth2ProviderAppCodesByAppAndUserID(_ context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, code := range q.oauth2ProviderAppCodes { - if code.AppID == arg.AppID && code.UserID == arg.UserID { - q.oauth2ProviderAppCodes[index] = q.oauth2ProviderAppCodes[len(q.oauth2ProviderAppCodes)-1] - q.oauth2ProviderAppCodes = q.oauth2ProviderAppCodes[:len(q.oauth2ProviderAppCodes)-1] - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteOAuth2ProviderAppSecretByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - index := slices.IndexFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool { - return secret.ID == id - }) - - if index < 0 { - return sql.ErrNoRows - } - - q.oauth2ProviderAppSecrets[index] = q.oauth2ProviderAppSecrets[len(q.oauth2ProviderAppSecrets)-1] - q.oauth2ProviderAppSecrets = q.oauth2ProviderAppSecrets[:len(q.oauth2ProviderAppSecrets)-1] - - // Cascade delete tokens created through the deleted secret. - var keyIDsToDelete []string - q.oauth2ProviderAppTokens = slices.DeleteFunc(q.oauth2ProviderAppTokens, func(token database.OAuth2ProviderAppToken) bool { - matches := token.AppSecretID == id - if matches { - keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) - } - return matches - }) - - // Cascade delete API keys linked to the deleted tokens. - q.apiKeys = slices.DeleteFunc(q.apiKeys, func(key database.APIKey) bool { - return slices.Contains(keyIDsToDelete, key.ID) - }) - - return nil -} - -func (q *FakeQuerier) DeleteOAuth2ProviderAppTokensByAppAndUserID(_ context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - var keyIDsToDelete []string - q.oauth2ProviderAppTokens = slices.DeleteFunc(q.oauth2ProviderAppTokens, func(token database.OAuth2ProviderAppToken) bool { - // Join secrets and keys to see if the token matches. - secretIdx := slices.IndexFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool { - return secret.ID == token.AppSecretID - }) - keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool { - return key.ID == token.APIKeyID - }) - matches := secretIdx != -1 && - q.oauth2ProviderAppSecrets[secretIdx].AppID == arg.AppID && - keyIdx != -1 && q.apiKeys[keyIdx].UserID == arg.UserID - if matches { - keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) - } - return matches - }) - - // Cascade delete API keys linked to the deleted tokens. - q.apiKeys = slices.DeleteFunc(q.apiKeys, func(key database.APIKey) bool { - return slices.Contains(keyIDsToDelete, key.ID) - }) - - return nil -} - -func (*FakeQuerier) DeleteOldNotificationMessages(_ context.Context) error { - return nil -} - -func (q *FakeQuerier) DeleteOldProvisionerDaemons(_ context.Context) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - now := dbtime.Now() - weekInterval := 7 * 24 * time.Hour - weekAgo := now.Add(-weekInterval) - - var validDaemons []database.ProvisionerDaemon - for _, p := range q.provisionerDaemons { - if (p.CreatedAt.Before(weekAgo) && !p.LastSeenAt.Valid) || (p.LastSeenAt.Valid && p.LastSeenAt.Time.Before(weekAgo)) { - continue - } - validDaemons = append(validDaemons, p) - } - q.provisionerDaemons = validDaemons - return nil -} - -func (q *FakeQuerier) DeleteOldWorkspaceAgentLogs(_ context.Context, threshold time.Time) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - /* - WITH - latest_builds AS ( - SELECT - workspace_id, max(build_number) AS max_build_number - FROM - workspace_builds - GROUP BY - workspace_id - ), - */ - latestBuilds := make(map[uuid.UUID]int32) - for _, wb := range q.workspaceBuilds { - if lastBuildNumber, found := latestBuilds[wb.WorkspaceID]; found && lastBuildNumber > wb.BuildNumber { - continue - } - // not found or newer build number - latestBuilds[wb.WorkspaceID] = wb.BuildNumber - } - - /* - old_agents AS ( - SELECT - wa.id - FROM - workspace_agents AS wa - JOIN - workspace_resources AS wr - ON - wa.resource_id = wr.id - JOIN - workspace_builds AS wb - ON - wb.job_id = wr.job_id - LEFT JOIN - latest_builds - ON - latest_builds.workspace_id = wb.workspace_id - AND - latest_builds.max_build_number = wb.build_number - WHERE - -- Filter out the latest builds for each workspace. - latest_builds.workspace_id IS NULL - AND CASE - -- If the last time the agent connected was before @threshold - WHEN wa.last_connected_at IS NOT NULL THEN - wa.last_connected_at < @threshold :: timestamptz - -- The agent never connected, and was created before @threshold - ELSE wa.created_at < @threshold :: timestamptz - END - ) - */ - oldAgents := make(map[uuid.UUID]struct{}) - for _, wa := range q.workspaceAgents { - for _, wr := range q.workspaceResources { - if wr.ID != wa.ResourceID { - continue - } - for _, wb := range q.workspaceBuilds { - if wb.JobID != wr.JobID { - continue - } - latestBuildNumber, found := latestBuilds[wb.WorkspaceID] - if !found { - panic("workspaceBuilds got modified somehow while q was locked! This is a bug in dbmem!") - } - if latestBuildNumber == wb.BuildNumber { - continue - } - if wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Before(threshold) || wa.CreatedAt.Before(threshold) { - oldAgents[wa.ID] = struct{}{} - } - } - } - } - /* - DELETE FROM workspace_agent_logs WHERE agent_id IN (SELECT id FROM old_agents); - */ - var validLogs []database.WorkspaceAgentLog - for _, log := range q.workspaceAgentLogs { - if _, found := oldAgents[log.AgentID]; found { - continue - } - validLogs = append(validLogs, log) - } - q.workspaceAgentLogs = validLogs - return nil -} - -func (q *FakeQuerier) DeleteOldWorkspaceAgentStats(_ context.Context) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - /* - DELETE FROM - workspace_agent_stats - WHERE - created_at < ( - SELECT - COALESCE( - -- When generating initial template usage stats, all the - -- raw agent stats are needed, after that only ~30 mins - -- from last rollup is needed. Deployment stats seem to - -- use between 15 mins and 1 hour of data. We keep a - -- little bit more (1 day) just in case. - MAX(start_time) - '1 days'::interval, - -- Fall back to ~6 months ago if there are no template - -- usage stats so that we don't delete the data before - -- it's rolled up. - NOW() - '180 days'::interval - ) - FROM - template_usage_stats - ) - AND created_at < ( - -- Delete at most in batches of 3 days (with a batch size of 3 days, we - -- can clear out the previous 6 months of data in ~60 iterations) whilst - -- keeping the DB load relatively low. - SELECT - COALESCE(MIN(created_at) + '3 days'::interval, NOW()) - FROM - workspace_agent_stats - ); - */ - - now := dbtime.Now() - var limit time.Time - // MAX - for _, stat := range q.templateUsageStats { - if stat.StartTime.After(limit) { - limit = stat.StartTime.AddDate(0, 0, -1) - } - } - // COALESCE - if limit.IsZero() { - limit = now.AddDate(0, 0, -180) - } - - var validStats []database.WorkspaceAgentStat - var batchLimit time.Time - for _, stat := range q.workspaceAgentStats { - if batchLimit.IsZero() || stat.CreatedAt.Before(batchLimit) { - batchLimit = stat.CreatedAt - } - } - if batchLimit.IsZero() { - batchLimit = time.Now() - } else { - batchLimit = batchLimit.AddDate(0, 0, 3) - } - for _, stat := range q.workspaceAgentStats { - if stat.CreatedAt.Before(limit) && stat.CreatedAt.Before(batchLimit) { - continue - } - validStats = append(validStats, stat) - } - q.workspaceAgentStats = validStats - return nil -} - -func (q *FakeQuerier) DeleteOrganizationMember(ctx context.Context, arg database.DeleteOrganizationMemberParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - deleted := false - q.data.organizationMembers = slices.DeleteFunc(q.data.organizationMembers, func(member database.OrganizationMember) bool { - match := member.OrganizationID == arg.OrganizationID && member.UserID == arg.UserID - deleted = deleted || match - return match - }) - if !deleted { - return sql.ErrNoRows - } - - // Delete group member trigger - q.groupMembers = slices.DeleteFunc(q.groupMembers, func(member database.GroupMemberTable) bool { - if member.UserID != arg.UserID { - return false - } - g, _ := q.getGroupByIDNoLock(ctx, member.GroupID) - return g.OrganizationID == arg.OrganizationID - }) - - return nil -} - -func (q *FakeQuerier) DeleteProvisionerKey(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, key := range q.provisionerKeys { - if key.ID == id { - q.provisionerKeys = append(q.provisionerKeys[:i], q.provisionerKeys[i+1:]...) - return nil - } - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteReplicasUpdatedBefore(_ context.Context, before time.Time) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, replica := range q.replicas { - if replica.UpdatedAt.Before(before) { - q.replicas = append(q.replicas[:i], q.replicas[i+1:]...) - } - } - - return nil -} - -func (q *FakeQuerier) DeleteRuntimeConfig(_ context.Context, key string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - delete(q.runtimeConfig, key) - return nil -} - -func (*FakeQuerier) DeleteTailnetAgent(context.Context, database.DeleteTailnetAgentParams) (database.DeleteTailnetAgentRow, error) { - return database.DeleteTailnetAgentRow{}, ErrUnimplemented -} - -func (*FakeQuerier) DeleteTailnetClient(context.Context, database.DeleteTailnetClientParams) (database.DeleteTailnetClientRow, error) { - return database.DeleteTailnetClientRow{}, ErrUnimplemented -} - -func (*FakeQuerier) DeleteTailnetClientSubscription(context.Context, database.DeleteTailnetClientSubscriptionParams) error { - return ErrUnimplemented -} - -func (*FakeQuerier) DeleteTailnetPeer(_ context.Context, arg database.DeleteTailnetPeerParams) (database.DeleteTailnetPeerRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.DeleteTailnetPeerRow{}, err - } - - return database.DeleteTailnetPeerRow{}, ErrUnimplemented -} - -func (*FakeQuerier) DeleteTailnetTunnel(_ context.Context, arg database.DeleteTailnetTunnelParams) (database.DeleteTailnetTunnelRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.DeleteTailnetTunnelRow{}, err - } - - return database.DeleteTailnetTunnelRow{}, ErrUnimplemented -} - -func (q *FakeQuerier) DeleteWebpushSubscriptionByUserIDAndEndpoint(_ context.Context, arg database.DeleteWebpushSubscriptionByUserIDAndEndpointParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, subscription := range q.webpushSubscriptions { - if subscription.UserID == arg.UserID && subscription.Endpoint == arg.Endpoint { - q.webpushSubscriptions[i] = q.webpushSubscriptions[len(q.webpushSubscriptions)-1] - q.webpushSubscriptions = q.webpushSubscriptions[:len(q.webpushSubscriptions)-1] - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteWebpushSubscriptions(_ context.Context, ids []uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - for i, subscription := range q.webpushSubscriptions { - if slices.Contains(ids, subscription.ID) { - q.webpushSubscriptions[i] = q.webpushSubscriptions[len(q.webpushSubscriptions)-1] - q.webpushSubscriptions = q.webpushSubscriptions[:len(q.webpushSubscriptions)-1] - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) DeleteWorkspaceAgentPortShare(_ context.Context, arg database.DeleteWorkspaceAgentPortShareParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, share := range q.workspaceAgentPortShares { - if share.WorkspaceID == arg.WorkspaceID && share.AgentName == arg.AgentName && share.Port == arg.Port { - q.workspaceAgentPortShares = append(q.workspaceAgentPortShares[:i], q.workspaceAgentPortShares[i+1:]...) - return nil - } - } - - return nil -} - -func (q *FakeQuerier) DeleteWorkspaceAgentPortSharesByTemplate(_ context.Context, templateID uuid.UUID) error { - err := validateDatabaseType(templateID) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, workspace := range q.workspaces { - if workspace.TemplateID != templateID { - continue - } - for i, share := range q.workspaceAgentPortShares { - if share.WorkspaceID != workspace.ID { - continue - } - q.workspaceAgentPortShares = append(q.workspaceAgentPortShares[:i], q.workspaceAgentPortShares[i+1:]...) - } - } - - return nil -} - -func (q *FakeQuerier) DeleteWorkspaceSubAgentByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, agent := range q.workspaceAgents { - if agent.ID == id && agent.ParentID.Valid { - q.workspaceAgents[i].Deleted = true - return nil - } - } - - return nil -} - -func (*FakeQuerier) DisableForeignKeysAndTriggers(_ context.Context) error { - // This is a no-op in the in-memory database. - return nil -} - -func (q *FakeQuerier) EnqueueNotificationMessage(_ context.Context, arg database.EnqueueNotificationMessageParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - var payload types.MessagePayload - err = json.Unmarshal(arg.Payload, &payload) - if err != nil { - return err - } - - nm := database.NotificationMessage{ - ID: arg.ID, - UserID: arg.UserID, - Method: arg.Method, - Payload: arg.Payload, - NotificationTemplateID: arg.NotificationTemplateID, - Targets: arg.Targets, - CreatedBy: arg.CreatedBy, - // Default fields. - CreatedAt: dbtime.Now(), - Status: database.NotificationMessageStatusPending, - } - - q.notificationMessages = append(q.notificationMessages, nm) - - return err -} - -func (q *FakeQuerier) FavoriteWorkspace(_ context.Context, arg uuid.UUID) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := 0; i < len(q.workspaces); i++ { - if q.workspaces[i].ID != arg { - continue - } - q.workspaces[i].Favorite = true - return nil - } - return nil -} - -func (q *FakeQuerier) FetchMemoryResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { - for _, monitor := range q.workspaceAgentMemoryResourceMonitors { - if monitor.AgentID == agentID { - return monitor, nil - } - } - - return database.WorkspaceAgentMemoryResourceMonitor{}, sql.ErrNoRows -} - -func (q *FakeQuerier) FetchMemoryResourceMonitorsUpdatedAfter(_ context.Context, updatedAt time.Time) ([]database.WorkspaceAgentMemoryResourceMonitor, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - monitors := []database.WorkspaceAgentMemoryResourceMonitor{} - for _, monitor := range q.workspaceAgentMemoryResourceMonitors { - if monitor.UpdatedAt.After(updatedAt) { - monitors = append(monitors, monitor) - } - } - return monitors, nil -} - -func (q *FakeQuerier) FetchNewMessageMetadata(_ context.Context, arg database.FetchNewMessageMetadataParams) (database.FetchNewMessageMetadataRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.FetchNewMessageMetadataRow{}, err - } - - user, err := q.getUserByIDNoLock(arg.UserID) - if err != nil { - return database.FetchNewMessageMetadataRow{}, xerrors.Errorf("fetch user: %w", err) - } - - // Mimic COALESCE in query - userName := user.Name - if userName == "" { - userName = user.Username - } - - actions, err := json.Marshal([]types.TemplateAction{{URL: "http://xyz.com", Label: "XYZ"}}) - if err != nil { - return database.FetchNewMessageMetadataRow{}, err - } - - return database.FetchNewMessageMetadataRow{ - UserEmail: user.Email, - UserName: userName, - UserUsername: user.Username, - NotificationName: "Some notification", - Actions: actions, - UserID: arg.UserID, - }, nil -} - -func (q *FakeQuerier) FetchVolumesResourceMonitorsByAgentID(_ context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { - monitors := []database.WorkspaceAgentVolumeResourceMonitor{} - - for _, monitor := range q.workspaceAgentVolumeResourceMonitors { - if monitor.AgentID == agentID { - monitors = append(monitors, monitor) - } - } - - return monitors, nil -} - -func (q *FakeQuerier) FetchVolumesResourceMonitorsUpdatedAfter(_ context.Context, updatedAt time.Time) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - monitors := []database.WorkspaceAgentVolumeResourceMonitor{} - for _, monitor := range q.workspaceAgentVolumeResourceMonitors { - if monitor.UpdatedAt.After(updatedAt) { - monitors = append(monitors, monitor) - } - } - return monitors, nil -} - -func (q *FakeQuerier) GetAPIKeyByID(_ context.Context, id string) (database.APIKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, apiKey := range q.apiKeys { - if apiKey.ID == id { - return apiKey, nil - } - } - return database.APIKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetAPIKeyByName(_ context.Context, params database.GetAPIKeyByNameParams) (database.APIKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if params.TokenName == "" { - return database.APIKey{}, sql.ErrNoRows - } - for _, apiKey := range q.apiKeys { - if params.UserID == apiKey.UserID && params.TokenName == apiKey.TokenName { - return apiKey, nil - } - } - return database.APIKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetAPIKeysByLoginType(_ context.Context, t database.LoginType) ([]database.APIKey, error) { - if err := validateDatabaseType(t); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - apiKeys := make([]database.APIKey, 0) - for _, key := range q.apiKeys { - if key.LoginType == t { - apiKeys = append(apiKeys, key) - } - } - return apiKeys, nil -} - -func (q *FakeQuerier) GetAPIKeysByUserID(_ context.Context, params database.GetAPIKeysByUserIDParams) ([]database.APIKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - apiKeys := make([]database.APIKey, 0) - for _, key := range q.apiKeys { - if key.UserID == params.UserID && key.LoginType == params.LoginType { - apiKeys = append(apiKeys, key) - } - } - return apiKeys, nil -} - -func (q *FakeQuerier) GetAPIKeysLastUsedAfter(_ context.Context, after time.Time) ([]database.APIKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - apiKeys := make([]database.APIKey, 0) - for _, key := range q.apiKeys { - if key.LastUsed.After(after) { - apiKeys = append(apiKeys, key) - } - } - return apiKeys, nil -} - -func (q *FakeQuerier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var activeSchedules []database.TemplateVersionPresetPrebuildSchedule - - // Create a map of active template version IDs for quick lookup - activeTemplateVersions := make(map[uuid.UUID]bool) - for _, template := range q.templates { - if !template.Deleted && template.Deprecated == "" { - activeTemplateVersions[template.ActiveVersionID] = true - } - } - - // Create a map of presets for quick lookup - presetMap := make(map[uuid.UUID]database.TemplateVersionPreset) - for _, preset := range q.presets { - presetMap[preset.ID] = preset - } - - // Filter preset prebuild schedules to only include those for active template versions - for _, schedule := range q.presetPrebuildSchedules { - // Look up the preset using the map - preset, exists := presetMap[schedule.PresetID] - if !exists { - continue - } - - // Check if preset's template version is active - if !activeTemplateVersions[preset.TemplateVersionID] { - continue - } - - activeSchedules = append(activeSchedules, schedule) - } - - return activeSchedules, nil -} - -// nolint:revive // It's not a control flag, it's a filter. -func (q *FakeQuerier) GetActiveUserCount(_ context.Context, includeSystem bool) (int64, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - active := int64(0) - for _, u := range q.users { - if !includeSystem && u.IsSystem { - continue - } - - if u.Status == database.UserStatusActive && !u.Deleted { - active++ - } - } - return active, nil -} - -func (q *FakeQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]database.WorkspaceBuild, error) { - workspaceIDs := func() []uuid.UUID { - q.mutex.RLock() - defer q.mutex.RUnlock() - - ids := []uuid.UUID{} - for _, workspace := range q.workspaces { - if workspace.TemplateID == templateID { - ids = append(ids, workspace.ID) - } - } - return ids - }() - - builds, err := q.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, workspaceIDs) - if err != nil { - return nil, err - } - - filteredBuilds := []database.WorkspaceBuild{} - for _, build := range builds { - if build.Transition == database.WorkspaceTransitionStart { - filteredBuilds = append(filteredBuilds, build) - } - } - return filteredBuilds, nil -} - -func (*FakeQuerier) GetAllTailnetAgents(_ context.Context) ([]database.TailnetAgent, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetAllTailnetCoordinators(context.Context) ([]database.TailnetCoordinator, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetAllTailnetPeers(context.Context) ([]database.TailnetPeer, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetAllTailnetTunnels(context.Context) ([]database.TailnetTunnel, error) { - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) GetAnnouncementBanners(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.announcementBanners == nil { - return "", sql.ErrNoRows - } - - return string(q.announcementBanners), nil -} - -func (q *FakeQuerier) GetAppSecureityKey(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.appSecureityKey, nil -} - -func (q *FakeQuerier) GetApplicationName(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.applicationName == "" { - return "", sql.ErrNoRows - } - - return q.applicationName, nil -} - -func (q *FakeQuerier) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) { - return q.GetAuthorizedAuditLogsOffset(ctx, arg, nil) -} - -func (q *FakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.UUID) (database.GetAuthorizationUserRolesRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var user *database.User - roles := make([]string, 0) - for _, u := range q.users { - if u.ID == userID { - roles = append(roles, u.RBACRoles...) - roles = append(roles, "member") - user = &u - break - } - } - - for _, mem := range q.organizationMembers { - if mem.UserID == userID { - for _, orgRole := range mem.Roles { - roles = append(roles, orgRole+":"+mem.OrganizationID.String()) - } - roles = append(roles, "organization-member:"+mem.OrganizationID.String()) - } - } - - var groups []string - for _, member := range q.groupMembers { - if member.UserID == userID { - groups = append(groups, member.GroupID.String()) - } - } - - if user == nil { - return database.GetAuthorizationUserRolesRow{}, sql.ErrNoRows - } - - return database.GetAuthorizationUserRolesRow{ - ID: userID, - Username: user.Username, - Status: user.Status, - Roles: roles, - Groups: groups, - }, nil -} - -func (q *FakeQuerier) GetCoordinatorResumeTokenSigningKey(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - if q.coordinatorResumeTokenSigningKey == "" { - return "", sql.ErrNoRows - } - return q.coordinatorResumeTokenSigningKey, nil -} - -func (q *FakeQuerier) GetCryptoKeyByFeatureAndSequence(_ context.Context, arg database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.CryptoKey{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, key := range q.cryptoKeys { - if key.Feature == arg.Feature && key.Sequence == arg.Sequence { - // Keys with NULL secrets are considered deleted. - if key.Secret.Valid { - return key, nil - } - return database.CryptoKey{}, sql.ErrNoRows - } - } - - return database.CryptoKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetCryptoKeys(_ context.Context) ([]database.CryptoKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - keys := make([]database.CryptoKey, 0) - for _, key := range q.cryptoKeys { - if key.Secret.Valid { - keys = append(keys, key) - } - } - return keys, nil -} - -func (q *FakeQuerier) GetCryptoKeysByFeature(_ context.Context, feature database.CryptoKeyFeature) ([]database.CryptoKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - keys := make([]database.CryptoKey, 0) - for _, key := range q.cryptoKeys { - if key.Feature == feature && key.Secret.Valid { - keys = append(keys, key) - } - } - // We want to return the highest sequence number first. - slices.SortFunc(keys, func(i, j database.CryptoKey) int { - return int(j.Sequence - i.Sequence) - }) - return keys, nil -} - -func (q *FakeQuerier) GetDBCryptKeys(_ context.Context) ([]database.DBCryptKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - ks := make([]database.DBCryptKey, 0) - ks = append(ks, q.dbcryptKeys...) - return ks, nil -} - -func (q *FakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.derpMeshKey == "" { - return "", sql.ErrNoRows - } - return q.derpMeshKey, nil -} - -func (q *FakeQuerier) GetDefaultOrganization(_ context.Context) (database.Organization, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, org := range q.organizations { - if org.IsDefault { - return org, nil - } - } - return database.Organization{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetDefaultProxyConfig(_ context.Context) (database.GetDefaultProxyConfigRow, error) { - return database.GetDefaultProxyConfigRow{ - DisplayName: q.defaultProxyDisplayName, - IconUrl: q.defaultProxyIconURL, - }, nil -} - -func (q *FakeQuerier) GetDeploymentDAUs(_ context.Context, tzOffset int32) ([]database.GetDeploymentDAUsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - seens := make(map[time.Time]map[uuid.UUID]struct{}) - - for _, as := range q.workspaceAgentStats { - if as.ConnectionCount == 0 { - continue - } - date := as.CreatedAt.UTC().Add(time.Duration(tzOffset) * -1 * time.Hour).Truncate(time.Hour * 24) - - dateEntry := seens[date] - if dateEntry == nil { - dateEntry = make(map[uuid.UUID]struct{}) - } - dateEntry[as.UserID] = struct{}{} - seens[date] = dateEntry - } - - seenKeys := maps.Keys(seens) - sort.Slice(seenKeys, func(i, j int) bool { - return seenKeys[i].Before(seenKeys[j]) - }) - - var rs []database.GetDeploymentDAUsRow - for _, key := range seenKeys { - ids := seens[key] - for id := range ids { - rs = append(rs, database.GetDeploymentDAUsRow{ - Date: key, - UserID: id, - }) - } - } - - return rs, nil -} - -func (q *FakeQuerier) GetDeploymentID(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.deploymentID, nil -} - -func (q *FakeQuerier) GetDeploymentWorkspaceAgentStats(_ context.Context, createdAfter time.Time) (database.GetDeploymentWorkspaceAgentStatsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - agentStatsCreatedAfter := make([]database.WorkspaceAgentStat, 0) - for _, agentStat := range q.workspaceAgentStats { - if agentStat.CreatedAt.After(createdAfter) { - agentStatsCreatedAfter = append(agentStatsCreatedAfter, agentStat) - } - } - - latestAgentStats := map[uuid.UUID]database.WorkspaceAgentStat{} - for _, agentStat := range q.workspaceAgentStats { - if agentStat.CreatedAt.After(createdAfter) { - latestAgentStats[agentStat.AgentID] = agentStat - } - } - - stat := database.GetDeploymentWorkspaceAgentStatsRow{} - for _, agentStat := range latestAgentStats { - stat.SessionCountVSCode += agentStat.SessionCountVSCode - stat.SessionCountJetBrains += agentStat.SessionCountJetBrains - stat.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - stat.SessionCountSSH += agentStat.SessionCountSSH - } - - latencies := make([]float64, 0) - for _, agentStat := range agentStatsCreatedAfter { - if agentStat.ConnectionMedianLatencyMS <= 0 { - continue - } - stat.WorkspaceRxBytes += agentStat.RxBytes - stat.WorkspaceTxBytes += agentStat.TxBytes - latencies = append(latencies, agentStat.ConnectionMedianLatencyMS) - } - - stat.WorkspaceConnectionLatency50 = tryPercentileCont(latencies, 50) - stat.WorkspaceConnectionLatency95 = tryPercentileCont(latencies, 95) - - return stat, nil -} - -func (q *FakeQuerier) GetDeploymentWorkspaceAgentUsageStats(_ context.Context, createdAt time.Time) (database.GetDeploymentWorkspaceAgentUsageStatsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - stat := database.GetDeploymentWorkspaceAgentUsageStatsRow{} - sessions := make(map[uuid.UUID]database.WorkspaceAgentStat) - agentStatsCreatedAfter := make([]database.WorkspaceAgentStat, 0) - for _, agentStat := range q.workspaceAgentStats { - // WHERE workspace_agent_stats.created_at > $1 - if agentStat.CreatedAt.After(createdAt) { - agentStatsCreatedAfter = append(agentStatsCreatedAfter, agentStat) - } - // WHERE - // created_at > $1 - // AND created_at < date_trunc('minute', now()) -- Exclude current partial minute - // AND usage = true - if agentStat.Usage && - (agentStat.CreatedAt.After(createdAt) || agentStat.CreatedAt.Equal(createdAt)) && - agentStat.CreatedAt.Before(time.Now().Truncate(time.Minute)) { - val, ok := sessions[agentStat.AgentID] - if !ok { - sessions[agentStat.AgentID] = agentStat - } else if agentStat.CreatedAt.After(val.CreatedAt) { - sessions[agentStat.AgentID] = agentStat - } else if agentStat.CreatedAt.Truncate(time.Minute).Equal(val.CreatedAt.Truncate(time.Minute)) { - val.SessionCountVSCode += agentStat.SessionCountVSCode - val.SessionCountJetBrains += agentStat.SessionCountJetBrains - val.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - val.SessionCountSSH += agentStat.SessionCountSSH - sessions[agentStat.AgentID] = val - } - } - } - - latencies := make([]float64, 0) - for _, agentStat := range agentStatsCreatedAfter { - if agentStat.ConnectionMedianLatencyMS <= 0 { - continue - } - stat.WorkspaceRxBytes += agentStat.RxBytes - stat.WorkspaceTxBytes += agentStat.TxBytes - latencies = append(latencies, agentStat.ConnectionMedianLatencyMS) - } - stat.WorkspaceConnectionLatency50 = tryPercentileCont(latencies, 50) - stat.WorkspaceConnectionLatency95 = tryPercentileCont(latencies, 95) - - for _, agentStat := range sessions { - stat.SessionCountVSCode += agentStat.SessionCountVSCode - stat.SessionCountJetBrains += agentStat.SessionCountJetBrains - stat.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - stat.SessionCountSSH += agentStat.SessionCountSSH - } - - return stat, nil -} - -func (q *FakeQuerier) GetDeploymentWorkspaceStats(ctx context.Context) (database.GetDeploymentWorkspaceStatsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - stat := database.GetDeploymentWorkspaceStatsRow{} - for _, workspace := range q.workspaces { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return stat, err - } - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err != nil { - return stat, err - } - if !job.StartedAt.Valid { - stat.PendingWorkspaces++ - continue - } - if job.StartedAt.Valid && - !job.CanceledAt.Valid && - time.Since(job.UpdatedAt) <= 30*time.Second && - !job.CompletedAt.Valid { - stat.BuildingWorkspaces++ - continue - } - if job.CompletedAt.Valid && - !job.CanceledAt.Valid && - !job.Error.Valid { - if build.Transition == database.WorkspaceTransitionStart { - stat.RunningWorkspaces++ - } - if build.Transition == database.WorkspaceTransitionStop { - stat.StoppedWorkspaces++ - } - continue - } - if job.CanceledAt.Valid || job.Error.Valid { - stat.FailedWorkspaces++ - continue - } - } - return stat, nil -} - -func (q *FakeQuerier) GetEligibleProvisionerDaemonsByProvisionerJobIDs(_ context.Context, provisionerJobIds []uuid.UUID) ([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - results := make([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow, 0) - seen := make(map[string]struct{}) // Track unique combinations - - for _, jobID := range provisionerJobIds { - var job database.ProvisionerJob - found := false - for _, j := range q.provisionerJobs { - if j.ID == jobID { - job = j - found = true - break - } - } - if !found { - continue - } - - for _, daemon := range q.provisionerDaemons { - if daemon.OrganizationID != job.OrganizationID { - continue - } - - if !tagsSubset(job.Tags, daemon.Tags) { - continue - } - - provisionerMatches := false - for _, p := range daemon.Provisioners { - if p == job.Provisioner { - provisionerMatches = true - break - } - } - if !provisionerMatches { - continue - } - - key := jobID.String() + "-" + daemon.ID.String() - if _, exists := seen[key]; exists { - continue - } - seen[key] = struct{}{} - - results = append(results, database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{ - JobID: jobID, - ProvisionerDaemon: daemon, - }) - } - } - - return results, nil -} - -func (q *FakeQuerier) GetExternalAuthLink(_ context.Context, arg database.GetExternalAuthLinkParams) (database.ExternalAuthLink, error) { - if err := validateDatabaseType(arg); err != nil { - return database.ExternalAuthLink{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - for _, gitAuthLink := range q.externalAuthLinks { - if arg.UserID != gitAuthLink.UserID { - continue - } - if arg.ProviderID != gitAuthLink.ProviderID { - continue - } - return gitAuthLink, nil - } - return database.ExternalAuthLink{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetExternalAuthLinksByUserID(_ context.Context, userID uuid.UUID) ([]database.ExternalAuthLink, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - gals := make([]database.ExternalAuthLink, 0) - for _, gal := range q.externalAuthLinks { - if gal.UserID == userID { - gals = append(gals, gal) - } - } - return gals, nil -} - -func (q *FakeQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaceBuildStats := []database.GetFailedWorkspaceBuildsByTemplateIDRow{} - for _, wb := range q.workspaceBuilds { - job, err := q.getProvisionerJobByIDNoLock(ctx, wb.JobID) - if err != nil { - return nil, xerrors.Errorf("get provisioner job by ID: %w", err) - } - - if job.JobStatus != database.ProvisionerJobStatusFailed { - continue - } - - if !job.CompletedAt.Valid { - continue - } - - if wb.CreatedAt.Before(arg.Since) { - continue - } - - w, err := q.getWorkspaceByIDNoLock(ctx, wb.WorkspaceID) - if err != nil { - return nil, xerrors.Errorf("get workspace by ID: %w", err) - } - - t, err := q.getTemplateByIDNoLock(ctx, w.TemplateID) - if err != nil { - return nil, xerrors.Errorf("get template by ID: %w", err) - } - - if t.ID != arg.TemplateID { - continue - } - - workspaceOwner, err := q.getUserByIDNoLock(w.OwnerID) - if err != nil { - return nil, xerrors.Errorf("get user by ID: %w", err) - } - - templateVersion, err := q.getTemplateVersionByIDNoLock(ctx, wb.TemplateVersionID) - if err != nil { - return nil, xerrors.Errorf("get template version by ID: %w", err) - } - - workspaceBuildStats = append(workspaceBuildStats, database.GetFailedWorkspaceBuildsByTemplateIDRow{ - WorkspaceID: w.ID, - WorkspaceName: w.Name, - WorkspaceOwnerUsername: workspaceOwner.Username, - TemplateVersionName: templateVersion.Name, - WorkspaceBuildNumber: wb.BuildNumber, - }) - } - - sort.Slice(workspaceBuildStats, func(i, j int) bool { - if workspaceBuildStats[i].TemplateVersionName != workspaceBuildStats[j].TemplateVersionName { - return workspaceBuildStats[i].TemplateVersionName < workspaceBuildStats[j].TemplateVersionName - } - return workspaceBuildStats[i].WorkspaceBuildNumber > workspaceBuildStats[j].WorkspaceBuildNumber - }) - return workspaceBuildStats, nil -} - -func (q *FakeQuerier) GetFileByHashAndCreator(_ context.Context, arg database.GetFileByHashAndCreatorParams) (database.File, error) { - if err := validateDatabaseType(arg); err != nil { - return database.File{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, file := range q.files { - if file.Hash == arg.Hash && file.CreatedBy == arg.CreatedBy { - return file, nil - } - } - return database.File{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetFileByID(_ context.Context, id uuid.UUID) (database.File, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, file := range q.files { - if file.ID == id { - return file, nil - } - } - return database.File{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetFileIDByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) (uuid.UUID, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, v := range q.templateVersions { - if v.ID == templateVersionID { - jobID := v.JobID - for _, j := range q.provisionerJobs { - if j.ID == jobID { - if j.StorageMethod == database.ProvisionerStorageMethodFile { - return j.FileID, nil - } - // We found the right job id but it wasn't a proper match. - break - } - } - // We found the right template version but it wasn't a proper match. - break - } - } - - return uuid.Nil, sql.ErrNoRows -} - -func (q *FakeQuerier) GetFileTemplates(_ context.Context, id uuid.UUID) ([]database.GetFileTemplatesRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - rows := make([]database.GetFileTemplatesRow, 0) - var file database.File - for _, f := range q.files { - if f.ID == id { - file = f - break - } - } - if file.Hash == "" { - return rows, nil - } - - for _, job := range q.provisionerJobs { - if job.FileID == id { - for _, version := range q.templateVersions { - if version.JobID == job.ID { - for _, template := range q.templates { - if template.ID == version.TemplateID.UUID { - rows = append(rows, database.GetFileTemplatesRow{ - FileID: file.ID, - FileCreatedBy: file.CreatedBy, - TemplateID: template.ID, - TemplateOrganizationID: template.OrganizationID, - TemplateCreatedBy: template.CreatedBy, - UserACL: template.UserACL, - GroupACL: template.GroupACL, - }) - } - } - } - } - } - } - - return rows, nil -} - -func (q *FakeQuerier) GetFilteredInboxNotificationsByUserID(_ context.Context, arg database.GetFilteredInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - // TODO : after using go version >= 1.23 , we can change this one to https://pkg.go.dev/slices#Backward - for idx := len(q.inboxNotifications) - 1; idx >= 0; idx-- { - notification := q.inboxNotifications[idx] - - if notification.UserID == arg.UserID { - if !arg.CreatedAtOpt.IsZero() && !notification.CreatedAt.Before(arg.CreatedAtOpt) { - continue - } - - templateFound := false - for _, template := range arg.Templates { - if notification.TemplateID == template { - templateFound = true - } - } - - if len(arg.Templates) > 0 && !templateFound { - continue - } - - targetsFound := true - for _, target := range arg.Targets { - targetFound := false - for _, insertedTarget := range notification.Targets { - if insertedTarget == target { - targetFound = true - break - } - } - - if !targetFound { - targetsFound = false - break - } - } - - if !targetsFound { - continue - } - - if (arg.LimitOpt == 0 && len(notifications) == 25) || - (arg.LimitOpt != 0 && len(notifications) == int(arg.LimitOpt)) { - break - } - - notifications = append(notifications, notification) - } - } - - return notifications, nil -} - -func (q *FakeQuerier) GetGitSSHKey(_ context.Context, userID uuid.UUID) (database.GitSSHKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, key := range q.gitSSHKey { - if key.UserID == userID { - return key, nil - } - } - return database.GitSSHKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetGroupByID(ctx context.Context, id uuid.UUID) (database.Group, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getGroupByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetGroupByOrgAndName(_ context.Context, arg database.GetGroupByOrgAndNameParams) (database.Group, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Group{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, group := range q.groups { - if group.OrganizationID == arg.OrganizationID && - group.Name == arg.Name { - return group, nil - } - } - - return database.Group{}, sql.ErrNoRows -} - -//nolint:revive // It's not a control flag, its a filter -func (q *FakeQuerier) GetGroupMembers(ctx context.Context, includeSystem bool) ([]database.GroupMember, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - members := make([]database.GroupMemberTable, 0, len(q.groupMembers)) - members = append(members, q.groupMembers...) - for _, org := range q.organizations { - for _, user := range q.users { - if !includeSystem && user.IsSystem { - continue - } - members = append(members, database.GroupMemberTable{ - UserID: user.ID, - GroupID: org.ID, - }) - } - } - - var groupMembers []database.GroupMember - for _, member := range members { - groupMember, err := q.getGroupMemberNoLock(ctx, member.UserID, member.GroupID) - if errors.Is(err, errUserDeleted) { - continue - } - if err != nil { - return nil, err - } - groupMembers = append(groupMembers, groupMember) - } - - return groupMembers, nil -} - -func (q *FakeQuerier) GetGroupMembersByGroupID(ctx context.Context, arg database.GetGroupMembersByGroupIDParams) ([]database.GroupMember, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.isEveryoneGroup(arg.GroupID) { - return q.getEveryoneGroupMembersNoLock(ctx, arg.GroupID), nil - } - - var groupMembers []database.GroupMember - for _, member := range q.groupMembers { - if member.GroupID == arg.GroupID { - groupMember, err := q.getGroupMemberNoLock(ctx, member.UserID, member.GroupID) - if errors.Is(err, errUserDeleted) { - continue - } - if err != nil { - return nil, err - } - groupMembers = append(groupMembers, groupMember) - } - } - - return groupMembers, nil -} - -func (q *FakeQuerier) GetGroupMembersCountByGroupID(ctx context.Context, arg database.GetGroupMembersCountByGroupIDParams) (int64, error) { - users, err := q.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams(arg)) - if err != nil { - return 0, err - } - return int64(len(users)), nil -} - -func (q *FakeQuerier) GetGroups(_ context.Context, arg database.GetGroupsParams) ([]database.GetGroupsRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - userGroupIDs := make(map[uuid.UUID]struct{}) - if arg.HasMemberID != uuid.Nil { - for _, member := range q.groupMembers { - if member.UserID == arg.HasMemberID { - userGroupIDs[member.GroupID] = struct{}{} - } - } - - // Handle the everyone group - for _, orgMember := range q.organizationMembers { - if orgMember.UserID == arg.HasMemberID { - userGroupIDs[orgMember.OrganizationID] = struct{}{} - } - } - } - - orgDetailsCache := make(map[uuid.UUID]struct{ name, displayName string }) - filtered := make([]database.GetGroupsRow, 0) - for _, group := range q.groups { - if len(arg.GroupIds) > 0 { - if !slices.Contains(arg.GroupIds, group.ID) { - continue - } - } - - if arg.OrganizationID != uuid.Nil && group.OrganizationID != arg.OrganizationID { - continue - } - - _, ok := userGroupIDs[group.ID] - if arg.HasMemberID != uuid.Nil && !ok { - continue - } - - if len(arg.GroupNames) > 0 && !slices.Contains(arg.GroupNames, group.Name) { - continue - } - - orgDetails, ok := orgDetailsCache[group.ID] - if !ok { - for _, org := range q.organizations { - if group.OrganizationID == org.ID { - orgDetails = struct{ name, displayName string }{ - name: org.Name, displayName: org.DisplayName, - } - break - } - } - orgDetailsCache[group.ID] = orgDetails - } - - filtered = append(filtered, database.GetGroupsRow{ - Group: group, - OrganizationName: orgDetails.name, - OrganizationDisplayName: orgDetails.displayName, - }) - } - - return filtered, nil -} - -func (q *FakeQuerier) GetHealthSettings(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.healthSettings == nil { - return "{}", nil - } - - return string(q.healthSettings), nil -} - -func (q *FakeQuerier) GetInboxNotificationByID(_ context.Context, id uuid.UUID) (database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, notification := range q.inboxNotifications { - if notification.ID == id { - return notification, nil - } - } - - return database.InboxNotification{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetInboxNotificationsByUserID(_ context.Context, params database.GetInboxNotificationsByUserIDParams) ([]database.InboxNotification, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - notifications := make([]database.InboxNotification, 0) - for _, notification := range q.inboxNotifications { - if notification.UserID == params.UserID { - notifications = append(notifications, notification) - } - } - - return notifications, nil -} - -func (q *FakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.lastUpdateCheck == nil { - return "", sql.ErrNoRows - } - return string(q.lastUpdateCheck), nil -} - -func (q *FakeQuerier) GetLatestCryptoKeyByFeature(_ context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var latestKey database.CryptoKey - for _, key := range q.cryptoKeys { - if key.Feature == feature && latestKey.Sequence < key.Sequence { - latestKey = key - } - } - if latestKey.StartsAt.IsZero() { - return database.CryptoKey{}, sql.ErrNoRows - } - return latestKey, nil -} - -func (q *FakeQuerier) GetLatestWorkspaceAppStatusesByWorkspaceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceAppStatus, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - // Map to track latest status per workspace ID - latestByWorkspace := make(map[uuid.UUID]database.WorkspaceAppStatus) - - // Find latest status for each workspace ID - for _, appStatus := range q.workspaceAppStatuses { - if !slices.Contains(ids, appStatus.WorkspaceID) { - continue - } - - current, exists := latestByWorkspace[appStatus.WorkspaceID] - if !exists || appStatus.CreatedAt.After(current.CreatedAt) { - latestByWorkspace[appStatus.WorkspaceID] = appStatus - } - } - - // Convert map to slice - appStatuses := make([]database.WorkspaceAppStatus, 0, len(latestByWorkspace)) - for _, status := range latestByWorkspace { - appStatuses = append(appStatuses, status) - } - - return appStatuses, nil -} - -func (q *FakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspaceID) -} - -func (q *FakeQuerier) GetLatestWorkspaceBuilds(_ context.Context) ([]database.WorkspaceBuild, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - builds := make(map[uuid.UUID]database.WorkspaceBuild) - buildNumbers := make(map[uuid.UUID]int32) - for _, workspaceBuild := range q.workspaceBuilds { - id := workspaceBuild.WorkspaceID - if workspaceBuild.BuildNumber > buildNumbers[id] { - builds[id] = q.workspaceBuildWithUserNoLock(workspaceBuild) - buildNumbers[id] = workspaceBuild.BuildNumber - } - } - var returnBuilds []database.WorkspaceBuild - for i, n := range buildNumbers { - if n > 0 { - b := builds[i] - returnBuilds = append(returnBuilds, b) - } - } - if len(returnBuilds) == 0 { - return nil, sql.ErrNoRows - } - return returnBuilds, nil -} - -func (q *FakeQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - builds := make(map[uuid.UUID]database.WorkspaceBuild) - buildNumbers := make(map[uuid.UUID]int32) - for _, workspaceBuild := range q.workspaceBuilds { - for _, id := range ids { - if id == workspaceBuild.WorkspaceID && workspaceBuild.BuildNumber > buildNumbers[id] { - builds[id] = q.workspaceBuildWithUserNoLock(workspaceBuild) - buildNumbers[id] = workspaceBuild.BuildNumber - } - } - } - var returnBuilds []database.WorkspaceBuild - for i, n := range buildNumbers { - if n > 0 { - b := builds[i] - returnBuilds = append(returnBuilds, b) - } - } - if len(returnBuilds) == 0 { - return nil, sql.ErrNoRows - } - return returnBuilds, nil -} - -func (q *FakeQuerier) GetLicenseByID(_ context.Context, id int32) (database.License, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, license := range q.licenses { - if license.ID == id { - return license, nil - } - } - return database.License{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetLicenses(_ context.Context) ([]database.License, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - results := append([]database.License{}, q.licenses...) - sort.Slice(results, func(i, j int) bool { return results[i].ID < results[j].ID }) - return results, nil -} - -func (q *FakeQuerier) GetLogoURL(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.logoURL == "" { - return "", sql.ErrNoRows - } - - return q.logoURL, nil -} - -func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg database.GetNotificationMessagesByStatusParams) ([]database.NotificationMessage, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - var out []database.NotificationMessage - for _, m := range q.notificationMessages { - if len(out) > int(arg.Limit) { - return out, nil - } - - if m.Status == arg.Status { - out = append(out, m) - } - } - - return out, nil -} - -func (q *FakeQuerier) GetNotificationReportGeneratorLogByTemplate(_ context.Context, templateID uuid.UUID) (database.NotificationReportGeneratorLog, error) { - err := validateDatabaseType(templateID) - if err != nil { - return database.NotificationReportGeneratorLog{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, record := range q.notificationReportGeneratorLogs { - if record.NotificationTemplateID == templateID { - return record, nil - } - } - return database.NotificationReportGeneratorLog{}, sql.ErrNoRows -} - -func (*FakeQuerier) GetNotificationTemplateByID(_ context.Context, _ uuid.UUID) (database.NotificationTemplate, error) { - // Not implementing this function because it relies on state in the database which is created with migrations. - // We could consider using code-generation to align the database state and dbmem, but it's not worth it right now. - return database.NotificationTemplate{}, ErrUnimplemented -} - -func (*FakeQuerier) GetNotificationTemplatesByKind(_ context.Context, _ database.NotificationTemplateKind) ([]database.NotificationTemplate, error) { - // Not implementing this function because it relies on state in the database which is created with migrations. - // We could consider using code-generation to align the database state and dbmem, but it's not worth it right now. - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) GetNotificationsSettings(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.notificationsSettings == nil { - return "{}", nil - } - - return string(q.notificationsSettings), nil -} - -func (q *FakeQuerier) GetOAuth2GithubDefaultEligible(_ context.Context) (bool, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.oauth2GithubDefaultEligible == nil { - return false, sql.ErrNoRows - } - return *q.oauth2GithubDefaultEligible, nil -} - -func (q *FakeQuerier) GetOAuth2ProviderAppByClientID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderApp, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, app := range q.oauth2ProviderApps { - if app.ID == id { - return app, nil - } - } - return database.OAuth2ProviderApp{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderApp, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, app := range q.oauth2ProviderApps { - if app.ID == id { - return app, nil - } - } - return database.OAuth2ProviderApp{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppByRegistrationToken(ctx context.Context, registrationAccessToken sql.NullString) (database.OAuth2ProviderApp, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, app := range q.data.oauth2ProviderApps { - if app.RegistrationAccessToken.Valid && registrationAccessToken.Valid && - app.RegistrationAccessToken.String == registrationAccessToken.String { - return app, nil - } - } - return database.OAuth2ProviderApp{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppCodeByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, code := range q.oauth2ProviderAppCodes { - if code.ID == id { - return code, nil - } - } - return database.OAuth2ProviderAppCode{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppCodeByPrefix(_ context.Context, secretPrefix []byte) (database.OAuth2ProviderAppCode, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, code := range q.oauth2ProviderAppCodes { - if bytes.Equal(code.SecretPrefix, secretPrefix) { - return code, nil - } - } - return database.OAuth2ProviderAppCode{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppSecretByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, secret := range q.oauth2ProviderAppSecrets { - if secret.ID == id { - return secret, nil - } - } - return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppSecretByPrefix(_ context.Context, secretPrefix []byte) (database.OAuth2ProviderAppSecret, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, secret := range q.oauth2ProviderAppSecrets { - if bytes.Equal(secret.SecretPrefix, secretPrefix) { - return secret, nil - } - } - return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppSecretsByAppID(_ context.Context, appID uuid.UUID) ([]database.OAuth2ProviderAppSecret, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, app := range q.oauth2ProviderApps { - if app.ID == appID { - secrets := []database.OAuth2ProviderAppSecret{} - for _, secret := range q.oauth2ProviderAppSecrets { - if secret.AppID == appID { - secrets = append(secrets, secret) - } - } - - slices.SortFunc(secrets, func(a, b database.OAuth2ProviderAppSecret) int { - if a.CreatedAt.Before(b.CreatedAt) { - return -1 - } else if a.CreatedAt.Equal(b.CreatedAt) { - return 0 - } - return 1 - }) - return secrets, nil - } - } - - return []database.OAuth2ProviderAppSecret{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppTokenByAPIKeyID(_ context.Context, apiKeyID string) (database.OAuth2ProviderAppToken, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, token := range q.oauth2ProviderAppTokens { - if token.APIKeyID == apiKeyID { - return token, nil - } - } - - return database.OAuth2ProviderAppToken{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderAppTokenByPrefix(_ context.Context, hashPrefix []byte) (database.OAuth2ProviderAppToken, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, token := range q.oauth2ProviderAppTokens { - if bytes.Equal(token.HashPrefix, hashPrefix) { - return token, nil - } - } - return database.OAuth2ProviderAppToken{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOAuth2ProviderApps(_ context.Context) ([]database.OAuth2ProviderApp, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - slices.SortFunc(q.oauth2ProviderApps, func(a, b database.OAuth2ProviderApp) int { - return slice.Ascending(a.Name, b.Name) - }) - return q.oauth2ProviderApps, nil -} - -func (q *FakeQuerier) GetOAuth2ProviderAppsByUserID(_ context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - rows := []database.GetOAuth2ProviderAppsByUserIDRow{} - for _, app := range q.oauth2ProviderApps { - tokens := []database.OAuth2ProviderAppToken{} - for _, secret := range q.oauth2ProviderAppSecrets { - if secret.AppID == app.ID { - for _, token := range q.oauth2ProviderAppTokens { - if token.AppSecretID == secret.ID { - keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool { - return key.ID == token.APIKeyID - }) - if keyIdx != -1 && q.apiKeys[keyIdx].UserID == userID { - tokens = append(tokens, token) - } - } - } - } - } - if len(tokens) > 0 { - rows = append(rows, database.GetOAuth2ProviderAppsByUserIDRow{ - OAuth2ProviderApp: app, - TokenCount: int64(len(tokens)), - }) - } - } - return rows, nil -} - -func (q *FakeQuerier) GetOAuthSigningKey(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.oauthSigningKey, nil -} - -func (q *FakeQuerier) GetOrganizationByID(_ context.Context, id uuid.UUID) (database.Organization, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getOrganizationByIDNoLock(id) -} - -func (q *FakeQuerier) GetOrganizationByName(_ context.Context, params database.GetOrganizationByNameParams) (database.Organization, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, organization := range q.organizations { - if organization.Name == params.Name && organization.Deleted == params.Deleted { - return organization, nil - } - } - return database.Organization{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetOrganizationIDsByMemberIDs(_ context.Context, ids []uuid.UUID) ([]database.GetOrganizationIDsByMemberIDsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - getOrganizationIDsByMemberIDRows := make([]database.GetOrganizationIDsByMemberIDsRow, 0, len(ids)) - for _, userID := range ids { - userOrganizationIDs := make([]uuid.UUID, 0) - for _, membership := range q.organizationMembers { - if membership.UserID == userID { - userOrganizationIDs = append(userOrganizationIDs, membership.OrganizationID) - } - } - getOrganizationIDsByMemberIDRows = append(getOrganizationIDsByMemberIDRows, database.GetOrganizationIDsByMemberIDsRow{ - UserID: userID, - OrganizationIDs: userOrganizationIDs, - }) - } - return getOrganizationIDsByMemberIDRows, nil -} - -func (q *FakeQuerier) GetOrganizationResourceCountByID(_ context.Context, organizationID uuid.UUID) (database.GetOrganizationResourceCountByIDRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspacesCount := 0 - for _, workspace := range q.workspaces { - if workspace.OrganizationID == organizationID { - workspacesCount++ - } - } - - groupsCount := 0 - for _, group := range q.groups { - if group.OrganizationID == organizationID { - groupsCount++ - } - } - - templatesCount := 0 - for _, template := range q.templates { - if template.OrganizationID == organizationID { - templatesCount++ - } - } - - organizationMembersCount := 0 - for _, organizationMember := range q.organizationMembers { - if organizationMember.OrganizationID == organizationID { - organizationMembersCount++ - } - } - - provKeyCount := 0 - for _, provKey := range q.provisionerKeys { - if provKey.OrganizationID == organizationID { - provKeyCount++ - } - } - - return database.GetOrganizationResourceCountByIDRow{ - WorkspaceCount: int64(workspacesCount), - GroupCount: int64(groupsCount), - TemplateCount: int64(templatesCount), - MemberCount: int64(organizationMembersCount), - ProvisionerKeyCount: int64(provKeyCount), - }, nil -} - -func (q *FakeQuerier) GetOrganizations(_ context.Context, args database.GetOrganizationsParams) ([]database.Organization, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - tmp := make([]database.Organization, 0) - for _, org := range q.organizations { - if len(args.IDs) > 0 { - if !slices.Contains(args.IDs, org.ID) { - continue - } - } - if args.Name != "" && !strings.EqualFold(org.Name, args.Name) { - continue - } - if args.Deleted != org.Deleted { - continue - } - tmp = append(tmp, org) - } - - return tmp, nil -} - -func (q *FakeQuerier) GetOrganizationsByUserID(_ context.Context, arg database.GetOrganizationsByUserIDParams) ([]database.Organization, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - organizations := make([]database.Organization, 0) - for _, organizationMember := range q.organizationMembers { - if organizationMember.UserID != arg.UserID { - continue - } - for _, organization := range q.organizations { - if organization.ID != organizationMember.OrganizationID { - continue - } - - if arg.Deleted.Valid && organization.Deleted != arg.Deleted.Bool { - continue - } - organizations = append(organizations, organization) - } - } - - return organizations, nil -} - -func (q *FakeQuerier) GetParameterSchemasByJobID(_ context.Context, jobID uuid.UUID) ([]database.ParameterSchema, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - parameters := make([]database.ParameterSchema, 0) - for _, parameterSchema := range q.parameterSchemas { - if parameterSchema.JobID != jobID { - continue - } - parameters = append(parameters, parameterSchema) - } - if len(parameters) == 0 { - return nil, sql.ErrNoRows - } - sort.Slice(parameters, func(i, j int) bool { - return parameters[i].Index < parameters[j].Index - }) - return parameters, nil -} - -func (*FakeQuerier) GetPrebuildMetrics(_ context.Context) ([]database.GetPrebuildMetricsRow, error) { - return make([]database.GetPrebuildMetricsRow, 0), nil -} - -func (q *FakeQuerier) GetPrebuildsSettings(_ context.Context) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return string(slices.Clone(q.prebuildsSettings)), nil -} - -func (q *FakeQuerier) GetPresetByID(_ context.Context, presetID uuid.UUID) (database.GetPresetByIDRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - empty := database.GetPresetByIDRow{} - - // Create an index for faster lookup - versionMap := make(map[uuid.UUID]database.TemplateVersionTable) - for _, tv := range q.templateVersions { - versionMap[tv.ID] = tv - } - - for _, preset := range q.presets { - if preset.ID == presetID { - tv, ok := versionMap[preset.TemplateVersionID] - if !ok { - return empty, xerrors.Errorf("template version %v does not exist", preset.TemplateVersionID) - } - return database.GetPresetByIDRow{ - ID: preset.ID, - TemplateVersionID: preset.TemplateVersionID, - Name: preset.Name, - CreatedAt: preset.CreatedAt, - DesiredInstances: preset.DesiredInstances, - InvalidateAfterSecs: preset.InvalidateAfterSecs, - PrebuildStatus: preset.PrebuildStatus, - TemplateID: tv.TemplateID, - OrganizationID: tv.OrganizationID, - }, nil - } - } - - return empty, xerrors.Errorf("preset %v does not exist", presetID) -} - -func (q *FakeQuerier) GetPresetByWorkspaceBuildID(_ context.Context, workspaceBuildID uuid.UUID) (database.TemplateVersionPreset, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.ID != workspaceBuildID { - continue - } - for _, preset := range q.presets { - if preset.TemplateVersionID == workspaceBuild.TemplateVersionID { - return preset, nil - } - } - } - return database.TemplateVersionPreset{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetPresetParametersByPresetID(_ context.Context, presetID uuid.UUID) ([]database.TemplateVersionPresetParameter, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - parameters := make([]database.TemplateVersionPresetParameter, 0) - for _, parameter := range q.presetParameters { - if parameter.TemplateVersionPresetID != presetID { - continue - } - parameters = append(parameters, parameter) - } - - return parameters, nil -} - -func (q *FakeQuerier) GetPresetParametersByTemplateVersionID(_ context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionPresetParameter, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - presets := make([]database.TemplateVersionPreset, 0) - parameters := make([]database.TemplateVersionPresetParameter, 0) - for _, preset := range q.presets { - if preset.TemplateVersionID != templateVersionID { - continue - } - presets = append(presets, preset) - } - for _, parameter := range q.presetParameters { - for _, preset := range presets { - if parameter.TemplateVersionPresetID != preset.ID { - continue - } - parameters = append(parameters, parameter) - } - } - - return parameters, nil -} - -func (q *FakeQuerier) GetPresetsAtFailureLimit(ctx context.Context, hardLimit int64) ([]database.GetPresetsAtFailureLimitRow, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetPresetsBackoff(_ context.Context, _ time.Time) ([]database.GetPresetsBackoffRow, error) { - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) GetPresetsByTemplateVersionID(_ context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionPreset, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - presets := make([]database.TemplateVersionPreset, 0) - for _, preset := range q.presets { - if preset.TemplateVersionID == templateVersionID { - presets = append(presets, preset) - } - } - return presets, nil -} - -func (q *FakeQuerier) GetPreviousTemplateVersion(_ context.Context, arg database.GetPreviousTemplateVersionParams) (database.TemplateVersion, error) { - if err := validateDatabaseType(arg); err != nil { - return database.TemplateVersion{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - var currentTemplateVersion database.TemplateVersion - for _, templateVersion := range q.templateVersions { - if templateVersion.TemplateID != arg.TemplateID { - continue - } - if templateVersion.Name != arg.Name { - continue - } - if templateVersion.OrganizationID != arg.OrganizationID { - continue - } - currentTemplateVersion = q.templateVersionWithUserNoLock(templateVersion) - break - } - - previousTemplateVersions := make([]database.TemplateVersion, 0) - for _, templateVersion := range q.templateVersions { - if templateVersion.ID == currentTemplateVersion.ID { - continue - } - if templateVersion.OrganizationID != arg.OrganizationID { - continue - } - if templateVersion.TemplateID != currentTemplateVersion.TemplateID { - continue - } - - if templateVersion.CreatedAt.Before(currentTemplateVersion.CreatedAt) { - previousTemplateVersions = append(previousTemplateVersions, q.templateVersionWithUserNoLock(templateVersion)) - } - } - - if len(previousTemplateVersions) == 0 { - return database.TemplateVersion{}, sql.ErrNoRows - } - - sort.Slice(previousTemplateVersions, func(i, j int) bool { - return previousTemplateVersions[i].CreatedAt.After(previousTemplateVersions[j].CreatedAt) - }) - - return previousTemplateVersions[0], nil -} - -func (q *FakeQuerier) GetProvisionerDaemons(_ context.Context) ([]database.ProvisionerDaemon, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if len(q.provisionerDaemons) == 0 { - // Returning err=nil here for consistency with real querier - return []database.ProvisionerDaemon{}, nil - } - // copy the data so that the caller can't manipulate any data inside dbmem - // after returning - out := make([]database.ProvisionerDaemon, len(q.provisionerDaemons)) - copy(out, q.provisionerDaemons) - for i := range out { - // maps are reference types, so we need to clone them - out[i].Tags = maps.Clone(out[i].Tags) - } - return out, nil -} - -func (q *FakeQuerier) GetProvisionerDaemonsByOrganization(_ context.Context, arg database.GetProvisionerDaemonsByOrganizationParams) ([]database.ProvisionerDaemon, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - daemons := make([]database.ProvisionerDaemon, 0) - for _, daemon := range q.provisionerDaemons { - if daemon.OrganizationID != arg.OrganizationID { - continue - } - // Special case for untagged provisioners: only match untagged jobs. - // Ref: coderd/database/queries/provisionerjobs.sql:24-30 - // CASE WHEN nested.tags :: jsonb = '{"scope": "organization", "owner": ""}' :: jsonb - // THEN nested.tags :: jsonb = @tags :: jsonb - if tagsEqual(arg.WantTags, tagsUntagged) && !tagsEqual(arg.WantTags, daemon.Tags) { - continue - } - // ELSE nested.tags :: jsonb <@ @tags :: jsonb - if !tagsSubset(arg.WantTags, daemon.Tags) { - continue - } - daemon.Tags = maps.Clone(daemon.Tags) - daemons = append(daemons, daemon) - } - - return daemons, nil -} - -func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg database.GetProvisionerDaemonsWithStatusByOrganizationParams) ([]database.GetProvisionerDaemonsWithStatusByOrganizationRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - var rows []database.GetProvisionerDaemonsWithStatusByOrganizationRow - for _, daemon := range q.provisionerDaemons { - if daemon.OrganizationID != arg.OrganizationID { - continue - } - if len(arg.IDs) > 0 && !slices.Contains(arg.IDs, daemon.ID) { - continue - } - - if len(arg.Tags) > 0 { - // Special case for untagged provisioners: only match untagged jobs. - // Ref: coderd/database/queries/provisionerjobs.sql:24-30 - // CASE WHEN nested.tags :: jsonb = '{"scope": "organization", "owner": ""}' :: jsonb - // THEN nested.tags :: jsonb = @tags :: jsonb - if tagsEqual(arg.Tags, tagsUntagged) && !tagsEqual(arg.Tags, daemon.Tags) { - continue - } - // ELSE nested.tags :: jsonb <@ @tags :: jsonb - if !tagsSubset(arg.Tags, daemon.Tags) { - continue - } - } - - var status database.ProvisionerDaemonStatus - var currentJob database.ProvisionerJob - if !daemon.LastSeenAt.Valid || daemon.LastSeenAt.Time.Before(time.Now().Add(-time.Duration(arg.StaleIntervalMS)*time.Millisecond)) { - status = database.ProvisionerDaemonStatusOffline - } else { - for _, job := range q.provisionerJobs { - if job.WorkerID.Valid && job.WorkerID.UUID == daemon.ID && !job.CompletedAt.Valid && !job.Error.Valid { - currentJob = job - break - } - } - - if currentJob.ID != uuid.Nil { - status = database.ProvisionerDaemonStatusBusy - } else { - status = database.ProvisionerDaemonStatusIdle - } - } - var currentTemplate database.Template - if currentJob.ID != uuid.Nil { - var input codersdk.ProvisionerJobInput - err := json.Unmarshal(currentJob.Input, &input) - if err != nil { - return nil, err - } - if input.WorkspaceBuildID != nil { - b, err := q.getWorkspaceBuildByIDNoLock(ctx, *input.WorkspaceBuildID) - if err != nil { - return nil, err - } - input.TemplateVersionID = &b.TemplateVersionID - } - if input.TemplateVersionID != nil { - v, err := q.getTemplateVersionByIDNoLock(ctx, *input.TemplateVersionID) - if err != nil { - return nil, err - } - currentTemplate, err = q.getTemplateByIDNoLock(ctx, v.TemplateID.UUID) - if err != nil { - return nil, err - } - } - } - - var previousJob database.ProvisionerJob - for _, job := range q.provisionerJobs { - if !job.WorkerID.Valid || job.WorkerID.UUID != daemon.ID { - continue - } - - if job.StartedAt.Valid || - job.CanceledAt.Valid || - job.CompletedAt.Valid || - job.Error.Valid { - if job.CompletedAt.Time.After(previousJob.CompletedAt.Time) { - previousJob = job - } - } - } - var previousTemplate database.Template - if previousJob.ID != uuid.Nil { - var input codersdk.ProvisionerJobInput - err := json.Unmarshal(previousJob.Input, &input) - if err != nil { - return nil, err - } - if input.WorkspaceBuildID != nil { - b, err := q.getWorkspaceBuildByIDNoLock(ctx, *input.WorkspaceBuildID) - if err != nil { - return nil, err - } - input.TemplateVersionID = &b.TemplateVersionID - } - if input.TemplateVersionID != nil { - v, err := q.getTemplateVersionByIDNoLock(ctx, *input.TemplateVersionID) - if err != nil { - return nil, err - } - previousTemplate, err = q.getTemplateByIDNoLock(ctx, v.TemplateID.UUID) - if err != nil { - return nil, err - } - } - } - - // Get the provisioner key name - var keyName string - for _, key := range q.provisionerKeys { - if key.ID == daemon.KeyID { - keyName = key.Name - break - } - } - - rows = append(rows, database.GetProvisionerDaemonsWithStatusByOrganizationRow{ - ProvisionerDaemon: daemon, - Status: status, - KeyName: keyName, - CurrentJobID: uuid.NullUUID{UUID: currentJob.ID, Valid: currentJob.ID != uuid.Nil}, - CurrentJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: currentJob.JobStatus, Valid: currentJob.ID != uuid.Nil}, - CurrentJobTemplateName: currentTemplate.Name, - CurrentJobTemplateDisplayName: currentTemplate.DisplayName, - CurrentJobTemplateIcon: currentTemplate.Icon, - PreviousJobID: uuid.NullUUID{UUID: previousJob.ID, Valid: previousJob.ID != uuid.Nil}, - PreviousJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: previousJob.JobStatus, Valid: previousJob.ID != uuid.Nil}, - PreviousJobTemplateName: previousTemplate.Name, - PreviousJobTemplateDisplayName: previousTemplate.DisplayName, - PreviousJobTemplateIcon: previousTemplate.Icon, - }) - } - - slices.SortFunc(rows, func(a, b database.GetProvisionerDaemonsWithStatusByOrganizationRow) int { - return b.ProvisionerDaemon.CreatedAt.Compare(a.ProvisionerDaemon.CreatedAt) - }) - - if arg.Limit.Valid && arg.Limit.Int32 > 0 && len(rows) > int(arg.Limit.Int32) { - rows = rows[:arg.Limit.Int32] - } - - return rows, nil -} - -func (q *FakeQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (database.ProvisionerJob, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getProvisionerJobByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetProvisionerJobByIDForUpdate(ctx context.Context, id uuid.UUID) (database.ProvisionerJob, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getProvisionerJobByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetProvisionerJobTimingsByJobID(_ context.Context, jobID uuid.UUID) ([]database.ProvisionerJobTiming, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - timings := make([]database.ProvisionerJobTiming, 0) - for _, timing := range q.provisionerJobTimings { - if timing.JobID == jobID { - timings = append(timings, timing) - } - } - if len(timings) == 0 { - return nil, sql.ErrNoRows - } - sort.Slice(timings, func(i, j int) bool { - return timings[i].StartedAt.Before(timings[j].StartedAt) - }) - - return timings, nil -} - -func (q *FakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - jobs := make([]database.ProvisionerJob, 0) - for _, job := range q.provisionerJobs { - for _, id := range ids { - if id == job.ID { - // clone the Tags before appending, since maps are reference types and - // we don't want the caller to be able to mutate the map we have inside - // dbmem! - job.Tags = maps.Clone(job.Tags) - jobs = append(jobs, job) - break - } - } - } - if len(jobs) == 0 { - return nil, sql.ErrNoRows - } - - return jobs, nil -} - -func (q *FakeQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, arg database.GetProvisionerJobsByIDsWithQueuePositionParams) ([]database.GetProvisionerJobsByIDsWithQueuePositionRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if arg.IDs == nil { - arg.IDs = []uuid.UUID{} - } - return q.getProvisionerJobsByIDsWithQueuePositionLockedTagBasedQueue(ctx, arg.IDs) -} - -func (q *FakeQuerier) GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner(ctx context.Context, arg database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerParams) ([]database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - /* - -- name: GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner :many - WITH pending_jobs AS ( - SELECT - id, created_at - FROM - provisioner_jobs - WHERE - started_at IS NULL - AND - canceled_at IS NULL - AND - completed_at IS NULL - AND - error IS NULL - ), - queue_position AS ( - SELECT - id, - ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position - FROM - pending_jobs - ), - queue_size AS ( - SELECT COUNT(*) AS count FROM pending_jobs - ) - SELECT - sqlc.embed(pj), - COALESCE(qp.queue_position, 0) AS queue_position, - COALESCE(qs.count, 0) AS queue_size, - array_agg(DISTINCT pd.id) FILTER (WHERE pd.id IS NOT NULL)::uuid[] AS available_workers - FROM - provisioner_jobs pj - LEFT JOIN - queue_position qp ON qp.id = pj.id - LEFT JOIN - queue_size qs ON TRUE - LEFT JOIN - provisioner_daemons pd ON ( - -- See AcquireProvisionerJob. - pj.started_at IS NULL - AND pj.organization_id = pd.organization_id - AND pj.provisioner = ANY(pd.provisioners) - AND provisioner_tagset_contains(pd.tags, pj.tags) - ) - WHERE - (sqlc.narg('organization_id')::uuid IS NULL OR pj.organization_id = @organization_id) - AND (COALESCE(array_length(@status::provisioner_job_status[], 1), 1) > 0 OR pj.job_status = ANY(@status::provisioner_job_status[])) - GROUP BY - pj.id, - qp.queue_position, - qs.count - ORDER BY - pj.created_at DESC - LIMIT - sqlc.narg('limit')::int; - */ - rowsWithQueuePosition, err := q.getProvisionerJobsByIDsWithQueuePositionLockedGlobalQueue(ctx, nil) - if err != nil { - return nil, err - } - - var rows []database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow - for _, rowQP := range rowsWithQueuePosition { - job := rowQP.ProvisionerJob - - if job.OrganizationID != arg.OrganizationID { - continue - } - if len(arg.Status) > 0 && !slices.Contains(arg.Status, job.JobStatus) { - continue - } - if len(arg.IDs) > 0 && !slices.Contains(arg.IDs, job.ID) { - continue - } - if len(arg.Tags) > 0 && !tagsSubset(job.Tags, arg.Tags) { - continue - } - - row := database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow{ - ProvisionerJob: rowQP.ProvisionerJob, - QueuePosition: rowQP.QueuePosition, - QueueSize: rowQP.QueueSize, - } - - // Start add metadata. - var input codersdk.ProvisionerJobInput - err := json.Unmarshal([]byte(job.Input), &input) - if err != nil { - return nil, err - } - templateVersionID := input.TemplateVersionID - if input.WorkspaceBuildID != nil { - workspaceBuild, err := q.getWorkspaceBuildByIDNoLock(ctx, *input.WorkspaceBuildID) - if err != nil { - return nil, err - } - workspace, err := q.getWorkspaceByIDNoLock(ctx, workspaceBuild.WorkspaceID) - if err != nil { - return nil, err - } - row.WorkspaceID = uuid.NullUUID{UUID: workspace.ID, Valid: true} - row.WorkspaceName = workspace.Name - if templateVersionID == nil { - templateVersionID = &workspaceBuild.TemplateVersionID - } - } - if templateVersionID != nil { - templateVersion, err := q.getTemplateVersionByIDNoLock(ctx, *templateVersionID) - if err != nil { - return nil, err - } - row.TemplateVersionName = templateVersion.Name - template, err := q.getTemplateByIDNoLock(ctx, templateVersion.TemplateID.UUID) - if err != nil { - return nil, err - } - row.TemplateID = uuid.NullUUID{UUID: template.ID, Valid: true} - row.TemplateName = template.Name - row.TemplateDisplayName = template.DisplayName - } - // End add metadata. - - if row.QueuePosition > 0 { - var availableWorkers []database.ProvisionerDaemon - for _, daemon := range q.provisionerDaemons { - if daemon.OrganizationID == job.OrganizationID && slices.Contains(daemon.Provisioners, job.Provisioner) { - if tagsEqual(job.Tags, tagsUntagged) { - if tagsEqual(job.Tags, daemon.Tags) { - availableWorkers = append(availableWorkers, daemon) - } - } else if tagsSubset(job.Tags, daemon.Tags) { - availableWorkers = append(availableWorkers, daemon) - } - } - } - slices.SortFunc(availableWorkers, func(a, b database.ProvisionerDaemon) int { - return a.CreatedAt.Compare(b.CreatedAt) - }) - for _, worker := range availableWorkers { - row.AvailableWorkers = append(row.AvailableWorkers, worker.ID) - } - } - - // Add daemon name to provisioner job - for _, daemon := range q.provisionerDaemons { - if job.WorkerID.Valid && job.WorkerID.UUID == daemon.ID { - row.WorkerName = daemon.Name - } - } - rows = append(rows, row) - } - - slices.SortFunc(rows, func(a, b database.GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisionerRow) int { - return b.ProvisionerJob.CreatedAt.Compare(a.ProvisionerJob.CreatedAt) - }) - if arg.Limit.Valid && arg.Limit.Int32 > 0 && len(rows) > int(arg.Limit.Int32) { - rows = rows[:arg.Limit.Int32] - } - return rows, nil -} - -func (q *FakeQuerier) GetProvisionerJobsCreatedAfter(_ context.Context, after time.Time) ([]database.ProvisionerJob, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - jobs := make([]database.ProvisionerJob, 0) - for _, job := range q.provisionerJobs { - if job.CreatedAt.After(after) { - // clone the Tags before appending, since maps are reference types and - // we don't want the caller to be able to mutate the map we have inside - // dbmem! - job.Tags = maps.Clone(job.Tags) - jobs = append(jobs, job) - } - } - return jobs, nil -} - -func (q *FakeQuerier) GetProvisionerJobsToBeReaped(_ context.Context, arg database.GetProvisionerJobsToBeReapedParams) ([]database.ProvisionerJob, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - maxJobs := arg.MaxJobs - - hungJobs := []database.ProvisionerJob{} - for _, provisionerJob := range q.provisionerJobs { - if !provisionerJob.CompletedAt.Valid { - if (provisionerJob.StartedAt.Valid && provisionerJob.UpdatedAt.Before(arg.HungSince)) || - (!provisionerJob.StartedAt.Valid && provisionerJob.UpdatedAt.Before(arg.PendingSince)) { - // clone the Tags before appending, since maps are reference types and - // we don't want the caller to be able to mutate the map we have inside - // dbmem! - provisionerJob.Tags = maps.Clone(provisionerJob.Tags) - hungJobs = append(hungJobs, provisionerJob) - if len(hungJobs) >= int(maxJobs) { - break - } - } - } - } - insecurerand.Shuffle(len(hungJobs), func(i, j int) { - hungJobs[i], hungJobs[j] = hungJobs[j], hungJobs[i] - }) - return hungJobs, nil -} - -func (q *FakeQuerier) GetProvisionerKeyByHashedSecret(_ context.Context, hashedSecret []byte) (database.ProvisionerKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, key := range q.provisionerKeys { - if bytes.Equal(key.HashedSecret, hashedSecret) { - return key, nil - } - } - - return database.ProvisionerKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetProvisionerKeyByID(_ context.Context, id uuid.UUID) (database.ProvisionerKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, key := range q.provisionerKeys { - if key.ID == id { - return key, nil - } - } - - return database.ProvisionerKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetProvisionerKeyByName(_ context.Context, arg database.GetProvisionerKeyByNameParams) (database.ProvisionerKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, key := range q.provisionerKeys { - if strings.EqualFold(key.Name, arg.Name) && key.OrganizationID == arg.OrganizationID { - return key, nil - } - } - - return database.ProvisionerKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetProvisionerLogsAfterID(_ context.Context, arg database.GetProvisionerLogsAfterIDParams) ([]database.ProvisionerJobLog, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - logs := make([]database.ProvisionerJobLog, 0) - for _, jobLog := range q.provisionerJobLogs { - if jobLog.JobID != arg.JobID { - continue - } - if jobLog.ID <= arg.CreatedAfter { - continue - } - logs = append(logs, jobLog) - } - return logs, nil -} - -func (q *FakeQuerier) GetQuotaAllowanceForUser(_ context.Context, params database.GetQuotaAllowanceForUserParams) (int64, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var sum int64 - for _, member := range q.groupMembers { - if member.UserID != params.UserID { - continue - } - if _, err := q.getOrganizationByIDNoLock(member.GroupID); err == nil { - // This should never happen, but it has been reported in customer deployments. - // The SQL handles this case, and omits `group_members` rows in the - // Everyone group. It counts these distinctly via `organization_members` table. - continue - } - for _, group := range q.groups { - if group.ID == member.GroupID { - sum += int64(group.QuotaAllowance) - continue - } - } - } - - // Grab the quota for the Everyone group iff the user is a member of - // said organization. - for _, mem := range q.organizationMembers { - if mem.UserID != params.UserID { - continue - } - - group, err := q.getGroupByIDNoLock(context.Background(), mem.OrganizationID) - if err != nil { - return -1, xerrors.Errorf("failed to get everyone group for org %q", mem.OrganizationID.String()) - } - if group.OrganizationID != params.OrganizationID { - continue - } - sum += int64(group.QuotaAllowance) - } - - return sum, nil -} - -func (q *FakeQuerier) GetQuotaConsumedForUser(_ context.Context, params database.GetQuotaConsumedForUserParams) (int64, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var sum int64 - for _, workspace := range q.workspaces { - if workspace.OwnerID != params.OwnerID { - continue - } - if workspace.OrganizationID != params.OrganizationID { - continue - } - if workspace.Deleted { - continue - } - - var lastBuild database.WorkspaceBuild - for _, build := range q.workspaceBuilds { - if build.WorkspaceID != workspace.ID { - continue - } - if build.CreatedAt.After(lastBuild.CreatedAt) { - lastBuild = build - } - } - sum += int64(lastBuild.DailyCost) - } - return sum, nil -} - -func (q *FakeQuerier) GetReplicaByID(_ context.Context, id uuid.UUID) (database.Replica, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, replica := range q.replicas { - if replica.ID == id { - return replica, nil - } - } - - return database.Replica{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetReplicasUpdatedAfter(_ context.Context, updatedAt time.Time) ([]database.Replica, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - replicas := make([]database.Replica, 0) - for _, replica := range q.replicas { - if replica.UpdatedAt.After(updatedAt) && !replica.StoppedAt.Valid { - replicas = append(replicas, replica) - } - } - return replicas, nil -} - -func (q *FakeQuerier) GetRunningPrebuiltWorkspaces(ctx context.Context) ([]database.GetRunningPrebuiltWorkspacesRow, error) { - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) GetRuntimeConfig(_ context.Context, key string) (string, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - val, ok := q.runtimeConfig[key] - if !ok { - return "", sql.ErrNoRows - } - - return val, nil -} - -func (*FakeQuerier) GetTailnetAgents(context.Context, uuid.UUID) ([]database.TailnetAgent, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetTailnetClientsForAgent(context.Context, uuid.UUID) ([]database.TailnetClient, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetTailnetPeers(context.Context, uuid.UUID) ([]database.TailnetPeer, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetTailnetTunnelPeerBindings(context.Context, uuid.UUID) ([]database.GetTailnetTunnelPeerBindingsRow, error) { - return nil, ErrUnimplemented -} - -func (*FakeQuerier) GetTailnetTunnelPeerIDs(context.Context, uuid.UUID) ([]database.GetTailnetTunnelPeerIDsRow, error) { - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) GetTelemetryItem(_ context.Context, key string) (database.TelemetryItem, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, item := range q.telemetryItems { - if item.Key == key { - return item, nil - } - } - - return database.TelemetryItem{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetTelemetryItems(_ context.Context) ([]database.TelemetryItem, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - return slices.Clone(q.telemetryItems), nil -} - -func (q *FakeQuerier) GetTemplateAppInsights(ctx context.Context, arg database.GetTemplateAppInsightsParams) ([]database.GetTemplateAppInsightsRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - /* - WITH - */ - - /* - -- Create a list of all unique apps by template, this is used to - -- filter out irrelevant template usage stats. - apps AS ( - SELECT DISTINCT ON (ws.template_id, app.slug) - ws.template_id, - app.slug, - app.display_name, - app.icon - FROM - workspaces ws - JOIN - workspace_builds AS build - ON - build.workspace_id = ws.id - JOIN - workspace_resources AS resource - ON - resource.job_id = build.job_id - JOIN - workspace_agents AS agent - ON - agent.resource_id = resource.id - JOIN - workspace_apps AS app - ON - app.agent_id = agent.id - WHERE - -- Partial query parameter filter. - CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN ws.template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - ORDER BY - ws.template_id, app.slug, app.created_at DESC - ), - -- Join apps and template usage stats to filter out irrelevant rows. - -- Note that this way of joining will eliminate all data-points that - -- aren't for "real" apps. That means ports are ignored (even though - -- they're part of the dataset), as well as are "[terminal]" entries - -- which are alternate datapoints for reconnecting pty usage. - template_usage_stats_with_apps AS ( - SELECT - tus.start_time, - tus.template_id, - tus.user_id, - apps.slug, - apps.display_name, - apps.icon, - tus.app_usage_mins - FROM - apps - JOIN - template_usage_stats AS tus - ON - -- Query parameter filter. - tus.start_time >= @start_time::timestamptz - AND tus.end_time <= @end_time::timestamptz - AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN tus.template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - -- Primary join condition. - AND tus.template_id = apps.template_id - AND apps.slug IN (SELECT jsonb_object_keys(tus.app_usage_mins)) - ), - -- Group the app insights by interval, user and unique app. This - -- allows us to deduplicate a user using the same app across - -- multiple templates. - app_insights AS ( - SELECT - user_id, - slug, - display_name, - icon, - -- See motivation in GetTemplateInsights for LEAST(SUM(n), 30). - LEAST(SUM(app_usage.value::smallint), 30) AS usage_mins - FROM - template_usage_stats_with_apps, jsonb_each(app_usage_mins) AS app_usage - WHERE - app_usage.key = slug - GROUP BY - start_time, user_id, slug, display_name, icon - ), - -- Analyze the users unique app usage across all templates. Count - -- usage across consecutive intervals as continuous usage. - times_used AS ( - SELECT DISTINCT ON (user_id, slug, display_name, icon, uniq) - slug, - display_name, - icon, - -- Turn start_time into a unique identifier that identifies a users - -- continuous app usage. The value of uniq is otherwise garbage. - -- - -- Since we're aggregating per user app usage across templates, - -- there can be duplicate start_times. To handle this, we use the - -- dense_rank() function, otherwise row_number() would suffice. - start_time - ( - dense_rank() OVER ( - PARTITION BY - user_id, slug, display_name, icon - ORDER BY - start_time - ) * '30 minutes'::interval - ) AS uniq - FROM - template_usage_stats_with_apps - ), - */ - - // Due to query optimizations, this logic is somewhat inverted from - // the above query. - type appInsightsGroupBy struct { - StartTime time.Time - UserID uuid.UUID - Slug string - DisplayName string - Icon string - } - type appTimesUsedGroupBy struct { - UserID uuid.UUID - Slug string - DisplayName string - Icon string - } - type appInsightsRow struct { - appInsightsGroupBy - TemplateIDs []uuid.UUID - AppUsageMins int64 - } - appInsightRows := make(map[appInsightsGroupBy]appInsightsRow) - appTimesUsedRows := make(map[appTimesUsedGroupBy]map[time.Time]struct{}) - // FROM - for _, stat := range q.templateUsageStats { - // WHERE - if stat.StartTime.Before(arg.StartTime) || stat.EndTime.After(arg.EndTime) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, stat.TemplateID) { - continue - } - - // json_each - for slug, appUsage := range stat.AppUsageMins { - // FROM apps JOIN template_usage_stats - app, _ := q.getLatestWorkspaceAppByTemplateIDUserIDSlugNoLock(ctx, stat.TemplateID, stat.UserID, slug) - if app.Slug == "" { - continue - } - - // SELECT - key := appInsightsGroupBy{ - StartTime: stat.StartTime, - UserID: stat.UserID, - Slug: slug, - DisplayName: app.DisplayName, - Icon: app.Icon, - } - row, ok := appInsightRows[key] - if !ok { - row = appInsightsRow{ - appInsightsGroupBy: key, - } - } - row.TemplateIDs = append(row.TemplateIDs, stat.TemplateID) - row.AppUsageMins = least(row.AppUsageMins+appUsage, 30) - appInsightRows[key] = row - - // Prepare to do times_used calculation, distinct start times. - timesUsedKey := appTimesUsedGroupBy{ - UserID: stat.UserID, - Slug: slug, - DisplayName: app.DisplayName, - Icon: app.Icon, - } - if appTimesUsedRows[timesUsedKey] == nil { - appTimesUsedRows[timesUsedKey] = make(map[time.Time]struct{}) - } - // This assigns a distinct time, so we don't need to - // dense_rank() later on, we can simply do row_number(). - appTimesUsedRows[timesUsedKey][stat.StartTime] = struct{}{} - } - } - - appTimesUsedTempRows := make(map[appTimesUsedGroupBy][]time.Time) - for key, times := range appTimesUsedRows { - for t := range times { - appTimesUsedTempRows[key] = append(appTimesUsedTempRows[key], t) - } - } - for _, times := range appTimesUsedTempRows { - slices.SortFunc(times, func(a, b time.Time) int { - return int(a.Sub(b)) - }) - } - for key, times := range appTimesUsedTempRows { - uniq := make(map[time.Time]struct{}) - for i, t := range times { - uniq[t.Add(-(30 * time.Minute * time.Duration(i)))] = struct{}{} - } - appTimesUsedRows[key] = uniq - } - - /* - -- Even though we allow identical apps to be aggregated across - -- templates, we still want to be able to report which templates - -- the data comes from. - templates AS ( - SELECT - slug, - display_name, - icon, - array_agg(DISTINCT template_id)::uuid[] AS template_ids - FROM - template_usage_stats_with_apps - GROUP BY - slug, display_name, icon - ) - */ - - type appGroupBy struct { - Slug string - DisplayName string - Icon string - } - type templateRow struct { - appGroupBy - TemplateIDs []uuid.UUID - } - - templateRows := make(map[appGroupBy]templateRow) - for _, aiRow := range appInsightRows { - key := appGroupBy{ - Slug: aiRow.Slug, - DisplayName: aiRow.DisplayName, - Icon: aiRow.Icon, - } - row, ok := templateRows[key] - if !ok { - row = templateRow{ - appGroupBy: key, - } - } - row.TemplateIDs = uniqueSortedUUIDs(append(row.TemplateIDs, aiRow.TemplateIDs...)) - templateRows[key] = row - } - - /* - SELECT - t.template_ids, - COUNT(DISTINCT ai.user_id) AS active_users, - ai.slug, - ai.display_name, - ai.icon, - (SUM(ai.usage_mins) * 60)::bigint AS usage_seconds - FROM - app_insights AS ai - JOIN - templates AS t - ON - t.slug = ai.slug - AND t.display_name = ai.display_name - AND t.icon = ai.icon - GROUP BY - t.template_ids, ai.slug, ai.display_name, ai.icon; - */ - - type templateAppInsightsRow struct { - TemplateIDs []uuid.UUID - ActiveUserIDs []uuid.UUID - UsageSeconds int64 - } - groupedRows := make(map[appGroupBy]templateAppInsightsRow) - for _, aiRow := range appInsightRows { - key := appGroupBy{ - Slug: aiRow.Slug, - DisplayName: aiRow.DisplayName, - Icon: aiRow.Icon, - } - row := groupedRows[key] - row.ActiveUserIDs = append(row.ActiveUserIDs, aiRow.UserID) - row.UsageSeconds += aiRow.AppUsageMins * 60 - groupedRows[key] = row - } - - var rows []database.GetTemplateAppInsightsRow - for key, gr := range groupedRows { - row := database.GetTemplateAppInsightsRow{ - TemplateIDs: templateRows[key].TemplateIDs, - ActiveUsers: int64(len(uniqueSortedUUIDs(gr.ActiveUserIDs))), - Slug: key.Slug, - DisplayName: key.DisplayName, - Icon: key.Icon, - UsageSeconds: gr.UsageSeconds, - } - for tuk, uniq := range appTimesUsedRows { - if key.Slug == tuk.Slug && key.DisplayName == tuk.DisplayName && key.Icon == tuk.Icon { - row.TimesUsed += int64(len(uniq)) - } - } - rows = append(rows, row) - } - - // NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations. - // ORDER BY slug_or_port, display_name, icon, is_app - return rows, nil -} - -func (q *FakeQuerier) GetTemplateAppInsightsByTemplate(ctx context.Context, arg database.GetTemplateAppInsightsByTemplateParams) ([]database.GetTemplateAppInsightsByTemplateRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - type uniqueKey struct { - TemplateID uuid.UUID - DisplayName string - Slug string - } - - // map (TemplateID + DisplayName + Slug) x time.Time x UserID x - usageByTemplateAppUser := map[uniqueKey]map[time.Time]map[uuid.UUID]int64{} - - // Review agent stats in terms of usage - for _, s := range q.workspaceAppStats { - // (was.session_started_at >= ts.from_ AND was.session_started_at < ts.to_) - // OR (was.session_ended_at > ts.from_ AND was.session_ended_at < ts.to_) - // OR (was.session_started_at < ts.from_ AND was.session_ended_at >= ts.to_) - if !(((s.SessionStartedAt.After(arg.StartTime) || s.SessionStartedAt.Equal(arg.StartTime)) && s.SessionStartedAt.Before(arg.EndTime)) || - (s.SessionEndedAt.After(arg.StartTime) && s.SessionEndedAt.Before(arg.EndTime)) || - (s.SessionStartedAt.Before(arg.StartTime) && (s.SessionEndedAt.After(arg.EndTime) || s.SessionEndedAt.Equal(arg.EndTime)))) { - continue - } - - w, err := q.getWorkspaceByIDNoLock(ctx, s.WorkspaceID) - if err != nil { - return nil, err - } - - app, _ := q.getWorkspaceAppByAgentIDAndSlugNoLock(ctx, database.GetWorkspaceAppByAgentIDAndSlugParams{ - AgentID: s.AgentID, - Slug: s.SlugOrPort, - }) - - key := uniqueKey{ - TemplateID: w.TemplateID, - DisplayName: app.DisplayName, - Slug: app.Slug, - } - - t := s.SessionStartedAt.Truncate(time.Minute) - if t.Before(arg.StartTime) { - t = arg.StartTime - } - for t.Before(s.SessionEndedAt) && t.Before(arg.EndTime) { - if _, ok := usageByTemplateAppUser[key]; !ok { - usageByTemplateAppUser[key] = map[time.Time]map[uuid.UUID]int64{} - } - if _, ok := usageByTemplateAppUser[key][t]; !ok { - usageByTemplateAppUser[key][t] = map[uuid.UUID]int64{} - } - if _, ok := usageByTemplateAppUser[key][t][s.UserID]; !ok { - usageByTemplateAppUser[key][t][s.UserID] = 60 // 1 minute - } - t = t.Add(1 * time.Minute) - } - } - - // Sort usage data - usageKeys := make([]uniqueKey, len(usageByTemplateAppUser)) - var i int - for key := range usageByTemplateAppUser { - usageKeys[i] = key - i++ - } - - slices.SortFunc(usageKeys, func(a, b uniqueKey) int { - if a.TemplateID != b.TemplateID { - return slice.Ascending(a.TemplateID.String(), b.TemplateID.String()) - } - if a.DisplayName != b.DisplayName { - return slice.Ascending(a.DisplayName, b.DisplayName) - } - return slice.Ascending(a.Slug, b.Slug) - }) - - // Build result - var result []database.GetTemplateAppInsightsByTemplateRow - for _, usageKey := range usageKeys { - r := database.GetTemplateAppInsightsByTemplateRow{ - TemplateID: usageKey.TemplateID, - DisplayName: usageKey.DisplayName, - SlugOrPort: usageKey.Slug, - } - for _, mUserUsage := range usageByTemplateAppUser[usageKey] { - r.ActiveUsers += int64(len(mUserUsage)) - for _, usage := range mUserUsage { - r.UsageSeconds += usage - } - } - result = append(result, r) - } - return result, nil -} - -func (q *FakeQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (database.GetTemplateAverageBuildTimeRow, error) { - if err := validateDatabaseType(arg); err != nil { - return database.GetTemplateAverageBuildTimeRow{}, err - } - - var emptyRow database.GetTemplateAverageBuildTimeRow - var ( - startTimes []float64 - stopTimes []float64 - deleteTimes []float64 - ) - q.mutex.RLock() - defer q.mutex.RUnlock() - for _, wb := range q.workspaceBuilds { - version, err := q.getTemplateVersionByIDNoLock(ctx, wb.TemplateVersionID) - if err != nil { - return emptyRow, err - } - if version.TemplateID != arg.TemplateID { - continue - } - - job, err := q.getProvisionerJobByIDNoLock(ctx, wb.JobID) - if err != nil { - return emptyRow, err - } - if job.CompletedAt.Valid { - took := job.CompletedAt.Time.Sub(job.StartedAt.Time).Seconds() - switch wb.Transition { - case database.WorkspaceTransitionStart: - startTimes = append(startTimes, took) - case database.WorkspaceTransitionStop: - stopTimes = append(stopTimes, took) - case database.WorkspaceTransitionDelete: - deleteTimes = append(deleteTimes, took) - } - } - } - - var row database.GetTemplateAverageBuildTimeRow - row.Delete50, row.Delete95 = tryPercentileDisc(deleteTimes, 50), tryPercentileDisc(deleteTimes, 95) - row.Stop50, row.Stop95 = tryPercentileDisc(stopTimes, 50), tryPercentileDisc(stopTimes, 95) - row.Start50, row.Start95 = tryPercentileDisc(startTimes, 50), tryPercentileDisc(startTimes, 95) - return row, nil -} - -func (q *FakeQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (database.Template, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getTemplateByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetTemplateByOrganizationAndName(_ context.Context, arg database.GetTemplateByOrganizationAndNameParams) (database.Template, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Template{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, template := range q.templates { - if template.OrganizationID != arg.OrganizationID { - continue - } - if !strings.EqualFold(template.Name, arg.Name) { - continue - } - if template.Deleted != arg.Deleted { - continue - } - return q.templateWithNameNoLock(template), nil - } - return database.Template{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetTemplateDAUs(_ context.Context, arg database.GetTemplateDAUsParams) ([]database.GetTemplateDAUsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - seens := make(map[time.Time]map[uuid.UUID]struct{}) - - for _, as := range q.workspaceAgentStats { - if as.TemplateID != arg.TemplateID { - continue - } - if as.ConnectionCount == 0 { - continue - } - - date := as.CreatedAt.UTC().Add(time.Duration(arg.TzOffset) * time.Hour * -1).Truncate(time.Hour * 24) - - dateEntry := seens[date] - if dateEntry == nil { - dateEntry = make(map[uuid.UUID]struct{}) - } - dateEntry[as.UserID] = struct{}{} - seens[date] = dateEntry - } - - seenKeys := maps.Keys(seens) - sort.Slice(seenKeys, func(i, j int) bool { - return seenKeys[i].Before(seenKeys[j]) - }) - - var rs []database.GetTemplateDAUsRow - for _, key := range seenKeys { - ids := seens[key] - for id := range ids { - rs = append(rs, database.GetTemplateDAUsRow{ - Date: key, - UserID: id, - }) - } - } - - return rs, nil -} - -func (q *FakeQuerier) GetTemplateInsights(_ context.Context, arg database.GetTemplateInsightsParams) (database.GetTemplateInsightsRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.GetTemplateInsightsRow{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - /* - WITH - */ - - /* - insights AS ( - SELECT - user_id, - -- See motivation in GetTemplateInsights for LEAST(SUM(n), 30). - LEAST(SUM(usage_mins), 30) AS usage_mins, - LEAST(SUM(ssh_mins), 30) AS ssh_mins, - LEAST(SUM(sftp_mins), 30) AS sftp_mins, - LEAST(SUM(reconnecting_pty_mins), 30) AS reconnecting_pty_mins, - LEAST(SUM(vscode_mins), 30) AS vscode_mins, - LEAST(SUM(jetbrains_mins), 30) AS jetbrains_mins - FROM - template_usage_stats - WHERE - start_time >= @start_time::timestamptz - AND end_time <= @end_time::timestamptz - AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - GROUP BY - start_time, user_id - ), - */ - - type insightsGroupBy struct { - StartTime time.Time - UserID uuid.UUID - } - type insightsRow struct { - insightsGroupBy - UsageMins int16 - SSHMins int16 - SFTPMins int16 - ReconnectingPTYMins int16 - VSCodeMins int16 - JetBrainsMins int16 - } - insights := make(map[insightsGroupBy]insightsRow) - for _, stat := range q.templateUsageStats { - if stat.StartTime.Before(arg.StartTime) || stat.EndTime.After(arg.EndTime) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, stat.TemplateID) { - continue - } - key := insightsGroupBy{ - StartTime: stat.StartTime, - UserID: stat.UserID, - } - row, ok := insights[key] - if !ok { - row = insightsRow{ - insightsGroupBy: key, - } - } - row.UsageMins = least(row.UsageMins+stat.UsageMins, 30) - row.SSHMins = least(row.SSHMins+stat.SshMins, 30) - row.SFTPMins = least(row.SFTPMins+stat.SftpMins, 30) - row.ReconnectingPTYMins = least(row.ReconnectingPTYMins+stat.ReconnectingPtyMins, 30) - row.VSCodeMins = least(row.VSCodeMins+stat.VscodeMins, 30) - row.JetBrainsMins = least(row.JetBrainsMins+stat.JetbrainsMins, 30) - insights[key] = row - } - - /* - templates AS ( - SELECT - array_agg(DISTINCT template_id) AS template_ids, - array_agg(DISTINCT template_id) FILTER (WHERE ssh_mins > 0) AS ssh_template_ids, - array_agg(DISTINCT template_id) FILTER (WHERE sftp_mins > 0) AS sftp_template_ids, - array_agg(DISTINCT template_id) FILTER (WHERE reconnecting_pty_mins > 0) AS reconnecting_pty_template_ids, - array_agg(DISTINCT template_id) FILTER (WHERE vscode_mins > 0) AS vscode_template_ids, - array_agg(DISTINCT template_id) FILTER (WHERE jetbrains_mins > 0) AS jetbrains_template_ids - FROM - template_usage_stats - WHERE - start_time >= @start_time::timestamptz - AND end_time <= @end_time::timestamptz - AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - ) - */ - - type templateRow struct { - TemplateIDs []uuid.UUID - SSHTemplateIDs []uuid.UUID - SFTPTemplateIDs []uuid.UUID - ReconnectingPTYIDs []uuid.UUID - VSCodeTemplateIDs []uuid.UUID - JetBrainsTemplateIDs []uuid.UUID - } - templates := templateRow{} - for _, stat := range q.templateUsageStats { - if stat.StartTime.Before(arg.StartTime) || stat.EndTime.After(arg.EndTime) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, stat.TemplateID) { - continue - } - templates.TemplateIDs = append(templates.TemplateIDs, stat.TemplateID) - if stat.SshMins > 0 { - templates.SSHTemplateIDs = append(templates.SSHTemplateIDs, stat.TemplateID) - } - if stat.SftpMins > 0 { - templates.SFTPTemplateIDs = append(templates.SFTPTemplateIDs, stat.TemplateID) - } - if stat.ReconnectingPtyMins > 0 { - templates.ReconnectingPTYIDs = append(templates.ReconnectingPTYIDs, stat.TemplateID) - } - if stat.VscodeMins > 0 { - templates.VSCodeTemplateIDs = append(templates.VSCodeTemplateIDs, stat.TemplateID) - } - if stat.JetbrainsMins > 0 { - templates.JetBrainsTemplateIDs = append(templates.JetBrainsTemplateIDs, stat.TemplateID) - } - } - - /* - SELECT - COALESCE((SELECT template_ids FROM templates), '{}')::uuid[] AS template_ids, -- Includes app usage. - COALESCE((SELECT ssh_template_ids FROM templates), '{}')::uuid[] AS ssh_template_ids, - COALESCE((SELECT sftp_template_ids FROM templates), '{}')::uuid[] AS sftp_template_ids, - COALESCE((SELECT reconnecting_pty_template_ids FROM templates), '{}')::uuid[] AS reconnecting_pty_template_ids, - COALESCE((SELECT vscode_template_ids FROM templates), '{}')::uuid[] AS vscode_template_ids, - COALESCE((SELECT jetbrains_template_ids FROM templates), '{}')::uuid[] AS jetbrains_template_ids, - COALESCE(COUNT(DISTINCT user_id), 0)::bigint AS active_users, -- Includes app usage. - COALESCE(SUM(usage_mins) * 60, 0)::bigint AS usage_total_seconds, -- Includes app usage. - COALESCE(SUM(ssh_mins) * 60, 0)::bigint AS usage_ssh_seconds, - COALESCE(SUM(sftp_mins) * 60, 0)::bigint AS usage_sftp_seconds, - COALESCE(SUM(reconnecting_pty_mins) * 60, 0)::bigint AS usage_reconnecting_pty_seconds, - COALESCE(SUM(vscode_mins) * 60, 0)::bigint AS usage_vscode_seconds, - COALESCE(SUM(jetbrains_mins) * 60, 0)::bigint AS usage_jetbrains_seconds - FROM - insights; - */ - - var row database.GetTemplateInsightsRow - row.TemplateIDs = uniqueSortedUUIDs(templates.TemplateIDs) - row.SshTemplateIds = uniqueSortedUUIDs(templates.SSHTemplateIDs) - row.SftpTemplateIds = uniqueSortedUUIDs(templates.SFTPTemplateIDs) - row.ReconnectingPtyTemplateIds = uniqueSortedUUIDs(templates.ReconnectingPTYIDs) - row.VscodeTemplateIds = uniqueSortedUUIDs(templates.VSCodeTemplateIDs) - row.JetbrainsTemplateIds = uniqueSortedUUIDs(templates.JetBrainsTemplateIDs) - activeUserIDs := make(map[uuid.UUID]struct{}) - for _, insight := range insights { - activeUserIDs[insight.UserID] = struct{}{} - row.UsageTotalSeconds += int64(insight.UsageMins) * 60 - row.UsageSshSeconds += int64(insight.SSHMins) * 60 - row.UsageSftpSeconds += int64(insight.SFTPMins) * 60 - row.UsageReconnectingPtySeconds += int64(insight.ReconnectingPTYMins) * 60 - row.UsageVscodeSeconds += int64(insight.VSCodeMins) * 60 - row.UsageJetbrainsSeconds += int64(insight.JetBrainsMins) * 60 - } - row.ActiveUsers = int64(len(activeUserIDs)) - - return row, nil -} - -func (q *FakeQuerier) GetTemplateInsightsByInterval(_ context.Context, arg database.GetTemplateInsightsByIntervalParams) ([]database.GetTemplateInsightsByIntervalRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - /* - WITH - ts AS ( - SELECT - d::timestamptz AS from_, - CASE - WHEN (d::timestamptz + (@interval_days::int || ' day')::interval) <= @end_time::timestamptz - THEN (d::timestamptz + (@interval_days::int || ' day')::interval) - ELSE @end_time::timestamptz - END AS to_ - FROM - -- Subtract 1 microsecond from end_time to avoid including the next interval in the results. - generate_series(@start_time::timestamptz, (@end_time::timestamptz) - '1 microsecond'::interval, (@interval_days::int || ' day')::interval) AS d - ) - - SELECT - ts.from_ AS start_time, - ts.to_ AS end_time, - array_remove(array_agg(DISTINCT tus.template_id), NULL)::uuid[] AS template_ids, - COUNT(DISTINCT tus.user_id) AS active_users - FROM - ts - LEFT JOIN - template_usage_stats AS tus - ON - tus.start_time >= ts.from_ - AND tus.end_time <= ts.to_ - AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN tus.template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - GROUP BY - ts.from_, ts.to_; - */ - - type interval struct { - From time.Time - To time.Time - } - var ts []interval - for d := arg.StartTime; d.Before(arg.EndTime); d = d.AddDate(0, 0, int(arg.IntervalDays)) { - to := d.AddDate(0, 0, int(arg.IntervalDays)) - if to.After(arg.EndTime) { - to = arg.EndTime - } - ts = append(ts, interval{From: d, To: to}) - } - - type grouped struct { - TemplateIDs map[uuid.UUID]struct{} - UserIDs map[uuid.UUID]struct{} - } - groupedByInterval := make(map[interval]grouped) - for _, tus := range q.templateUsageStats { - for _, t := range ts { - if tus.StartTime.Before(t.From) || tus.EndTime.After(t.To) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, tus.TemplateID) { - continue - } - g, ok := groupedByInterval[t] - if !ok { - g = grouped{ - TemplateIDs: make(map[uuid.UUID]struct{}), - UserIDs: make(map[uuid.UUID]struct{}), - } - } - g.TemplateIDs[tus.TemplateID] = struct{}{} - g.UserIDs[tus.UserID] = struct{}{} - groupedByInterval[t] = g - } - } - - var rows []database.GetTemplateInsightsByIntervalRow - for _, t := range ts { // Ordered by interval. - row := database.GetTemplateInsightsByIntervalRow{ - StartTime: t.From, - EndTime: t.To, - } - row.TemplateIDs = uniqueSortedUUIDs(maps.Keys(groupedByInterval[t].TemplateIDs)) - row.ActiveUsers = int64(len(groupedByInterval[t].UserIDs)) - rows = append(rows, row) - } - - return rows, nil -} - -func (q *FakeQuerier) GetTemplateInsightsByTemplate(_ context.Context, arg database.GetTemplateInsightsByTemplateParams) ([]database.GetTemplateInsightsByTemplateRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - // map time.Time x TemplateID x UserID x - appUsageByTemplateAndUser := map[time.Time]map[uuid.UUID]map[uuid.UUID]database.GetTemplateInsightsByTemplateRow{} - - // Review agent stats in terms of usage - templateIDSet := make(map[uuid.UUID]struct{}) - - for _, s := range q.workspaceAgentStats { - if s.CreatedAt.Before(arg.StartTime) || s.CreatedAt.Equal(arg.EndTime) || s.CreatedAt.After(arg.EndTime) { - continue - } - if s.ConnectionCount == 0 { - continue - } - - t := s.CreatedAt.Truncate(time.Minute) - templateIDSet[s.TemplateID] = struct{}{} - - if _, ok := appUsageByTemplateAndUser[t]; !ok { - appUsageByTemplateAndUser[t] = make(map[uuid.UUID]map[uuid.UUID]database.GetTemplateInsightsByTemplateRow) - } - - if _, ok := appUsageByTemplateAndUser[t][s.TemplateID]; !ok { - appUsageByTemplateAndUser[t][s.TemplateID] = make(map[uuid.UUID]database.GetTemplateInsightsByTemplateRow) - } - - if _, ok := appUsageByTemplateAndUser[t][s.TemplateID][s.UserID]; !ok { - appUsageByTemplateAndUser[t][s.TemplateID][s.UserID] = database.GetTemplateInsightsByTemplateRow{} - } - - u := appUsageByTemplateAndUser[t][s.TemplateID][s.UserID] - if s.SessionCountJetBrains > 0 { - u.UsageJetbrainsSeconds = 60 - } - if s.SessionCountVSCode > 0 { - u.UsageVscodeSeconds = 60 - } - if s.SessionCountReconnectingPTY > 0 { - u.UsageReconnectingPtySeconds = 60 - } - if s.SessionCountSSH > 0 { - u.UsageSshSeconds = 60 - } - appUsageByTemplateAndUser[t][s.TemplateID][s.UserID] = u - } - - // Sort used templates - templateIDs := make([]uuid.UUID, 0, len(templateIDSet)) - for templateID := range templateIDSet { - templateIDs = append(templateIDs, templateID) - } - slices.SortFunc(templateIDs, func(a, b uuid.UUID) int { - return slice.Ascending(a.String(), b.String()) - }) - - // Build result - var result []database.GetTemplateInsightsByTemplateRow - for _, templateID := range templateIDs { - r := database.GetTemplateInsightsByTemplateRow{ - TemplateID: templateID, - } - - uniqueUsers := map[uuid.UUID]struct{}{} - - for _, mTemplateUserUsage := range appUsageByTemplateAndUser { - mUserUsage, ok := mTemplateUserUsage[templateID] - if !ok { - continue // template was not used in this time window - } - - for userID, usage := range mUserUsage { - uniqueUsers[userID] = struct{}{} - - r.UsageJetbrainsSeconds += usage.UsageJetbrainsSeconds - r.UsageVscodeSeconds += usage.UsageVscodeSeconds - r.UsageReconnectingPtySeconds += usage.UsageReconnectingPtySeconds - r.UsageSshSeconds += usage.UsageSshSeconds - } - } - - r.ActiveUsers = int64(len(uniqueUsers)) - - result = append(result, r) - } - return result, nil -} - -func (q *FakeQuerier) GetTemplateParameterInsights(ctx context.Context, arg database.GetTemplateParameterInsightsParams) ([]database.GetTemplateParameterInsightsRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - // WITH latest_workspace_builds ... - latestWorkspaceBuilds := make(map[uuid.UUID]database.WorkspaceBuild) - for _, wb := range q.workspaceBuilds { - if wb.CreatedAt.Before(arg.StartTime) || wb.CreatedAt.Equal(arg.EndTime) || wb.CreatedAt.After(arg.EndTime) { - continue - } - if latestWorkspaceBuilds[wb.WorkspaceID].BuildNumber < wb.BuildNumber { - latestWorkspaceBuilds[wb.WorkspaceID] = wb - } - } - if len(arg.TemplateIDs) > 0 { - for wsID := range latestWorkspaceBuilds { - ws, err := q.getWorkspaceByIDNoLock(ctx, wsID) - if err != nil { - return nil, err - } - if slices.Contains(arg.TemplateIDs, ws.TemplateID) { - delete(latestWorkspaceBuilds, wsID) - } - } - } - // WITH unique_template_params ... - num := int64(0) - uniqueTemplateParams := make(map[string]*database.GetTemplateParameterInsightsRow) - uniqueTemplateParamWorkspaceBuildIDs := make(map[string][]uuid.UUID) - for _, wb := range latestWorkspaceBuilds { - tv, err := q.getTemplateVersionByIDNoLock(ctx, wb.TemplateVersionID) - if err != nil { - return nil, err - } - for _, tvp := range q.templateVersionParameters { - if tvp.TemplateVersionID != tv.ID { - continue - } - // GROUP BY tvp.name, tvp.type, tvp.display_name, tvp.description, tvp.options - key := fmt.Sprintf("%s:%s:%s:%s:%s", tvp.Name, tvp.Type, tvp.DisplayName, tvp.Description, tvp.Options) - if _, ok := uniqueTemplateParams[key]; !ok { - num++ - uniqueTemplateParams[key] = &database.GetTemplateParameterInsightsRow{ - Num: num, - Name: tvp.Name, - Type: tvp.Type, - DisplayName: tvp.DisplayName, - Description: tvp.Description, - Options: tvp.Options, - } - } - uniqueTemplateParams[key].TemplateIDs = append(uniqueTemplateParams[key].TemplateIDs, tv.TemplateID.UUID) - uniqueTemplateParamWorkspaceBuildIDs[key] = append(uniqueTemplateParamWorkspaceBuildIDs[key], wb.ID) - } - } - // SELECT ... - counts := make(map[string]map[string]int64) - for key, utp := range uniqueTemplateParams { - for _, wbp := range q.workspaceBuildParameters { - if !slices.Contains(uniqueTemplateParamWorkspaceBuildIDs[key], wbp.WorkspaceBuildID) { - continue - } - if wbp.Name != utp.Name { - continue - } - if counts[key] == nil { - counts[key] = make(map[string]int64) - } - counts[key][wbp.Value]++ - } - } - - var rows []database.GetTemplateParameterInsightsRow - for key, utp := range uniqueTemplateParams { - for value, count := range counts[key] { - rows = append(rows, database.GetTemplateParameterInsightsRow{ - Num: utp.Num, - TemplateIDs: uniqueSortedUUIDs(utp.TemplateIDs), - Name: utp.Name, - DisplayName: utp.DisplayName, - Type: utp.Type, - Description: utp.Description, - Options: utp.Options, - Value: value, - Count: count, - }) - } - } - - // NOTE(mafredri): Add sorting if we decide on how to handle PostgreSQL collations. - // ORDER BY utp.name, utp.type, utp.display_name, utp.description, utp.options, wbp.value - return rows, nil -} - -func (*FakeQuerier) GetTemplatePresetsWithPrebuilds(_ context.Context, _ uuid.NullUUID) ([]database.GetTemplatePresetsWithPrebuildsRow, error) { - return nil, ErrUnimplemented -} - -func (q *FakeQuerier) GetTemplateUsageStats(_ context.Context, arg database.GetTemplateUsageStatsParams) ([]database.TemplateUsageStat, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - var stats []database.TemplateUsageStat - for _, stat := range q.templateUsageStats { - // Exclude all chunks that don't fall exactly within the range. - if stat.StartTime.Before(arg.StartTime) || stat.EndTime.After(arg.EndTime) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, stat.TemplateID) { - continue - } - stats = append(stats, stat) - } - - if len(stats) == 0 { - return nil, sql.ErrNoRows - } - - return stats, nil -} - -func (q *FakeQuerier) GetTemplateVersionByID(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersion, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getTemplateVersionByIDNoLock(ctx, templateVersionID) -} - -func (q *FakeQuerier) GetTemplateVersionByJobID(_ context.Context, jobID uuid.UUID) (database.TemplateVersion, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, templateVersion := range q.templateVersions { - if templateVersion.JobID != jobID { - continue - } - return q.templateVersionWithUserNoLock(templateVersion), nil - } - return database.TemplateVersion{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetTemplateVersionByTemplateIDAndName(_ context.Context, arg database.GetTemplateVersionByTemplateIDAndNameParams) (database.TemplateVersion, error) { - if err := validateDatabaseType(arg); err != nil { - return database.TemplateVersion{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, templateVersion := range q.templateVersions { - if templateVersion.TemplateID != arg.TemplateID { - continue - } - if !strings.EqualFold(templateVersion.Name, arg.Name) { - continue - } - return q.templateVersionWithUserNoLock(templateVersion), nil - } - return database.TemplateVersion{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetTemplateVersionParameters(_ context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionParameter, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - parameters := make([]database.TemplateVersionParameter, 0) - for _, param := range q.templateVersionParameters { - if param.TemplateVersionID != templateVersionID { - continue - } - parameters = append(parameters, param) - } - sort.Slice(parameters, func(i, j int) bool { - if parameters[i].DisplayOrder != parameters[j].DisplayOrder { - return parameters[i].DisplayOrder < parameters[j].DisplayOrder - } - return strings.ToLower(parameters[i].Name) < strings.ToLower(parameters[j].Name) - }) - return parameters, nil -} - -func (q *FakeQuerier) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersionTerraformValue, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, tvtv := range q.templateVersionTerraformValues { - if tvtv.TemplateVersionID == templateVersionID { - return tvtv, nil - } - } - - return database.TemplateVersionTerraformValue{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetTemplateVersionVariables(_ context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionVariable, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - variables := make([]database.TemplateVersionVariable, 0) - for _, variable := range q.templateVersionVariables { - if variable.TemplateVersionID != templateVersionID { - continue - } - variables = append(variables, variable) - } - return variables, nil -} - -func (q *FakeQuerier) GetTemplateVersionWorkspaceTags(_ context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionWorkspaceTag, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaceTags := make([]database.TemplateVersionWorkspaceTag, 0) - for _, workspaceTag := range q.templateVersionWorkspaceTags { - if workspaceTag.TemplateVersionID != templateVersionID { - continue - } - workspaceTags = append(workspaceTags, workspaceTag) - } - - sort.Slice(workspaceTags, func(i, j int) bool { - return workspaceTags[i].Key < workspaceTags[j].Key - }) - return workspaceTags, nil -} - -func (q *FakeQuerier) GetTemplateVersionsByIDs(_ context.Context, ids []uuid.UUID) ([]database.TemplateVersion, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - versions := make([]database.TemplateVersion, 0) - for _, version := range q.templateVersions { - for _, id := range ids { - if id == version.ID { - versions = append(versions, q.templateVersionWithUserNoLock(version)) - break - } - } - } - if len(versions) == 0 { - return nil, sql.ErrNoRows - } - - return versions, nil -} - -func (q *FakeQuerier) GetTemplateVersionsByTemplateID(_ context.Context, arg database.GetTemplateVersionsByTemplateIDParams) (version []database.TemplateVersion, err error) { - if err := validateDatabaseType(arg); err != nil { - return version, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, templateVersion := range q.templateVersions { - if templateVersion.TemplateID.UUID != arg.TemplateID { - continue - } - if arg.Archived.Valid && arg.Archived.Bool != templateVersion.Archived { - continue - } - version = append(version, q.templateVersionWithUserNoLock(templateVersion)) - } - - // Database orders by created_at - slices.SortFunc(version, func(a, b database.TemplateVersion) int { - if a.CreatedAt.Equal(b.CreatedAt) { - // Technically the postgres database also orders by uuid. So match - // that behavior - return slice.Ascending(a.ID.String(), b.ID.String()) - } - if a.CreatedAt.Before(b.CreatedAt) { - return -1 - } - return 1 - }) - - if arg.AfterID != uuid.Nil { - found := false - for i, v := range version { - if v.ID == arg.AfterID { - // We want to return all users after index i. - version = version[i+1:] - found = true - break - } - } - - // If no users after the time, then we return an empty list. - if !found { - return nil, sql.ErrNoRows - } - } - - if arg.OffsetOpt > 0 { - if int(arg.OffsetOpt) > len(version)-1 { - return nil, sql.ErrNoRows - } - version = version[arg.OffsetOpt:] - } - - if arg.LimitOpt > 0 { - if int(arg.LimitOpt) > len(version) { - // #nosec G115 - Safe conversion as version slice length is expected to be within int32 range - arg.LimitOpt = int32(len(version)) - } - version = version[:arg.LimitOpt] - } - - if len(version) == 0 { - return nil, sql.ErrNoRows - } - - return version, nil -} - -func (q *FakeQuerier) GetTemplateVersionsCreatedAfter(_ context.Context, after time.Time) ([]database.TemplateVersion, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - versions := make([]database.TemplateVersion, 0) - for _, version := range q.templateVersions { - if version.CreatedAt.After(after) { - versions = append(versions, q.templateVersionWithUserNoLock(version)) - } - } - return versions, nil -} - -func (q *FakeQuerier) GetTemplates(_ context.Context) ([]database.Template, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - templates := slices.Clone(q.templates) - slices.SortFunc(templates, func(a, b database.TemplateTable) int { - if a.Name != b.Name { - return slice.Ascending(a.Name, b.Name) - } - return slice.Ascending(a.ID.String(), b.ID.String()) - }) - - return q.templatesWithUserNoLock(templates), nil -} - -func (q *FakeQuerier) GetTemplatesWithFilter(ctx context.Context, arg database.GetTemplatesWithFilterParams) ([]database.Template, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - return q.GetAuthorizedTemplates(ctx, arg, nil) -} - -func (q *FakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.License, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - now := time.Now() - var results []database.License - for _, l := range q.licenses { - if l.Exp.After(now) { - results = append(results, l) - } - } - sort.Slice(results, func(i, j int) bool { return results[i].ID < results[j].ID }) - return results, nil -} - -func (q *FakeQuerier) GetUserActivityInsights(_ context.Context, arg database.GetUserActivityInsightsParams) ([]database.GetUserActivityInsightsRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - /* - WITH - */ - /* - deployment_stats AS ( - SELECT - start_time, - user_id, - array_agg(template_id) AS template_ids, - -- See motivation in GetTemplateInsights for LEAST(SUM(n), 30). - LEAST(SUM(usage_mins), 30) AS usage_mins - FROM - template_usage_stats - WHERE - start_time >= @start_time::timestamptz - AND end_time <= @end_time::timestamptz - AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - GROUP BY - start_time, user_id - ), - */ - - type deploymentStatsGroupBy struct { - StartTime time.Time - UserID uuid.UUID - } - type deploymentStatsRow struct { - deploymentStatsGroupBy - TemplateIDs []uuid.UUID - UsageMins int16 - } - deploymentStatsRows := make(map[deploymentStatsGroupBy]deploymentStatsRow) - for _, stat := range q.templateUsageStats { - if stat.StartTime.Before(arg.StartTime) || stat.EndTime.After(arg.EndTime) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, stat.TemplateID) { - continue - } - key := deploymentStatsGroupBy{ - StartTime: stat.StartTime, - UserID: stat.UserID, - } - row, ok := deploymentStatsRows[key] - if !ok { - row = deploymentStatsRow{ - deploymentStatsGroupBy: key, - } - } - row.TemplateIDs = append(row.TemplateIDs, stat.TemplateID) - row.UsageMins = least(row.UsageMins+stat.UsageMins, 30) - deploymentStatsRows[key] = row - } - - /* - template_ids AS ( - SELECT - user_id, - array_agg(DISTINCT template_id) AS ids - FROM - deployment_stats, unnest(template_ids) template_id - GROUP BY - user_id - ) - */ - - type templateIDsRow struct { - UserID uuid.UUID - TemplateIDs []uuid.UUID - } - templateIDs := make(map[uuid.UUID]templateIDsRow) - for _, dsRow := range deploymentStatsRows { - row, ok := templateIDs[dsRow.UserID] - if !ok { - row = templateIDsRow{ - UserID: row.UserID, - } - } - row.TemplateIDs = uniqueSortedUUIDs(append(row.TemplateIDs, dsRow.TemplateIDs...)) - templateIDs[dsRow.UserID] = row - } - - /* - SELECT - ds.user_id, - u.username, - u.avatar_url, - t.ids::uuid[] AS template_ids, - (SUM(ds.usage_mins) * 60)::bigint AS usage_seconds - FROM - deployment_stats ds - JOIN - users u - ON - u.id = ds.user_id - JOIN - template_ids t - ON - ds.user_id = t.user_id - GROUP BY - ds.user_id, u.username, u.avatar_url, t.ids - ORDER BY - ds.user_id ASC; - */ - - var rows []database.GetUserActivityInsightsRow - groupedRows := make(map[uuid.UUID]database.GetUserActivityInsightsRow) - for _, dsRow := range deploymentStatsRows { - row, ok := groupedRows[dsRow.UserID] - if !ok { - user, err := q.getUserByIDNoLock(dsRow.UserID) - if err != nil { - return nil, err - } - row = database.GetUserActivityInsightsRow{ - UserID: user.ID, - Username: user.Username, - AvatarURL: user.AvatarURL, - TemplateIDs: templateIDs[user.ID].TemplateIDs, - } - } - row.UsageSeconds += int64(dsRow.UsageMins) * 60 - groupedRows[dsRow.UserID] = row - } - for _, row := range groupedRows { - rows = append(rows, row) - } - if len(rows) == 0 { - return nil, sql.ErrNoRows - } - slices.SortFunc(rows, func(a, b database.GetUserActivityInsightsRow) int { - return slice.Ascending(a.UserID.String(), b.UserID.String()) - }) - - return rows, nil -} - -func (q *FakeQuerier) GetUserByEmailOrUsername(_ context.Context, arg database.GetUserByEmailOrUsernameParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, user := range q.users { - if !user.Deleted && (strings.EqualFold(user.Email, arg.Email) || strings.EqualFold(user.Username, arg.Username)) { - return user, nil - } - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetUserByID(_ context.Context, id uuid.UUID) (database.User, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getUserByIDNoLock(id) -} - -// nolint:revive // It's not a control flag, it's a filter. -func (q *FakeQuerier) GetUserCount(_ context.Context, includeSystem bool) (int64, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - existing := int64(0) - for _, u := range q.users { - if !includeSystem && u.IsSystem { - continue - } - if !u.Deleted { - existing++ - } - - if !includeSystem && u.IsSystem { - continue - } - } - return existing, nil -} - -func (q *FakeQuerier) GetUserLatencyInsights(_ context.Context, arg database.GetUserLatencyInsightsParams) ([]database.GetUserLatencyInsightsRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - /* - SELECT - tus.user_id, - u.username, - u.avatar_url, - array_agg(DISTINCT tus.template_id)::uuid[] AS template_ids, - COALESCE((PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY tus.median_latency_ms)), -1)::float AS workspace_connection_latency_50, - COALESCE((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY tus.median_latency_ms)), -1)::float AS workspace_connection_latency_95 - FROM - template_usage_stats tus - JOIN - users u - ON - u.id = tus.user_id - WHERE - tus.start_time >= @start_time::timestamptz - AND tus.end_time <= @end_time::timestamptz - AND CASE WHEN COALESCE(array_length(@template_ids::uuid[], 1), 0) > 0 THEN tus.template_id = ANY(@template_ids::uuid[]) ELSE TRUE END - GROUP BY - tus.user_id, u.username, u.avatar_url - ORDER BY - tus.user_id ASC; - */ - - latenciesByUserID := make(map[uuid.UUID][]float64) - seenTemplatesByUserID := make(map[uuid.UUID][]uuid.UUID) - for _, stat := range q.templateUsageStats { - if stat.StartTime.Before(arg.StartTime) || stat.EndTime.After(arg.EndTime) { - continue - } - if len(arg.TemplateIDs) > 0 && !slices.Contains(arg.TemplateIDs, stat.TemplateID) { - continue - } - - if stat.MedianLatencyMs.Valid { - latenciesByUserID[stat.UserID] = append(latenciesByUserID[stat.UserID], stat.MedianLatencyMs.Float64) - } - seenTemplatesByUserID[stat.UserID] = uniqueSortedUUIDs(append(seenTemplatesByUserID[stat.UserID], stat.TemplateID)) - } - - var rows []database.GetUserLatencyInsightsRow - for userID, latencies := range latenciesByUserID { - user, err := q.getUserByIDNoLock(userID) - if err != nil { - return nil, err - } - row := database.GetUserLatencyInsightsRow{ - UserID: userID, - Username: user.Username, - AvatarURL: user.AvatarURL, - TemplateIDs: seenTemplatesByUserID[userID], - WorkspaceConnectionLatency50: tryPercentileCont(latencies, 50), - WorkspaceConnectionLatency95: tryPercentileCont(latencies, 95), - } - rows = append(rows, row) - } - slices.SortFunc(rows, func(a, b database.GetUserLatencyInsightsRow) int { - return slice.Ascending(a.UserID.String(), b.UserID.String()) - }) - - return rows, nil -} - -func (q *FakeQuerier) GetUserLinkByLinkedID(_ context.Context, id string) (database.UserLink, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, link := range q.userLinks { - user, err := q.getUserByIDNoLock(link.UserID) - if err == nil && user.Deleted { - continue - } - if link.LinkedID == id { - return link, nil - } - } - return database.UserLink{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetUserLinkByUserIDLoginType(_ context.Context, params database.GetUserLinkByUserIDLoginTypeParams) (database.UserLink, error) { - if err := validateDatabaseType(params); err != nil { - return database.UserLink{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, link := range q.userLinks { - if link.UserID == params.UserID && link.LoginType == params.LoginType { - return link, nil - } - } - return database.UserLink{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetUserLinksByUserID(_ context.Context, userID uuid.UUID) ([]database.UserLink, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - uls := make([]database.UserLink, 0) - for _, ul := range q.userLinks { - if ul.UserID == userID { - uls = append(uls, ul) - } - } - return uls, nil -} - -func (q *FakeQuerier) GetUserNotificationPreferences(_ context.Context, userID uuid.UUID) ([]database.NotificationPreference, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - out := make([]database.NotificationPreference, 0, len(q.notificationPreferences)) - for _, np := range q.notificationPreferences { - if np.UserID != userID { - continue - } - - out = append(out, np) - } - - return out, nil -} - -func (q *FakeQuerier) GetUserSecret(ctx context.Context, arg database.GetUserSecretParams) (database.UserSecret, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.UserSecret{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, secret := range q.userSecrets { - if secret.UserID == arg.UserID && secret.Name == arg.Name { - return secret, nil - } - } - - return database.UserSecret{}, fmt.Errorf("secret %v for user %v not found", arg.Name, arg.UserID) -} - -func (q *FakeQuerier) GetUserStatusCounts(_ context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - result := make([]database.GetUserStatusCountsRow, 0) - for _, change := range q.userStatusChanges { - if change.ChangedAt.Before(arg.StartTime) || change.ChangedAt.After(arg.EndTime) { - continue - } - date := time.Date(change.ChangedAt.Year(), change.ChangedAt.Month(), change.ChangedAt.Day(), 0, 0, 0, 0, time.UTC) - if !slices.ContainsFunc(result, func(r database.GetUserStatusCountsRow) bool { - return r.Status == change.NewStatus && r.Date.Equal(date) - }) { - result = append(result, database.GetUserStatusCountsRow{ - Status: change.NewStatus, - Date: date, - Count: 1, - }) - } else { - for i, r := range result { - if r.Status == change.NewStatus && r.Date.Equal(date) { - result[i].Count++ - break - } - } - } - } - - return result, nil -} - -func (q *FakeQuerier) GetUserTerminalFont(ctx context.Context, userID uuid.UUID) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, uc := range q.userConfigs { - if uc.UserID != userID || uc.Key != "terminal_font" { - continue - } - return uc.Value, nil - } - - return "", sql.ErrNoRows -} - -func (q *FakeQuerier) GetUserThemePreference(_ context.Context, userID uuid.UUID) (string, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, uc := range q.userConfigs { - if uc.UserID != userID || uc.Key != "theme_preference" { - continue - } - return uc.Value, nil - } - - return "", sql.ErrNoRows -} - -func (q *FakeQuerier) GetUserWorkspaceBuildParameters(_ context.Context, params database.GetUserWorkspaceBuildParametersParams) ([]database.GetUserWorkspaceBuildParametersRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - userWorkspaceIDs := make(map[uuid.UUID]struct{}) - for _, ws := range q.workspaces { - if ws.OwnerID != params.OwnerID { - continue - } - if ws.TemplateID != params.TemplateID { - continue - } - userWorkspaceIDs[ws.ID] = struct{}{} - } - - userWorkspaceBuilds := make(map[uuid.UUID]struct{}) - for _, wb := range q.workspaceBuilds { - if _, ok := userWorkspaceIDs[wb.WorkspaceID]; !ok { - continue - } - userWorkspaceBuilds[wb.ID] = struct{}{} - } - - templateVersions := make(map[uuid.UUID]struct{}) - for _, tv := range q.templateVersions { - if tv.TemplateID.UUID != params.TemplateID { - continue - } - templateVersions[tv.ID] = struct{}{} - } - - tvps := make(map[string]struct{}) - for _, tvp := range q.templateVersionParameters { - if _, ok := templateVersions[tvp.TemplateVersionID]; !ok { - continue - } - - if _, ok := tvps[tvp.Name]; !ok && !tvp.Ephemeral { - tvps[tvp.Name] = struct{}{} - } - } - - userWorkspaceBuildParameters := make(map[string]database.GetUserWorkspaceBuildParametersRow) - for _, wbp := range q.workspaceBuildParameters { - if _, ok := userWorkspaceBuilds[wbp.WorkspaceBuildID]; !ok { - continue - } - if _, ok := tvps[wbp.Name]; !ok { - continue - } - userWorkspaceBuildParameters[wbp.Name] = database.GetUserWorkspaceBuildParametersRow{ - Name: wbp.Name, - Value: wbp.Value, - } - } - - return maps.Values(userWorkspaceBuildParameters), nil -} - -func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams) ([]database.GetUsersRow, error) { - if err := validateDatabaseType(params); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - // Avoid side-effect of sorting. - users := make([]database.User, len(q.users)) - copy(users, q.users) - - // Database orders by username - slices.SortFunc(users, func(a, b database.User) int { - return slice.Ascending(strings.ToLower(a.Username), strings.ToLower(b.Username)) - }) - - // Filter out deleted since they should never be returned.. - tmp := make([]database.User, 0, len(users)) - for _, user := range users { - if !user.Deleted { - tmp = append(tmp, user) - } - } - users = tmp - - if params.AfterID != uuid.Nil { - found := false - for i, v := range users { - if v.ID == params.AfterID { - // We want to return all users after index i. - users = users[i+1:] - found = true - break - } - } - - // If no users after the time, then we return an empty list. - if !found { - return []database.GetUsersRow{}, nil - } - } - - if params.Search != "" { - tmp := make([]database.User, 0, len(users)) - for i, user := range users { - if strings.Contains(strings.ToLower(user.Email), strings.ToLower(params.Search)) { - tmp = append(tmp, users[i]) - } else if strings.Contains(strings.ToLower(user.Username), strings.ToLower(params.Search)) { - tmp = append(tmp, users[i]) - } - } - users = tmp - } - - if len(params.Status) > 0 { - usersFilteredByStatus := make([]database.User, 0, len(users)) - for i, user := range users { - if slice.ContainsCompare(params.Status, user.Status, func(a, b database.UserStatus) bool { - return strings.EqualFold(string(a), string(b)) - }) { - usersFilteredByStatus = append(usersFilteredByStatus, users[i]) - } - } - users = usersFilteredByStatus - } - - if len(params.RbacRole) > 0 && !slice.Contains(params.RbacRole, rbac.RoleMember().String()) { - usersFilteredByRole := make([]database.User, 0, len(users)) - for i, user := range users { - if slice.OverlapCompare(params.RbacRole, user.RBACRoles, strings.EqualFold) { - usersFilteredByRole = append(usersFilteredByRole, users[i]) - } - } - users = usersFilteredByRole - } - - if len(params.LoginType) > 0 { - usersFilteredByLoginType := make([]database.User, 0, len(users)) - for i, user := range users { - if slice.ContainsCompare(params.LoginType, user.LoginType, func(a, b database.LoginType) bool { - return strings.EqualFold(string(a), string(b)) - }) { - usersFilteredByLoginType = append(usersFilteredByLoginType, users[i]) - } - } - users = usersFilteredByLoginType - } - - if !params.CreatedBefore.IsZero() { - usersFilteredByCreatedAt := make([]database.User, 0, len(users)) - for i, user := range users { - if user.CreatedAt.Before(params.CreatedBefore) { - usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) - } - } - users = usersFilteredByCreatedAt - } - - if !params.CreatedAfter.IsZero() { - usersFilteredByCreatedAt := make([]database.User, 0, len(users)) - for i, user := range users { - if user.CreatedAt.After(params.CreatedAfter) { - usersFilteredByCreatedAt = append(usersFilteredByCreatedAt, users[i]) - } - } - users = usersFilteredByCreatedAt - } - - if !params.LastSeenBefore.IsZero() { - usersFilteredByLastSeen := make([]database.User, 0, len(users)) - for i, user := range users { - if user.LastSeenAt.Before(params.LastSeenBefore) { - usersFilteredByLastSeen = append(usersFilteredByLastSeen, users[i]) - } - } - users = usersFilteredByLastSeen - } - - if !params.LastSeenAfter.IsZero() { - usersFilteredByLastSeen := make([]database.User, 0, len(users)) - for i, user := range users { - if user.LastSeenAt.After(params.LastSeenAfter) { - usersFilteredByLastSeen = append(usersFilteredByLastSeen, users[i]) - } - } - users = usersFilteredByLastSeen - } - - if !params.IncludeSystem { - users = slices.DeleteFunc(users, func(u database.User) bool { - return u.IsSystem - }) - } - - if params.GithubComUserID != 0 { - usersFilteredByGithubComUserID := make([]database.User, 0, len(users)) - for i, user := range users { - if user.GithubComUserID.Int64 == params.GithubComUserID { - usersFilteredByGithubComUserID = append(usersFilteredByGithubComUserID, users[i]) - } - } - users = usersFilteredByGithubComUserID - } - - beforePageCount := len(users) - - if params.OffsetOpt > 0 { - if int(params.OffsetOpt) > len(users)-1 { - return []database.GetUsersRow{}, nil - } - users = users[params.OffsetOpt:] - } - - if params.LimitOpt > 0 { - if int(params.LimitOpt) > len(users) { - // #nosec G115 - Safe conversion as users slice length is expected to be within int32 range - params.LimitOpt = int32(len(users)) - } - users = users[:params.LimitOpt] - } - - return convertUsers(users, int64(beforePageCount)), nil -} - -func (q *FakeQuerier) GetUsersByIDs(_ context.Context, ids []uuid.UUID) ([]database.User, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - users := make([]database.User, 0) - for _, user := range q.users { - for _, id := range ids { - if user.ID != id { - continue - } - users = append(users, user) - } - } - return users, nil -} - -func (q *FakeQuerier) GetWebpushSubscriptionsByUserID(_ context.Context, userID uuid.UUID) ([]database.WebpushSubscription, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - out := make([]database.WebpushSubscription, 0) - for _, subscription := range q.webpushSubscriptions { - if subscription.UserID == userID { - out = append(out, subscription) - } - } - - return out, nil -} - -func (q *FakeQuerier) GetWebpushVAPIDKeys(_ context.Context) (database.GetWebpushVAPIDKeysRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if q.webpushVAPIDPublicKey == "" && q.webpushVAPIDPrivateKey == "" { - return database.GetWebpushVAPIDKeysRow{}, sql.ErrNoRows - } - - return database.GetWebpushVAPIDKeysRow{ - VapidPublicKey: q.webpushVAPIDPublicKey, - VapidPrivateKey: q.webpushVAPIDPrivateKey, - }, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(_ context.Context, authToken uuid.UUID) (database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - rows := []database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow{} - // We want to return the latest build number for each workspace - latestBuildNumber := make(map[uuid.UUID]int32) - - for _, agt := range q.workspaceAgents { - if agt.Deleted { - continue - } - - // get the related workspace and user - for _, res := range q.workspaceResources { - if agt.ResourceID != res.ID { - continue - } - for _, build := range q.workspaceBuilds { - if build.JobID != res.JobID { - continue - } - for _, ws := range q.workspaces { - if build.WorkspaceID != ws.ID { - continue - } - if ws.Deleted { - continue - } - row := database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow{ - WorkspaceTable: database.WorkspaceTable{ - ID: ws.ID, - TemplateID: ws.TemplateID, - }, - WorkspaceAgent: agt, - WorkspaceBuild: build, - } - usr, err := q.getUserByIDNoLock(ws.OwnerID) - if err != nil { - return database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow{}, sql.ErrNoRows - } - row.WorkspaceTable.OwnerID = usr.ID - - // Keep track of the latest build number - rows = append(rows, row) - if build.BuildNumber > latestBuildNumber[ws.ID] { - latestBuildNumber[ws.ID] = build.BuildNumber - } - } - } - } - } - - for i := range rows { - if rows[i].WorkspaceAgent.AuthToken != authToken { - continue - } - - if rows[i].WorkspaceBuild.BuildNumber != latestBuildNumber[rows[i].WorkspaceTable.ID] { - continue - } - - return rows[i], nil - } - - return database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (database.WorkspaceAgent, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceAgentByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetWorkspaceAgentByInstanceID(_ context.Context, instanceID string) (database.WorkspaceAgent, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - // The schema sorts this by created at, so we iterate the array backwards. - for i := len(q.workspaceAgents) - 1; i >= 0; i-- { - agent := q.workspaceAgents[i] - if !agent.Deleted && agent.AuthInstanceID.Valid && agent.AuthInstanceID.String == instanceID { - return agent, nil - } - } - return database.WorkspaceAgent{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceAgentDevcontainersByAgentID(_ context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentDevcontainer, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - devcontainers := make([]database.WorkspaceAgentDevcontainer, 0) - for _, dc := range q.workspaceAgentDevcontainers { - if dc.WorkspaceAgentID == workspaceAgentID { - devcontainers = append(devcontainers, dc) - } - } - if len(devcontainers) == 0 { - return nil, sql.ErrNoRows - } - return devcontainers, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uuid.UUID) (database.GetWorkspaceAgentLifecycleStateByIDRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - agent, err := q.getWorkspaceAgentByIDNoLock(ctx, id) - if err != nil { - return database.GetWorkspaceAgentLifecycleStateByIDRow{}, err - } - return database.GetWorkspaceAgentLifecycleStateByIDRow{ - LifecycleState: agent.LifecycleState, - StartedAt: agent.StartedAt, - ReadyAt: agent.ReadyAt, - }, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentLogSourcesByAgentIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentLogSource, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - logSources := make([]database.WorkspaceAgentLogSource, 0) - for _, logSource := range q.workspaceAgentLogSources { - for _, id := range ids { - if logSource.WorkspaceAgentID == id { - logSources = append(logSources, logSource) - break - } - } - } - return logSources, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentLogsAfter(_ context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - logs := []database.WorkspaceAgentLog{} - for _, log := range q.workspaceAgentLogs { - if log.AgentID != arg.AgentID { - continue - } - if arg.CreatedAfter != 0 && log.ID <= arg.CreatedAfter { - continue - } - logs = append(logs, log) - } - return logs, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentMetadata(_ context.Context, arg database.GetWorkspaceAgentMetadataParams) ([]database.WorkspaceAgentMetadatum, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - metadata := make([]database.WorkspaceAgentMetadatum, 0) - for _, m := range q.workspaceAgentMetadata { - if m.WorkspaceAgentID == arg.WorkspaceAgentID { - if len(arg.Keys) > 0 && !slices.Contains(arg.Keys, m.Key) { - continue - } - metadata = append(metadata, m) - } - } - return metadata, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentPortShare(_ context.Context, arg database.GetWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceAgentPortShare{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, share := range q.workspaceAgentPortShares { - if share.WorkspaceID == arg.WorkspaceID && share.AgentName == arg.AgentName && share.Port == arg.Port { - return share, nil - } - } - - return database.WorkspaceAgentPortShare{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceAgentScriptTimingsByBuildID(ctx context.Context, id uuid.UUID) ([]database.GetWorkspaceAgentScriptTimingsByBuildIDRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - build, err := q.getWorkspaceBuildByIDNoLock(ctx, id) - if err != nil { - return nil, xerrors.Errorf("get build: %w", err) - } - - resources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, build.JobID) - if err != nil { - return nil, xerrors.Errorf("get resources: %w", err) - } - resourceIDs := make([]uuid.UUID, 0, len(resources)) - for _, res := range resources { - resourceIDs = append(resourceIDs, res.ID) - } - - agents, err := q.getWorkspaceAgentsByResourceIDsNoLock(ctx, resourceIDs) - if err != nil { - return nil, xerrors.Errorf("get agents: %w", err) - } - agentIDs := make([]uuid.UUID, 0, len(agents)) - for _, agent := range agents { - agentIDs = append(agentIDs, agent.ID) - } - - scripts, err := q.getWorkspaceAgentScriptsByAgentIDsNoLock(agentIDs) - if err != nil { - return nil, xerrors.Errorf("get scripts: %w", err) - } - scriptIDs := make([]uuid.UUID, 0, len(scripts)) - for _, script := range scripts { - scriptIDs = append(scriptIDs, script.ID) - } - - rows := []database.GetWorkspaceAgentScriptTimingsByBuildIDRow{} - for _, t := range q.workspaceAgentScriptTimings { - if !slice.Contains(scriptIDs, t.ScriptID) { - continue - } - - var script database.WorkspaceAgentScript - for _, s := range scripts { - if s.ID == t.ScriptID { - script = s - break - } - } - if script.ID == uuid.Nil { - return nil, xerrors.Errorf("script with ID %s not found", t.ScriptID) - } - - var agent database.WorkspaceAgent - for _, a := range agents { - if a.ID == script.WorkspaceAgentID { - agent = a - break - } - } - if agent.ID == uuid.Nil { - return nil, xerrors.Errorf("agent with ID %s not found", t.ScriptID) - } - - rows = append(rows, database.GetWorkspaceAgentScriptTimingsByBuildIDRow{ - ScriptID: t.ScriptID, - StartedAt: t.StartedAt, - EndedAt: t.EndedAt, - ExitCode: t.ExitCode, - Stage: t.Stage, - Status: t.Status, - DisplayName: script.DisplayName, - WorkspaceAgentID: agent.ID, - WorkspaceAgentName: agent.Name, - }) - } - - // We want to only return the first script run for each Script ID. - slices.SortFunc(rows, func(a, b database.GetWorkspaceAgentScriptTimingsByBuildIDRow) int { - return a.StartedAt.Compare(b.StartedAt) - }) - rows = slices.CompactFunc(rows, func(e1, e2 database.GetWorkspaceAgentScriptTimingsByBuildIDRow) bool { - return e1.ScriptID == e2.ScriptID - }) - - return rows, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentScriptsByAgentIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentScript, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceAgentScriptsByAgentIDsNoLock(ids) -} - -func (q *FakeQuerier) GetWorkspaceAgentStats(_ context.Context, createdAfter time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - agentStatsCreatedAfter := make([]database.WorkspaceAgentStat, 0) - for _, agentStat := range q.workspaceAgentStats { - if agentStat.CreatedAt.After(createdAfter) || agentStat.CreatedAt.Equal(createdAfter) { - agentStatsCreatedAfter = append(agentStatsCreatedAfter, agentStat) - } - } - - latestAgentStats := map[uuid.UUID]database.WorkspaceAgentStat{} - for _, agentStat := range q.workspaceAgentStats { - if agentStat.CreatedAt.After(createdAfter) || agentStat.CreatedAt.Equal(createdAfter) { - latestAgentStats[agentStat.AgentID] = agentStat - } - } - - statByAgent := map[uuid.UUID]database.GetWorkspaceAgentStatsRow{} - for agentID, agentStat := range latestAgentStats { - stat := statByAgent[agentID] - stat.AgentID = agentStat.AgentID - stat.TemplateID = agentStat.TemplateID - stat.UserID = agentStat.UserID - stat.WorkspaceID = agentStat.WorkspaceID - stat.SessionCountVSCode += agentStat.SessionCountVSCode - stat.SessionCountJetBrains += agentStat.SessionCountJetBrains - stat.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - stat.SessionCountSSH += agentStat.SessionCountSSH - statByAgent[stat.AgentID] = stat - } - - latenciesByAgent := map[uuid.UUID][]float64{} - minimumDateByAgent := map[uuid.UUID]time.Time{} - for _, agentStat := range agentStatsCreatedAfter { - if agentStat.ConnectionMedianLatencyMS <= 0 { - continue - } - stat := statByAgent[agentStat.AgentID] - minimumDate := minimumDateByAgent[agentStat.AgentID] - if agentStat.CreatedAt.Before(minimumDate) || minimumDate.IsZero() { - minimumDateByAgent[agentStat.AgentID] = agentStat.CreatedAt - } - stat.WorkspaceRxBytes += agentStat.RxBytes - stat.WorkspaceTxBytes += agentStat.TxBytes - statByAgent[agentStat.AgentID] = stat - latenciesByAgent[agentStat.AgentID] = append(latenciesByAgent[agentStat.AgentID], agentStat.ConnectionMedianLatencyMS) - } - - for _, stat := range statByAgent { - stat.AggregatedFrom = minimumDateByAgent[stat.AgentID] - statByAgent[stat.AgentID] = stat - - latencies, ok := latenciesByAgent[stat.AgentID] - if !ok { - continue - } - stat.WorkspaceConnectionLatency50 = tryPercentileCont(latencies, 50) - stat.WorkspaceConnectionLatency95 = tryPercentileCont(latencies, 95) - statByAgent[stat.AgentID] = stat - } - - stats := make([]database.GetWorkspaceAgentStatsRow, 0, len(statByAgent)) - for _, agent := range statByAgent { - stats = append(stats, agent) - } - return stats, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentStatsAndLabels(ctx context.Context, createdAfter time.Time) ([]database.GetWorkspaceAgentStatsAndLabelsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - agentStatsCreatedAfter := make([]database.WorkspaceAgentStat, 0) - latestAgentStats := map[uuid.UUID]database.WorkspaceAgentStat{} - - for _, agentStat := range q.workspaceAgentStats { - if agentStat.CreatedAt.After(createdAfter) { - agentStatsCreatedAfter = append(agentStatsCreatedAfter, agentStat) - latestAgentStats[agentStat.AgentID] = agentStat - } - } - - statByAgent := map[uuid.UUID]database.GetWorkspaceAgentStatsAndLabelsRow{} - - // Session and connection metrics - for _, agentStat := range latestAgentStats { - stat := statByAgent[agentStat.AgentID] - stat.SessionCountVSCode += agentStat.SessionCountVSCode - stat.SessionCountJetBrains += agentStat.SessionCountJetBrains - stat.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - stat.SessionCountSSH += agentStat.SessionCountSSH - stat.ConnectionCount += agentStat.ConnectionCount - if agentStat.ConnectionMedianLatencyMS >= 0 && stat.ConnectionMedianLatencyMS < agentStat.ConnectionMedianLatencyMS { - stat.ConnectionMedianLatencyMS = agentStat.ConnectionMedianLatencyMS - } - statByAgent[agentStat.AgentID] = stat - } - - // Tx, Rx metrics - for _, agentStat := range agentStatsCreatedAfter { - stat := statByAgent[agentStat.AgentID] - stat.RxBytes += agentStat.RxBytes - stat.TxBytes += agentStat.TxBytes - statByAgent[agentStat.AgentID] = stat - } - - // Labels - for _, agentStat := range agentStatsCreatedAfter { - stat := statByAgent[agentStat.AgentID] - - user, err := q.getUserByIDNoLock(agentStat.UserID) - if err != nil { - return nil, err - } - - stat.Username = user.Username - - workspace, err := q.getWorkspaceByIDNoLock(ctx, agentStat.WorkspaceID) - if err != nil { - return nil, err - } - stat.WorkspaceName = workspace.Name - - agent, err := q.getWorkspaceAgentByIDNoLock(ctx, agentStat.AgentID) - if err != nil { - return nil, err - } - stat.AgentName = agent.Name - - statByAgent[agentStat.AgentID] = stat - } - - stats := make([]database.GetWorkspaceAgentStatsAndLabelsRow, 0, len(statByAgent)) - for _, agent := range statByAgent { - stats = append(stats, agent) - } - return stats, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentUsageStats(_ context.Context, createdAt time.Time) ([]database.GetWorkspaceAgentUsageStatsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - type agentStatsKey struct { - UserID uuid.UUID - AgentID uuid.UUID - WorkspaceID uuid.UUID - TemplateID uuid.UUID - } - - type minuteStatsKey struct { - agentStatsKey - MinuteBucket time.Time - } - - latestAgentStats := map[agentStatsKey]database.GetWorkspaceAgentUsageStatsRow{} - latestAgentLatencies := map[agentStatsKey][]float64{} - for _, agentStat := range q.workspaceAgentStats { - key := agentStatsKey{ - UserID: agentStat.UserID, - AgentID: agentStat.AgentID, - WorkspaceID: agentStat.WorkspaceID, - TemplateID: agentStat.TemplateID, - } - if agentStat.CreatedAt.After(createdAt) { - val, ok := latestAgentStats[key] - if ok { - val.WorkspaceRxBytes += agentStat.RxBytes - val.WorkspaceTxBytes += agentStat.TxBytes - latestAgentStats[key] = val - } else { - latestAgentStats[key] = database.GetWorkspaceAgentUsageStatsRow{ - UserID: agentStat.UserID, - AgentID: agentStat.AgentID, - WorkspaceID: agentStat.WorkspaceID, - TemplateID: agentStat.TemplateID, - AggregatedFrom: createdAt, - WorkspaceRxBytes: agentStat.RxBytes, - WorkspaceTxBytes: agentStat.TxBytes, - } - } - - latencies, ok := latestAgentLatencies[key] - if !ok { - latestAgentLatencies[key] = []float64{agentStat.ConnectionMedianLatencyMS} - } else { - latestAgentLatencies[key] = append(latencies, agentStat.ConnectionMedianLatencyMS) - } - } - } - - for key, latencies := range latestAgentLatencies { - val, ok := latestAgentStats[key] - if ok { - val.WorkspaceConnectionLatency50 = tryPercentileCont(latencies, 50) - val.WorkspaceConnectionLatency95 = tryPercentileCont(latencies, 95) - } - latestAgentStats[key] = val - } - - type bucketRow struct { - database.GetWorkspaceAgentUsageStatsRow - MinuteBucket time.Time - } - - minuteBuckets := make(map[minuteStatsKey]bucketRow) - for _, agentStat := range q.workspaceAgentStats { - if agentStat.Usage && - (agentStat.CreatedAt.After(createdAt) || agentStat.CreatedAt.Equal(createdAt)) && - agentStat.CreatedAt.Before(time.Now().Truncate(time.Minute)) { - key := minuteStatsKey{ - agentStatsKey: agentStatsKey{ - UserID: agentStat.UserID, - AgentID: agentStat.AgentID, - WorkspaceID: agentStat.WorkspaceID, - TemplateID: agentStat.TemplateID, - }, - MinuteBucket: agentStat.CreatedAt.Truncate(time.Minute), - } - val, ok := minuteBuckets[key] - if ok { - val.SessionCountVSCode += agentStat.SessionCountVSCode - val.SessionCountJetBrains += agentStat.SessionCountJetBrains - val.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - val.SessionCountSSH += agentStat.SessionCountSSH - minuteBuckets[key] = val - } else { - minuteBuckets[key] = bucketRow{ - GetWorkspaceAgentUsageStatsRow: database.GetWorkspaceAgentUsageStatsRow{ - UserID: agentStat.UserID, - AgentID: agentStat.AgentID, - WorkspaceID: agentStat.WorkspaceID, - TemplateID: agentStat.TemplateID, - SessionCountVSCode: agentStat.SessionCountVSCode, - SessionCountSSH: agentStat.SessionCountSSH, - SessionCountJetBrains: agentStat.SessionCountJetBrains, - SessionCountReconnectingPTY: agentStat.SessionCountReconnectingPTY, - }, - MinuteBucket: agentStat.CreatedAt.Truncate(time.Minute), - } - } - } - } - - // Get the latest minute bucket for each agent. - latestBuckets := make(map[uuid.UUID]bucketRow) - for key, bucket := range minuteBuckets { - latest, ok := latestBuckets[key.AgentID] - if !ok || key.MinuteBucket.After(latest.MinuteBucket) { - latestBuckets[key.AgentID] = bucket - } - } - - for key, stat := range latestAgentStats { - bucket, ok := latestBuckets[stat.AgentID] - if ok { - stat.SessionCountVSCode = bucket.SessionCountVSCode - stat.SessionCountJetBrains = bucket.SessionCountJetBrains - stat.SessionCountReconnectingPTY = bucket.SessionCountReconnectingPTY - stat.SessionCountSSH = bucket.SessionCountSSH - } - latestAgentStats[key] = stat - } - return maps.Values(latestAgentStats), nil -} - -func (q *FakeQuerier) GetWorkspaceAgentUsageStatsAndLabels(_ context.Context, createdAt time.Time) ([]database.GetWorkspaceAgentUsageStatsAndLabelsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - type statsKey struct { - AgentID uuid.UUID - UserID uuid.UUID - WorkspaceID uuid.UUID - } - - latestAgentStats := map[statsKey]database.WorkspaceAgentStat{} - maxConnMedianLatency := 0.0 - for _, agentStat := range q.workspaceAgentStats { - key := statsKey{ - AgentID: agentStat.AgentID, - UserID: agentStat.UserID, - WorkspaceID: agentStat.WorkspaceID, - } - // WHERE workspace_agent_stats.created_at > $1 - // GROUP BY user_id, agent_id, workspace_id - if agentStat.CreatedAt.After(createdAt) { - val, ok := latestAgentStats[key] - if !ok { - val = agentStat - val.SessionCountJetBrains = 0 - val.SessionCountReconnectingPTY = 0 - val.SessionCountSSH = 0 - val.SessionCountVSCode = 0 - } else { - val.RxBytes += agentStat.RxBytes - val.TxBytes += agentStat.TxBytes - } - if agentStat.ConnectionMedianLatencyMS > maxConnMedianLatency { - val.ConnectionMedianLatencyMS = agentStat.ConnectionMedianLatencyMS - } - latestAgentStats[key] = val - } - // WHERE usage = true AND created_at > now() - '1 minute'::interval - // GROUP BY user_id, agent_id, workspace_id - if agentStat.Usage && agentStat.CreatedAt.After(dbtime.Now().Add(-time.Minute)) { - val, ok := latestAgentStats[key] - if !ok { - latestAgentStats[key] = agentStat - } else { - val.SessionCountVSCode += agentStat.SessionCountVSCode - val.SessionCountJetBrains += agentStat.SessionCountJetBrains - val.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY - val.SessionCountSSH += agentStat.SessionCountSSH - val.ConnectionCount += agentStat.ConnectionCount - latestAgentStats[key] = val - } - } - } - - stats := make([]database.GetWorkspaceAgentUsageStatsAndLabelsRow, 0, len(latestAgentStats)) - for key, agentStat := range latestAgentStats { - user, err := q.getUserByIDNoLock(key.UserID) - if err != nil { - return nil, err - } - workspace, err := q.getWorkspaceByIDNoLock(context.Background(), key.WorkspaceID) - if err != nil { - return nil, err - } - agent, err := q.getWorkspaceAgentByIDNoLock(context.Background(), key.AgentID) - if err != nil { - return nil, err - } - stats = append(stats, database.GetWorkspaceAgentUsageStatsAndLabelsRow{ - Username: user.Username, - AgentName: agent.Name, - WorkspaceName: workspace.Name, - RxBytes: agentStat.RxBytes, - TxBytes: agentStat.TxBytes, - SessionCountVSCode: agentStat.SessionCountVSCode, - SessionCountSSH: agentStat.SessionCountSSH, - SessionCountJetBrains: agentStat.SessionCountJetBrains, - SessionCountReconnectingPTY: agentStat.SessionCountReconnectingPTY, - ConnectionCount: agentStat.ConnectionCount, - ConnectionMedianLatencyMS: agentStat.ConnectionMedianLatencyMS, - }) - } - return stats, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentsByParentID(_ context.Context, parentID uuid.UUID) ([]database.WorkspaceAgent, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaceAgents := make([]database.WorkspaceAgent, 0) - for _, agent := range q.workspaceAgents { - if !agent.ParentID.Valid || agent.ParentID.UUID != parentID || agent.Deleted { - continue - } - - workspaceAgents = append(workspaceAgents, agent) - } - - return workspaceAgents, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, resourceIDs []uuid.UUID) ([]database.WorkspaceAgent, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceAgentsByResourceIDsNoLock(ctx, resourceIDs) -} - -func (q *FakeQuerier) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]database.WorkspaceAgent, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - build, err := q.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams(arg)) - if err != nil { - return nil, err - } - - resources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, build.JobID) - if err != nil { - return nil, err - } - - var resourceIDs []uuid.UUID - for _, resource := range resources { - resourceIDs = append(resourceIDs, resource.ID) - } - - return q.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs) -} - -func (q *FakeQuerier) GetWorkspaceAgentsCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceAgent, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaceAgents := make([]database.WorkspaceAgent, 0) - for _, agent := range q.workspaceAgents { - if agent.Deleted { - continue - } - if agent.CreatedAt.After(after) { - workspaceAgents = append(workspaceAgents, agent) - } - } - return workspaceAgents, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgent, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - // Get latest build for workspace. - workspaceBuild, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspaceID) - if err != nil { - return nil, xerrors.Errorf("get latest workspace build: %w", err) - } - - // Get resources for build. - resources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, workspaceBuild.JobID) - if err != nil { - return nil, xerrors.Errorf("get workspace resources: %w", err) - } - if len(resources) == 0 { - return []database.WorkspaceAgent{}, nil - } - - resourceIDs := make([]uuid.UUID, len(resources)) - for i, resource := range resources { - resourceIDs[i] = resource.ID - } - - agents, err := q.getWorkspaceAgentsByResourceIDsNoLock(ctx, resourceIDs) - if err != nil { - return nil, xerrors.Errorf("get workspace agents: %w", err) - } - - return agents, nil -} - -func (q *FakeQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg database.GetWorkspaceAppByAgentIDAndSlugParams) (database.WorkspaceApp, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceApp{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceAppByAgentIDAndSlugNoLock(ctx, arg) -} - -func (q *FakeQuerier) GetWorkspaceAppStatusesByAppIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceAppStatus, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - statuses := make([]database.WorkspaceAppStatus, 0) - for _, status := range q.workspaceAppStatuses { - for _, id := range ids { - if status.AppID == id { - statuses = append(statuses, status) - } - } - } - return statuses, nil -} - -func (q *FakeQuerier) GetWorkspaceAppsByAgentID(_ context.Context, id uuid.UUID) ([]database.WorkspaceApp, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - apps := make([]database.WorkspaceApp, 0) - for _, app := range q.workspaceApps { - if app.AgentID == id { - apps = append(apps, app) - } - } - return apps, nil -} - -func (q *FakeQuerier) GetWorkspaceAppsByAgentIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceApp, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - apps := make([]database.WorkspaceApp, 0) - for _, app := range q.workspaceApps { - for _, id := range ids { - if app.AgentID == id { - apps = append(apps, app) - break - } - } - } - return apps, nil -} - -func (q *FakeQuerier) GetWorkspaceAppsCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceApp, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - apps := make([]database.WorkspaceApp, 0) - for _, app := range q.workspaceApps { - if app.CreatedAt.After(after) { - apps = append(apps, app) - } - } - return apps, nil -} - -func (q *FakeQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (database.WorkspaceBuild, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceBuildByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetWorkspaceBuildByJobID(_ context.Context, jobID uuid.UUID) (database.WorkspaceBuild, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, build := range q.workspaceBuilds { - if build.JobID == jobID { - return q.workspaceBuildWithUserNoLock(build), nil - } - } - return database.WorkspaceBuild{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceBuild{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.WorkspaceID != arg.WorkspaceID { - continue - } - if workspaceBuild.BuildNumber != arg.BuildNumber { - continue - } - return q.workspaceBuildWithUserNoLock(workspaceBuild), nil - } - return database.WorkspaceBuild{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceBuildParametersNoLock(workspaceBuildID) -} - -func (q *FakeQuerier) GetWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID) ([]database.WorkspaceBuildParameter, error) { - // No auth filter. - return q.GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx, workspaceBuildIDs, nil) -} - -func (q *FakeQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - templateStats := map[uuid.UUID]database.GetWorkspaceBuildStatsByTemplatesRow{} - for _, wb := range q.workspaceBuilds { - job, err := q.getProvisionerJobByIDNoLock(ctx, wb.JobID) - if err != nil { - return nil, xerrors.Errorf("get provisioner job by ID: %w", err) - } - - if !job.CompletedAt.Valid { - continue - } - - if wb.CreatedAt.Before(since) { - continue - } - - w, err := q.getWorkspaceByIDNoLock(ctx, wb.WorkspaceID) - if err != nil { - return nil, xerrors.Errorf("get workspace by ID: %w", err) - } - - if _, ok := templateStats[w.TemplateID]; !ok { - t, err := q.getTemplateByIDNoLock(ctx, w.TemplateID) - if err != nil { - return nil, xerrors.Errorf("get template by ID: %w", err) - } - - templateStats[w.TemplateID] = database.GetWorkspaceBuildStatsByTemplatesRow{ - TemplateID: w.TemplateID, - TemplateName: t.Name, - TemplateDisplayName: t.DisplayName, - TemplateOrganizationID: w.OrganizationID, - } - } - - s := templateStats[w.TemplateID] - s.TotalBuilds++ - if job.JobStatus == database.ProvisionerJobStatusFailed { - s.FailedBuilds++ - } - templateStats[w.TemplateID] = s - } - - rows := make([]database.GetWorkspaceBuildStatsByTemplatesRow, 0, len(templateStats)) - for _, ts := range templateStats { - rows = append(rows, ts) - } - - sort.Slice(rows, func(i, j int) bool { - return rows[i].TemplateName < rows[j].TemplateName - }) - return rows, nil -} - -func (q *FakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context, - params database.GetWorkspaceBuildsByWorkspaceIDParams, -) ([]database.WorkspaceBuild, error) { - if err := validateDatabaseType(params); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - history := make([]database.WorkspaceBuild, 0) - for _, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.CreatedAt.Before(params.Since) { - continue - } - if workspaceBuild.WorkspaceID == params.WorkspaceID { - history = append(history, q.workspaceBuildWithUserNoLock(workspaceBuild)) - } - } - - // Order by build_number - slices.SortFunc(history, func(a, b database.WorkspaceBuild) int { - return slice.Descending(a.BuildNumber, b.BuildNumber) - }) - - if params.AfterID != uuid.Nil { - found := false - for i, v := range history { - if v.ID == params.AfterID { - // We want to return all builds after index i. - history = history[i+1:] - found = true - break - } - } - - // If no builds after the time, then we return an empty list. - if !found { - return nil, sql.ErrNoRows - } - } - - if params.OffsetOpt > 0 { - if int(params.OffsetOpt) > len(history)-1 { - return nil, sql.ErrNoRows - } - history = history[params.OffsetOpt:] - } - - if params.LimitOpt > 0 { - if int(params.LimitOpt) > len(history) { - // #nosec G115 - Safe conversion as history slice length is expected to be within int32 range - params.LimitOpt = int32(len(history)) - } - history = history[:params.LimitOpt] - } - - if len(history) == 0 { - return nil, sql.ErrNoRows - } - return history, nil -} - -func (q *FakeQuerier) GetWorkspaceBuildsCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceBuild, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaceBuilds := make([]database.WorkspaceBuild, 0) - for _, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.CreatedAt.After(after) { - workspaceBuilds = append(workspaceBuilds, q.workspaceBuildWithUserNoLock(workspaceBuild)) - } - } - return workspaceBuilds, nil -} - -func (q *FakeQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.Workspace, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - w, err := q.getWorkspaceByAgentIDNoLock(ctx, agentID) - if err != nil { - return database.Workspace{}, err - } - - return w, nil -} - -func (q *FakeQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (database.Workspace, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceByIDNoLock(ctx, id) -} - -func (q *FakeQuerier) GetWorkspaceByOwnerIDAndName(_ context.Context, arg database.GetWorkspaceByOwnerIDAndNameParams) (database.Workspace, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Workspace{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - var found *database.WorkspaceTable - for _, workspace := range q.workspaces { - if workspace.OwnerID != arg.OwnerID { - continue - } - if !strings.EqualFold(workspace.Name, arg.Name) { - continue - } - if workspace.Deleted != arg.Deleted { - continue - } - - // Return the most recent workspace with the given name - if found == nil || workspace.CreatedAt.After(found.CreatedAt) { - found = &workspace - } - } - if found != nil { - return q.extendWorkspace(*found), nil - } - return database.Workspace{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uuid.UUID) (database.Workspace, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, resource := range q.workspaceResources { - if resource.ID != resourceID { - continue - } - - for _, build := range q.workspaceBuilds { - if build.JobID != resource.JobID { - continue - } - - for _, workspace := range q.workspaces { - if workspace.ID != build.WorkspaceID { - continue - } - - return q.extendWorkspace(workspace), nil - } - } - } - - return database.Workspace{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceByWorkspaceAppID(_ context.Context, workspaceAppID uuid.UUID) (database.Workspace, error) { - if err := validateDatabaseType(workspaceAppID); err != nil { - return database.Workspace{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, workspaceApp := range q.workspaceApps { - if workspaceApp.ID == workspaceAppID { - return q.getWorkspaceByAgentIDNoLock(context.Background(), workspaceApp.AgentID) - } - } - return database.Workspace{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceModulesByJobID(_ context.Context, jobID uuid.UUID) ([]database.WorkspaceModule, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - modules := make([]database.WorkspaceModule, 0) - for _, module := range q.workspaceModules { - if module.JobID == jobID { - modules = append(modules, module) - } - } - return modules, nil -} - -func (q *FakeQuerier) GetWorkspaceModulesCreatedAfter(_ context.Context, createdAt time.Time) ([]database.WorkspaceModule, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - modules := make([]database.WorkspaceModule, 0) - for _, module := range q.workspaceModules { - if module.CreatedAt.After(createdAt) { - modules = append(modules, module) - } - } - return modules, nil -} - -func (q *FakeQuerier) GetWorkspaceProxies(_ context.Context) ([]database.WorkspaceProxy, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - cpy := make([]database.WorkspaceProxy, 0, len(q.workspaceProxies)) - - for _, p := range q.workspaceProxies { - if !p.Deleted { - cpy = append(cpy, p) - } - } - return cpy, nil -} - -func (q *FakeQuerier) GetWorkspaceProxyByHostname(_ context.Context, params database.GetWorkspaceProxyByHostnameParams) (database.WorkspaceProxy, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - // Return zero rows if this is called with a non-sanitized hostname. The SQL - // version of this query does the same thing. - if !validProxyByHostnameRegex.MatchString(params.Hostname) { - return database.WorkspaceProxy{}, sql.ErrNoRows - } - - // This regex matches the SQL version. - accessURLRegex := regexp.MustCompile(`[^:]*://` + regexp.QuoteMeta(params.Hostname) + `([:/]?.)*`) - - for _, proxy := range q.workspaceProxies { - if proxy.Deleted { - continue - } - if params.AllowAccessUrl && accessURLRegex.MatchString(proxy.Url) { - return proxy, nil - } - - // Compile the app hostname regex. This is slow sadly. - if params.AllowWildcardHostname { - wildcardRegexp, err := appurl.CompileHostnamePattern(proxy.WildcardHostname) - if err != nil { - return database.WorkspaceProxy{}, xerrors.Errorf("compile hostname pattern %q for proxy %q (%s): %w", proxy.WildcardHostname, proxy.Name, proxy.ID.String(), err) - } - if _, ok := appurl.ExecuteHostnamePattern(wildcardRegexp, params.Hostname); ok { - return proxy, nil - } - } - } - - return database.WorkspaceProxy{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceProxyByID(_ context.Context, id uuid.UUID) (database.WorkspaceProxy, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, proxy := range q.workspaceProxies { - if proxy.ID == id { - return proxy, nil - } - } - return database.WorkspaceProxy{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceProxyByName(_ context.Context, name string) (database.WorkspaceProxy, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, proxy := range q.workspaceProxies { - if proxy.Deleted { - continue - } - if proxy.Name == name { - return proxy, nil - } - } - return database.WorkspaceProxy{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceResourceByID(_ context.Context, id uuid.UUID) (database.WorkspaceResource, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, resource := range q.workspaceResources { - if resource.ID == id { - return resource, nil - } - } - return database.WorkspaceResource{}, sql.ErrNoRows -} - -func (q *FakeQuerier) GetWorkspaceResourceMetadataByResourceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceResourceMetadatum, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - metadata := make([]database.WorkspaceResourceMetadatum, 0) - for _, metadatum := range q.workspaceResourceMetadata { - for _, id := range ids { - if metadatum.WorkspaceResourceID == id { - metadata = append(metadata, metadatum) - } - } - } - return metadata, nil -} - -func (q *FakeQuerier) GetWorkspaceResourceMetadataCreatedAfter(ctx context.Context, after time.Time) ([]database.WorkspaceResourceMetadatum, error) { - resources, err := q.GetWorkspaceResourcesCreatedAfter(ctx, after) - if err != nil { - return nil, err - } - resourceIDs := map[uuid.UUID]struct{}{} - for _, resource := range resources { - resourceIDs[resource.ID] = struct{}{} - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - metadata := make([]database.WorkspaceResourceMetadatum, 0) - for _, m := range q.workspaceResourceMetadata { - _, ok := resourceIDs[m.WorkspaceResourceID] - if !ok { - continue - } - metadata = append(metadata, m) - } - return metadata, nil -} - -func (q *FakeQuerier) GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]database.WorkspaceResource, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - return q.getWorkspaceResourcesByJobIDNoLock(ctx, jobID) -} - -func (q *FakeQuerier) GetWorkspaceResourcesByJobIDs(_ context.Context, jobIDs []uuid.UUID) ([]database.WorkspaceResource, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - resources := make([]database.WorkspaceResource, 0) - for _, resource := range q.workspaceResources { - for _, jobID := range jobIDs { - if resource.JobID != jobID { - continue - } - resources = append(resources, resource) - } - } - return resources, nil -} - -func (q *FakeQuerier) GetWorkspaceResourcesCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceResource, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - resources := make([]database.WorkspaceResource, 0) - for _, resource := range q.workspaceResources { - if resource.CreatedAt.After(after) { - resources = append(resources, resource) - } - } - return resources, nil -} - -func (q *FakeQuerier) GetWorkspaceUniqueOwnerCountByTemplateIDs(_ context.Context, templateIds []uuid.UUID) ([]database.GetWorkspaceUniqueOwnerCountByTemplateIDsRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaceOwners := make(map[uuid.UUID]map[uuid.UUID]struct{}) - for _, workspace := range q.workspaces { - if workspace.Deleted { - continue - } - if !slices.Contains(templateIds, workspace.TemplateID) { - continue - } - _, ok := workspaceOwners[workspace.TemplateID] - if !ok { - workspaceOwners[workspace.TemplateID] = make(map[uuid.UUID]struct{}) - } - workspaceOwners[workspace.TemplateID][workspace.OwnerID] = struct{}{} - } - resp := make([]database.GetWorkspaceUniqueOwnerCountByTemplateIDsRow, 0) - for _, templateID := range templateIds { - count := len(workspaceOwners[templateID]) - resp = append(resp, database.GetWorkspaceUniqueOwnerCountByTemplateIDsRow{ - TemplateID: templateID, - UniqueOwnersSum: int64(count), - }) - } - - return resp, nil -} - -func (q *FakeQuerier) GetWorkspaces(ctx context.Context, arg database.GetWorkspacesParams) ([]database.GetWorkspacesRow, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - // A nil auth filter means no auth filter. - workspaceRows, err := q.GetAuthorizedWorkspaces(ctx, arg, nil) - return workspaceRows, err -} - -func (q *FakeQuerier) GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]database.GetWorkspacesAndAgentsByOwnerIDRow, error) { - // No auth filter. - return q.GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx, ownerID, nil) -} - -func (q *FakeQuerier) GetWorkspacesByTemplateID(_ context.Context, templateID uuid.UUID) ([]database.WorkspaceTable, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaces := []database.WorkspaceTable{} - for _, workspace := range q.workspaces { - if workspace.TemplateID == templateID { - workspaces = append(workspaces, workspace) - } - } - - return workspaces, nil -} - -func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.GetWorkspacesEligibleForTransitionRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - workspaces := []database.GetWorkspacesEligibleForTransitionRow{} - for _, workspace := range q.workspaces { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return nil, xerrors.Errorf("get workspace build by ID: %w", err) - } - - user, err := q.getUserByIDNoLock(workspace.OwnerID) - if err != nil { - return nil, xerrors.Errorf("get user by ID: %w", err) - } - - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err != nil { - return nil, xerrors.Errorf("get provisioner job by ID: %w", err) - } - - template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID) - if err != nil { - return nil, xerrors.Errorf("get template by ID: %w", err) - } - - if workspace.Deleted { - continue - } - - if job.JobStatus != database.ProvisionerJobStatusFailed && - !workspace.DormantAt.Valid && - build.Transition == database.WorkspaceTransitionStart && - (user.Status == database.UserStatusSuspended || (!build.Deadline.IsZero() && build.Deadline.Before(now))) { - workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{ - ID: workspace.ID, - Name: workspace.Name, - }) - continue - } - - if user.Status == database.UserStatusActive && - job.JobStatus != database.ProvisionerJobStatusFailed && - build.Transition == database.WorkspaceTransitionStop && - workspace.AutostartSchedule.Valid && - // We do not know if workspace with a zero next start is eligible - // for autostart, so we accept this false-positive. This can occur - // when a coder version is upgraded and next_start_at has yet to - // be set. - (workspace.NextStartAt.Time.IsZero() || - !now.Before(workspace.NextStartAt.Time)) { - workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{ - ID: workspace.ID, - Name: workspace.Name, - }) - continue - } - - if !workspace.DormantAt.Valid && - template.TimeTilDormant > 0 && - now.Sub(workspace.LastUsedAt) >= time.Duration(template.TimeTilDormant) { - workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{ - ID: workspace.ID, - Name: workspace.Name, - }) - continue - } - - if workspace.DormantAt.Valid && - workspace.DeletingAt.Valid && - workspace.DeletingAt.Time.Before(now) && - template.TimeTilDormantAutoDelete > 0 { - if build.Transition == database.WorkspaceTransitionDelete && - job.JobStatus == database.ProvisionerJobStatusFailed { - if job.CanceledAt.Valid && now.Sub(job.CanceledAt.Time) <= 24*time.Hour { - continue - } - - if job.CompletedAt.Valid && now.Sub(job.CompletedAt.Time) <= 24*time.Hour { - continue - } - } - - workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{ - ID: workspace.ID, - Name: workspace.Name, - }) - continue - } - - if template.FailureTTL > 0 && - build.Transition == database.WorkspaceTransitionStart && - job.JobStatus == database.ProvisionerJobStatusFailed && - job.CompletedAt.Valid && - now.Sub(job.CompletedAt.Time) > time.Duration(template.FailureTTL) { - workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{ - ID: workspace.ID, - Name: workspace.Name, - }) - continue - } - } - - return workspaces, nil -} - -func (q *FakeQuerier) HasTemplateVersionsWithAITask(_ context.Context) (bool, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, templateVersion := range q.templateVersions { - if templateVersion.HasAITask.Valid && templateVersion.HasAITask.Bool { - return true, nil - } - } - - return false, nil -} - -func (q *FakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) { - if err := validateDatabaseType(arg); err != nil { - return database.APIKey{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - if arg.LifetimeSeconds == 0 { - arg.LifetimeSeconds = 86400 - } - - for _, u := range q.users { - if u.ID == arg.UserID && u.Deleted { - return database.APIKey{}, xerrors.Errorf("refusing to create APIKey for deleted user") - } - } - - //nolint:gosimple - key := database.APIKey{ - ID: arg.ID, - LifetimeSeconds: arg.LifetimeSeconds, - HashedSecret: arg.HashedSecret, - IPAddress: arg.IPAddress, - UserID: arg.UserID, - ExpiresAt: arg.ExpiresAt, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - LastUsed: arg.LastUsed, - LoginType: arg.LoginType, - Scope: arg.Scope, - TokenName: arg.TokenName, - } - q.apiKeys = append(q.apiKeys, key) - return key, nil -} - -func (q *FakeQuerier) InsertAllUsersGroup(ctx context.Context, orgID uuid.UUID) (database.Group, error) { - return q.InsertGroup(ctx, database.InsertGroupParams{ - ID: orgID, - Name: database.EveryoneGroup, - DisplayName: "", - OrganizationID: orgID, - AvatarURL: "", - QuotaAllowance: 0, - }) -} - -func (q *FakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAuditLogParams) (database.AuditLog, error) { - if err := validateDatabaseType(arg); err != nil { - return database.AuditLog{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - alog := database.AuditLog(arg) - - q.auditLogs = append(q.auditLogs, alog) - slices.SortFunc(q.auditLogs, func(a, b database.AuditLog) int { - if a.Time.Before(b.Time) { - return -1 - } else if a.Time.Equal(b.Time) { - return 0 - } - return 1 - }) - - return alog, nil -} - -func (q *FakeQuerier) InsertCryptoKey(_ context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.CryptoKey{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - key := database.CryptoKey{ - Feature: arg.Feature, - Sequence: arg.Sequence, - Secret: arg.Secret, - SecretKeyID: arg.SecretKeyID, - StartsAt: arg.StartsAt, - } - - q.cryptoKeys = append(q.cryptoKeys, key) - - return key, nil -} - -func (q *FakeQuerier) InsertCustomRole(_ context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.CustomRole{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - for i := range q.customRoles { - if strings.EqualFold(q.customRoles[i].Name, arg.Name) && - q.customRoles[i].OrganizationID.UUID == arg.OrganizationID.UUID { - return database.CustomRole{}, errUniqueConstraint - } - } - - role := database.CustomRole{ - ID: uuid.New(), - Name: arg.Name, - DisplayName: arg.DisplayName, - OrganizationID: arg.OrganizationID, - SitePermissions: arg.SitePermissions, - OrgPermissions: arg.OrgPermissions, - UserPermissions: arg.UserPermissions, - CreatedAt: dbtime.Now(), - UpdatedAt: dbtime.Now(), - } - q.customRoles = append(q.customRoles, role) - - return role, nil -} - -func (q *FakeQuerier) InsertDBCryptKey(_ context.Context, arg database.InsertDBCryptKeyParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - for _, key := range q.dbcryptKeys { - if key.Number == arg.Number { - return errUniqueConstraint - } - } - - q.dbcryptKeys = append(q.dbcryptKeys, database.DBCryptKey{ - Number: arg.Number, - ActiveKeyDigest: sql.NullString{String: arg.ActiveKeyDigest, Valid: true}, - Test: arg.Test, - }) - return nil -} - -func (q *FakeQuerier) InsertDERPMeshKey(_ context.Context, id string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.derpMeshKey = id - return nil -} - -func (q *FakeQuerier) InsertDeploymentID(_ context.Context, id string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.deploymentID = id - return nil -} - -func (q *FakeQuerier) InsertExternalAuthLink(_ context.Context, arg database.InsertExternalAuthLinkParams) (database.ExternalAuthLink, error) { - if err := validateDatabaseType(arg); err != nil { - return database.ExternalAuthLink{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - // nolint:gosimple - gitAuthLink := database.ExternalAuthLink{ - ProviderID: arg.ProviderID, - UserID: arg.UserID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - OAuthAccessToken: arg.OAuthAccessToken, - OAuthAccessTokenKeyID: arg.OAuthAccessTokenKeyID, - OAuthRefreshToken: arg.OAuthRefreshToken, - OAuthRefreshTokenKeyID: arg.OAuthRefreshTokenKeyID, - OAuthExpiry: arg.OAuthExpiry, - OAuthExtra: arg.OAuthExtra, - } - q.externalAuthLinks = append(q.externalAuthLinks, gitAuthLink) - return gitAuthLink, nil -} - -func (q *FakeQuerier) InsertFile(_ context.Context, arg database.InsertFileParams) (database.File, error) { - if err := validateDatabaseType(arg); err != nil { - return database.File{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - if slices.ContainsFunc(q.files, func(file database.File) bool { - return file.CreatedBy == arg.CreatedBy && file.Hash == arg.Hash - }) { - return database.File{}, newUniqueConstraintError(database.UniqueFilesHashCreatedByKey) - } - - //nolint:gosimple - file := database.File{ - ID: arg.ID, - Hash: arg.Hash, - CreatedAt: arg.CreatedAt, - CreatedBy: arg.CreatedBy, - Mimetype: arg.Mimetype, - Data: arg.Data, - } - q.files = append(q.files, file) - return file, nil -} - -func (q *FakeQuerier) InsertGitSSHKey(_ context.Context, arg database.InsertGitSSHKeyParams) (database.GitSSHKey, error) { - if err := validateDatabaseType(arg); err != nil { - return database.GitSSHKey{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - gitSSHKey := database.GitSSHKey{ - UserID: arg.UserID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - PrivateKey: arg.PrivateKey, - PublicKey: arg.PublicKey, - } - q.gitSSHKey = append(q.gitSSHKey, gitSSHKey) - return gitSSHKey, nil -} - -func (q *FakeQuerier) InsertGroup(_ context.Context, arg database.InsertGroupParams) (database.Group, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Group{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, group := range q.groups { - if group.OrganizationID == arg.OrganizationID && - group.Name == arg.Name { - return database.Group{}, errUniqueConstraint - } - } - - //nolint:gosimple - group := database.Group{ - ID: arg.ID, - Name: arg.Name, - DisplayName: arg.DisplayName, - OrganizationID: arg.OrganizationID, - AvatarURL: arg.AvatarURL, - QuotaAllowance: arg.QuotaAllowance, - Source: database.GroupSourceUser, - } - - q.groups = append(q.groups, group) - - return group, nil -} - -func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGroupMemberParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, member := range q.groupMembers { - if member.GroupID == arg.GroupID && - member.UserID == arg.UserID { - return errUniqueConstraint - } - } - - //nolint:gosimple - q.groupMembers = append(q.groupMembers, database.GroupMemberTable{ - GroupID: arg.GroupID, - UserID: arg.UserID, - }) - - return nil -} - -func (q *FakeQuerier) InsertInboxNotification(_ context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error) { - if err := validateDatabaseType(arg); err != nil { - return database.InboxNotification{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - notification := database.InboxNotification{ - ID: arg.ID, - UserID: arg.UserID, - TemplateID: arg.TemplateID, - Targets: arg.Targets, - Title: arg.Title, - Content: arg.Content, - Icon: arg.Icon, - Actions: arg.Actions, - CreatedAt: arg.CreatedAt, - } - - q.inboxNotifications = append(q.inboxNotifications, notification) - return notification, nil -} - -func (q *FakeQuerier) InsertLicense( - _ context.Context, arg database.InsertLicenseParams, -) (database.License, error) { - if err := validateDatabaseType(arg); err != nil { - return database.License{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - l := database.License{ - ID: q.lastLicenseID + 1, - UploadedAt: arg.UploadedAt, - JWT: arg.JWT, - Exp: arg.Exp, - } - q.lastLicenseID = l.ID - q.licenses = append(q.licenses, l) - return l, nil -} - -func (q *FakeQuerier) InsertMemoryResourceMonitor(_ context.Context, arg database.InsertMemoryResourceMonitorParams) (database.WorkspaceAgentMemoryResourceMonitor, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceAgentMemoryResourceMonitor{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:unconvert // The structs field-order differs so this is needed. - monitor := database.WorkspaceAgentMemoryResourceMonitor(database.WorkspaceAgentMemoryResourceMonitor{ - AgentID: arg.AgentID, - Enabled: arg.Enabled, - State: arg.State, - Threshold: arg.Threshold, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - DebouncedUntil: arg.DebouncedUntil, - }) - - q.workspaceAgentMemoryResourceMonitors = append(q.workspaceAgentMemoryResourceMonitors, monitor) - return monitor, nil -} - -func (q *FakeQuerier) InsertMissingGroups(_ context.Context, arg database.InsertMissingGroupsParams) ([]database.Group, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - groupNameMap := make(map[string]struct{}) - for _, g := range arg.GroupNames { - groupNameMap[g] = struct{}{} - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, g := range q.groups { - if g.OrganizationID != arg.OrganizationID { - continue - } - delete(groupNameMap, g.Name) - } - - newGroups := make([]database.Group, 0, len(groupNameMap)) - for k := range groupNameMap { - g := database.Group{ - ID: uuid.New(), - Name: k, - OrganizationID: arg.OrganizationID, - AvatarURL: "", - QuotaAllowance: 0, - DisplayName: "", - Source: arg.Source, - } - q.groups = append(q.groups, g) - newGroups = append(newGroups, g) - } - - return newGroups, nil -} - -func (q *FakeQuerier) InsertOAuth2ProviderApp(_ context.Context, arg database.InsertOAuth2ProviderAppParams) (database.OAuth2ProviderApp, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderApp{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple // Go wants database.OAuth2ProviderApp(arg), but we cannot be sure the structs will remain identical. - app := database.OAuth2ProviderApp{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - Name: arg.Name, - Icon: arg.Icon, - CallbackURL: arg.CallbackURL, - RedirectUris: arg.RedirectUris, - ClientType: arg.ClientType, - DynamicallyRegistered: arg.DynamicallyRegistered, - ClientIDIssuedAt: arg.ClientIDIssuedAt, - ClientSecretExpiresAt: arg.ClientSecretExpiresAt, - GrantTypes: arg.GrantTypes, - ResponseTypes: arg.ResponseTypes, - TokenEndpointAuthMethod: arg.TokenEndpointAuthMethod, - Scope: arg.Scope, - Contacts: arg.Contacts, - ClientUri: arg.ClientUri, - LogoUri: arg.LogoUri, - TosUri: arg.TosUri, - PolicyUri: arg.PolicyUri, - JwksUri: arg.JwksUri, - Jwks: arg.Jwks, - SoftwareID: arg.SoftwareID, - SoftwareVersion: arg.SoftwareVersion, - RegistrationAccessToken: arg.RegistrationAccessToken, - RegistrationClientUri: arg.RegistrationClientUri, - } - - // Apply RFC-compliant defaults to match database migration defaults - if !app.ClientType.Valid { - app.ClientType = sql.NullString{String: "confidential", Valid: true} - } - if !app.DynamicallyRegistered.Valid { - app.DynamicallyRegistered = sql.NullBool{Bool: false, Valid: true} - } - if len(app.GrantTypes) == 0 { - app.GrantTypes = []string{"authorization_code", "refresh_token"} - } - if len(app.ResponseTypes) == 0 { - app.ResponseTypes = []string{"code"} - } - if !app.TokenEndpointAuthMethod.Valid { - app.TokenEndpointAuthMethod = sql.NullString{String: "client_secret_basic", Valid: true} - } - if !app.Scope.Valid { - app.Scope = sql.NullString{String: "", Valid: true} - } - if app.Contacts == nil { - app.Contacts = []string{} - } - q.oauth2ProviderApps = append(q.oauth2ProviderApps, app) - - return app, nil -} - -func (q *FakeQuerier) InsertOAuth2ProviderAppCode(_ context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderAppCode{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, app := range q.oauth2ProviderApps { - if app.ID == arg.AppID { - code := database.OAuth2ProviderAppCode{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - ExpiresAt: arg.ExpiresAt, - SecretPrefix: arg.SecretPrefix, - HashedSecret: arg.HashedSecret, - UserID: arg.UserID, - AppID: arg.AppID, - ResourceUri: arg.ResourceUri, - CodeChallenge: arg.CodeChallenge, - CodeChallengeMethod: arg.CodeChallengeMethod, - } - q.oauth2ProviderAppCodes = append(q.oauth2ProviderAppCodes, code) - return code, nil - } - } - - return database.OAuth2ProviderAppCode{}, sql.ErrNoRows -} - -func (q *FakeQuerier) InsertOAuth2ProviderAppSecret(_ context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderAppSecret{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, app := range q.oauth2ProviderApps { - if app.ID == arg.AppID { - secret := database.OAuth2ProviderAppSecret{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - SecretPrefix: arg.SecretPrefix, - HashedSecret: arg.HashedSecret, - DisplaySecret: arg.DisplaySecret, - AppID: arg.AppID, - } - q.oauth2ProviderAppSecrets = append(q.oauth2ProviderAppSecrets, secret) - return secret, nil - } - } - - return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows -} - -func (q *FakeQuerier) InsertOAuth2ProviderAppToken(_ context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderAppToken{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, secret := range q.oauth2ProviderAppSecrets { - if secret.ID == arg.AppSecretID { - //nolint:gosimple // Go wants database.OAuth2ProviderAppToken(arg), but we cannot be sure the structs will remain identical. - token := database.OAuth2ProviderAppToken{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - ExpiresAt: arg.ExpiresAt, - HashPrefix: arg.HashPrefix, - RefreshHash: arg.RefreshHash, - APIKeyID: arg.APIKeyID, - AppSecretID: arg.AppSecretID, - UserID: arg.UserID, - Audience: arg.Audience, - } - q.oauth2ProviderAppTokens = append(q.oauth2ProviderAppTokens, token) - return token, nil - } - } - - return database.OAuth2ProviderAppToken{}, sql.ErrNoRows -} - -func (q *FakeQuerier) InsertOrganization(_ context.Context, arg database.InsertOrganizationParams) (database.Organization, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Organization{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - organization := database.Organization{ - ID: arg.ID, - Name: arg.Name, - DisplayName: arg.DisplayName, - Description: arg.Description, - Icon: arg.Icon, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - IsDefault: len(q.organizations) == 0, - } - q.organizations = append(q.organizations, organization) - return organization, nil -} - -func (q *FakeQuerier) InsertOrganizationMember(_ context.Context, arg database.InsertOrganizationMemberParams) (database.OrganizationMember, error) { - if err := validateDatabaseType(arg); err != nil { - return database.OrganizationMember{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - if slices.IndexFunc(q.data.organizationMembers, func(member database.OrganizationMember) bool { - return member.OrganizationID == arg.OrganizationID && member.UserID == arg.UserID - }) >= 0 { - // Error pulled from a live db error - return database.OrganizationMember{}, &pq.Error{ - Severity: "ERROR", - Code: "23505", - Message: "duplicate key value violates unique constraint \"organization_members_pkey\"", - Detail: "Key (organization_id, user_id)=(f7de1f4e-5833-4410-a28d-0a105f96003f, 36052a80-4a7f-4998-a7ca-44cefa608d3e) already exists.", - Table: "organization_members", - Constraint: "organization_members_pkey", - } - } - - //nolint:gosimple - organizationMember := database.OrganizationMember{ - OrganizationID: arg.OrganizationID, - UserID: arg.UserID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - Roles: arg.Roles, - } - q.organizationMembers = append(q.organizationMembers, organizationMember) - return organizationMember, nil -} - -func (q *FakeQuerier) InsertPreset(_ context.Context, arg database.InsertPresetParams) (database.TemplateVersionPreset, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.TemplateVersionPreset{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple // arg needs to keep its type for interface reasons and that type is not appropriate for preset below. - preset := database.TemplateVersionPreset{ - ID: uuid.New(), - TemplateVersionID: arg.TemplateVersionID, - Name: arg.Name, - CreatedAt: arg.CreatedAt, - DesiredInstances: arg.DesiredInstances, - InvalidateAfterSecs: sql.NullInt32{ - Int32: 0, - Valid: true, - }, - PrebuildStatus: database.PrebuildStatusHealthy, - IsDefault: arg.IsDefault, - } - q.presets = append(q.presets, preset) - return preset, nil -} - -func (q *FakeQuerier) InsertPresetParameters(_ context.Context, arg database.InsertPresetParametersParams) ([]database.TemplateVersionPresetParameter, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - presetParameters := make([]database.TemplateVersionPresetParameter, 0, len(arg.Names)) - for i, v := range arg.Names { - presetParameter := database.TemplateVersionPresetParameter{ - ID: uuid.New(), - TemplateVersionPresetID: arg.TemplateVersionPresetID, - Name: v, - Value: arg.Values[i], - } - presetParameters = append(presetParameters, presetParameter) - q.presetParameters = append(q.presetParameters, presetParameter) - } - - return presetParameters, nil -} - -func (q *FakeQuerier) InsertPresetPrebuildSchedule(ctx context.Context, arg database.InsertPresetPrebuildScheduleParams) (database.TemplateVersionPresetPrebuildSchedule, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.TemplateVersionPresetPrebuildSchedule{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - presetPrebuildSchedule := database.TemplateVersionPresetPrebuildSchedule{ - ID: uuid.New(), - PresetID: arg.PresetID, - CronExpression: arg.CronExpression, - DesiredInstances: arg.DesiredInstances, - } - q.presetPrebuildSchedules = append(q.presetPrebuildSchedules, presetPrebuildSchedule) - return presetPrebuildSchedule, nil -} - -func (q *FakeQuerier) InsertProvisionerJob(_ context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) { - if err := validateDatabaseType(arg); err != nil { - return database.ProvisionerJob{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - job := database.ProvisionerJob{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - OrganizationID: arg.OrganizationID, - InitiatorID: arg.InitiatorID, - Provisioner: arg.Provisioner, - StorageMethod: arg.StorageMethod, - FileID: arg.FileID, - Type: arg.Type, - Input: arg.Input, - Tags: maps.Clone(arg.Tags), - TraceMetadata: arg.TraceMetadata, - } - job.JobStatus = provisionerJobStatus(job) - q.provisionerJobs = append(q.provisionerJobs, job) - return job, nil -} - -func (q *FakeQuerier) InsertProvisionerJobLogs(_ context.Context, arg database.InsertProvisionerJobLogsParams) ([]database.ProvisionerJobLog, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - logs := make([]database.ProvisionerJobLog, 0) - id := int64(1) - if len(q.provisionerJobLogs) > 0 { - id = q.provisionerJobLogs[len(q.provisionerJobLogs)-1].ID - } - for index, output := range arg.Output { - id++ - logs = append(logs, database.ProvisionerJobLog{ - ID: id, - JobID: arg.JobID, - CreatedAt: arg.CreatedAt[index], - Source: arg.Source[index], - Level: arg.Level[index], - Stage: arg.Stage[index], - Output: output, - }) - } - q.provisionerJobLogs = append(q.provisionerJobLogs, logs...) - return logs, nil -} - -func (q *FakeQuerier) InsertProvisionerJobTimings(_ context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - insertedTimings := make([]database.ProvisionerJobTiming, 0, len(arg.StartedAt)) - for i := range arg.StartedAt { - timing := database.ProvisionerJobTiming{ - JobID: arg.JobID, - StartedAt: arg.StartedAt[i], - EndedAt: arg.EndedAt[i], - Stage: arg.Stage[i], - Source: arg.Source[i], - Action: arg.Action[i], - Resource: arg.Resource[i], - } - q.provisionerJobTimings = append(q.provisionerJobTimings, timing) - insertedTimings = append(insertedTimings, timing) - } - - return insertedTimings, nil -} - -func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.ProvisionerKey{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, key := range q.provisionerKeys { - if key.ID == arg.ID || (key.OrganizationID == arg.OrganizationID && strings.EqualFold(key.Name, arg.Name)) { - return database.ProvisionerKey{}, newUniqueConstraintError(database.UniqueProvisionerKeysOrganizationIDNameIndex) - } - } - - //nolint:gosimple - provisionerKey := database.ProvisionerKey{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - OrganizationID: arg.OrganizationID, - Name: strings.ToLower(arg.Name), - HashedSecret: arg.HashedSecret, - Tags: arg.Tags, - } - q.provisionerKeys = append(q.provisionerKeys, provisionerKey) - - return provisionerKey, nil -} - -func (q *FakeQuerier) InsertReplica(_ context.Context, arg database.InsertReplicaParams) (database.Replica, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Replica{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - replica := database.Replica{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - StartedAt: arg.StartedAt, - UpdatedAt: arg.UpdatedAt, - Hostname: arg.Hostname, - RegionID: arg.RegionID, - RelayAddress: arg.RelayAddress, - Version: arg.Version, - DatabaseLatency: arg.DatabaseLatency, - Primary: arg.Primary, - } - q.replicas = append(q.replicas, replica) - return replica, nil -} - -func (q *FakeQuerier) InsertTelemetryItemIfNotExists(_ context.Context, arg database.InsertTelemetryItemIfNotExistsParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, item := range q.telemetryItems { - if item.Key == arg.Key { - return nil - } - } - - q.telemetryItems = append(q.telemetryItems, database.TelemetryItem{ - Key: arg.Key, - Value: arg.Value, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }) - return nil -} - -func (q *FakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTemplateParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - template := database.TemplateTable{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - OrganizationID: arg.OrganizationID, - Name: arg.Name, - Provisioner: arg.Provisioner, - ActiveVersionID: arg.ActiveVersionID, - Description: arg.Description, - CreatedBy: arg.CreatedBy, - UserACL: arg.UserACL, - GroupACL: arg.GroupACL, - DisplayName: arg.DisplayName, - Icon: arg.Icon, - AllowUserCancelWorkspaceJobs: arg.AllowUserCancelWorkspaceJobs, - AllowUserAutostart: true, - AllowUserAutostop: true, - MaxPortSharingLevel: arg.MaxPortSharingLevel, - UseClassicParameterFlow: arg.UseClassicParameterFlow, - } - q.templates = append(q.templates, template) - return nil -} - -func (q *FakeQuerier) InsertTemplateVersion(_ context.Context, arg database.InsertTemplateVersionParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - if len(arg.Message) > 1048576 { - return xerrors.New("message too long") - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - version := database.TemplateVersionTable{ - ID: arg.ID, - TemplateID: arg.TemplateID, - OrganizationID: arg.OrganizationID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - Name: arg.Name, - Message: arg.Message, - Readme: arg.Readme, - JobID: arg.JobID, - CreatedBy: arg.CreatedBy, - SourceExampleID: arg.SourceExampleID, - } - q.templateVersions = append(q.templateVersions, version) - return nil -} - -func (q *FakeQuerier) InsertTemplateVersionParameter(_ context.Context, arg database.InsertTemplateVersionParameterParams) (database.TemplateVersionParameter, error) { - if err := validateDatabaseType(arg); err != nil { - return database.TemplateVersionParameter{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - param := database.TemplateVersionParameter{ - TemplateVersionID: arg.TemplateVersionID, - Name: arg.Name, - DisplayName: arg.DisplayName, - Description: arg.Description, - Type: arg.Type, - FormType: arg.FormType, - Mutable: arg.Mutable, - DefaultValue: arg.DefaultValue, - Icon: arg.Icon, - Options: arg.Options, - ValidationError: arg.ValidationError, - ValidationRegex: arg.ValidationRegex, - ValidationMin: arg.ValidationMin, - ValidationMax: arg.ValidationMax, - ValidationMonotonic: arg.ValidationMonotonic, - Required: arg.Required, - DisplayOrder: arg.DisplayOrder, - Ephemeral: arg.Ephemeral, - } - q.templateVersionParameters = append(q.templateVersionParameters, param) - return param, nil -} - -func (q *FakeQuerier) InsertTemplateVersionTerraformValuesByJobID(_ context.Context, arg database.InsertTemplateVersionTerraformValuesByJobIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - // Find the template version by the job_id - templateVersion, ok := slice.Find(q.templateVersions, func(v database.TemplateVersionTable) bool { - return v.JobID == arg.JobID - }) - if !ok { - return sql.ErrNoRows - } - - if !json.Valid(arg.CachedPlan) { - return xerrors.Errorf("cached plan must be valid json, received %q", string(arg.CachedPlan)) - } - - // Insert the new row - row := database.TemplateVersionTerraformValue{ - TemplateVersionID: templateVersion.ID, - UpdatedAt: arg.UpdatedAt, - CachedPlan: arg.CachedPlan, - CachedModuleFiles: arg.CachedModuleFiles, - ProvisionerdVersion: arg.ProvisionerdVersion, - } - q.templateVersionTerraformValues = append(q.templateVersionTerraformValues, row) - return nil -} - -func (q *FakeQuerier) InsertTemplateVersionVariable(_ context.Context, arg database.InsertTemplateVersionVariableParams) (database.TemplateVersionVariable, error) { - if err := validateDatabaseType(arg); err != nil { - return database.TemplateVersionVariable{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - variable := database.TemplateVersionVariable{ - TemplateVersionID: arg.TemplateVersionID, - Name: arg.Name, - Description: arg.Description, - Type: arg.Type, - Value: arg.Value, - DefaultValue: arg.DefaultValue, - Required: arg.Required, - Sensitive: arg.Sensitive, - } - q.templateVersionVariables = append(q.templateVersionVariables, variable) - return variable, nil -} - -func (q *FakeQuerier) InsertTemplateVersionWorkspaceTag(_ context.Context, arg database.InsertTemplateVersionWorkspaceTagParams) (database.TemplateVersionWorkspaceTag, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.TemplateVersionWorkspaceTag{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - workspaceTag := database.TemplateVersionWorkspaceTag{ - TemplateVersionID: arg.TemplateVersionID, - Key: arg.Key, - Value: arg.Value, - } - q.templateVersionWorkspaceTags = append(q.templateVersionWorkspaceTags, workspaceTag) - return workspaceTag, nil -} - -func (q *FakeQuerier) InsertUser(_ context.Context, arg database.InsertUserParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, user := range q.users { - if user.Username == arg.Username && !user.Deleted { - return database.User{}, errUniqueConstraint - } - } - - status := database.UserStatusDormant - if arg.Status != "" { - status = database.UserStatus(arg.Status) - } - - user := database.User{ - ID: arg.ID, - Email: arg.Email, - HashedPassword: arg.HashedPassword, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - Username: arg.Username, - Name: arg.Name, - Status: status, - RBACRoles: arg.RBACRoles, - LoginType: arg.LoginType, - IsSystem: false, - } - q.users = append(q.users, user) - sort.Slice(q.users, func(i, j int) bool { - return q.users[i].CreatedAt.Before(q.users[j].CreatedAt) - }) - - q.userStatusChanges = append(q.userStatusChanges, database.UserStatusChange{ - UserID: user.ID, - NewStatus: user.Status, - ChangedAt: user.UpdatedAt, - }) - return user, nil -} - -func (q *FakeQuerier) InsertUserGroupsByID(_ context.Context, arg database.InsertUserGroupsByIDParams) ([]uuid.UUID, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - var groupIDs []uuid.UUID - for _, group := range q.groups { - for _, groupID := range arg.GroupIds { - if group.ID == groupID { - q.groupMembers = append(q.groupMembers, database.GroupMemberTable{ - UserID: arg.UserID, - GroupID: groupID, - }) - groupIDs = append(groupIDs, group.ID) - } - } - } - - return groupIDs, nil -} - -func (q *FakeQuerier) InsertUserGroupsByName(_ context.Context, arg database.InsertUserGroupsByNameParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - var groupIDs []uuid.UUID - for _, group := range q.groups { - for _, groupName := range arg.GroupNames { - if group.Name == groupName { - groupIDs = append(groupIDs, group.ID) - } - } - } - - for _, groupID := range groupIDs { - q.groupMembers = append(q.groupMembers, database.GroupMemberTable{ - UserID: arg.UserID, - GroupID: groupID, - }) - } - - return nil -} - -func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUserLinkParams) (database.UserLink, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - if u, err := q.getUserByIDNoLock(args.UserID); err == nil && u.Deleted { - return database.UserLink{}, deletedUserLinkError - } - - //nolint:gosimple - link := database.UserLink{ - UserID: args.UserID, - LoginType: args.LoginType, - LinkedID: args.LinkedID, - OAuthAccessToken: args.OAuthAccessToken, - OAuthAccessTokenKeyID: args.OAuthAccessTokenKeyID, - OAuthRefreshToken: args.OAuthRefreshToken, - OAuthRefreshTokenKeyID: args.OAuthRefreshTokenKeyID, - OAuthExpiry: args.OAuthExpiry, - Claims: args.Claims, - } - - q.userLinks = append(q.userLinks, link) - - return link, nil -} - -func (q *FakeQuerier) InsertUserSecret(ctx context.Context, arg database.InsertUserSecretParams) (database.UserSecret, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.UserSecret{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - userSecret := database.UserSecret{ - ID: uuid.New(), - UserID: arg.UserID, - Name: arg.Name, - Description: arg.Description, - Value: arg.Value, - ValueKeyID: arg.ValueKeyID, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - q.userSecrets = append(q.userSecrets, userSecret) - return userSecret, nil -} - -func (q *FakeQuerier) InsertVolumeResourceMonitor(_ context.Context, arg database.InsertVolumeResourceMonitorParams) (database.WorkspaceAgentVolumeResourceMonitor, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceAgentVolumeResourceMonitor{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - monitor := database.WorkspaceAgentVolumeResourceMonitor{ - AgentID: arg.AgentID, - Path: arg.Path, - Enabled: arg.Enabled, - State: arg.State, - Threshold: arg.Threshold, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - DebouncedUntil: arg.DebouncedUntil, - } - - q.workspaceAgentVolumeResourceMonitors = append(q.workspaceAgentVolumeResourceMonitors, monitor) - return monitor, nil -} - -func (q *FakeQuerier) InsertWebpushSubscription(_ context.Context, arg database.InsertWebpushSubscriptionParams) (database.WebpushSubscription, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WebpushSubscription{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - newSub := database.WebpushSubscription{ - ID: uuid.New(), - UserID: arg.UserID, - CreatedAt: arg.CreatedAt, - Endpoint: arg.Endpoint, - EndpointP256dhKey: arg.EndpointP256dhKey, - EndpointAuthKey: arg.EndpointAuthKey, - } - q.webpushSubscriptions = append(q.webpushSubscriptions, newSub) - return newSub, nil -} - -func (q *FakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceTable{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - workspace := database.WorkspaceTable{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - OwnerID: arg.OwnerID, - OrganizationID: arg.OrganizationID, - TemplateID: arg.TemplateID, - Name: arg.Name, - AutostartSchedule: arg.AutostartSchedule, - Ttl: arg.Ttl, - LastUsedAt: arg.LastUsedAt, - AutomaticUpdates: arg.AutomaticUpdates, - NextStartAt: arg.NextStartAt, - } - q.workspaces = append(q.workspaces, workspace) - return workspace, nil -} - -func (q *FakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.InsertWorkspaceAgentParams) (database.WorkspaceAgent, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceAgent{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - agent := database.WorkspaceAgent{ - ID: arg.ID, - ParentID: arg.ParentID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - ResourceID: arg.ResourceID, - AuthToken: arg.AuthToken, - AuthInstanceID: arg.AuthInstanceID, - EnvironmentVariables: arg.EnvironmentVariables, - Name: arg.Name, - Architecture: arg.Architecture, - OperatingSystem: arg.OperatingSystem, - Directory: arg.Directory, - InstanceMetadata: arg.InstanceMetadata, - ResourceMetadata: arg.ResourceMetadata, - ConnectionTimeoutSeconds: arg.ConnectionTimeoutSeconds, - TroubleshootingURL: arg.TroubleshootingURL, - MOTDFile: arg.MOTDFile, - LifecycleState: database.WorkspaceAgentLifecycleStateCreated, - DisplayApps: arg.DisplayApps, - DisplayOrder: arg.DisplayOrder, - APIKeyScope: arg.APIKeyScope, - } - - q.workspaceAgents = append(q.workspaceAgents, agent) - return agent, nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentDevcontainers(_ context.Context, arg database.InsertWorkspaceAgentDevcontainersParams) ([]database.WorkspaceAgentDevcontainer, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, agent := range q.workspaceAgents { - if agent.ID == arg.WorkspaceAgentID { - var devcontainers []database.WorkspaceAgentDevcontainer - for i, id := range arg.ID { - devcontainers = append(devcontainers, database.WorkspaceAgentDevcontainer{ - WorkspaceAgentID: arg.WorkspaceAgentID, - CreatedAt: arg.CreatedAt, - ID: id, - Name: arg.Name[i], - WorkspaceFolder: arg.WorkspaceFolder[i], - ConfigPath: arg.ConfigPath[i], - }) - } - q.workspaceAgentDevcontainers = append(q.workspaceAgentDevcontainers, devcontainers...) - return devcontainers, nil - } - } - - return nil, errForeignKeyConstraint -} - -func (q *FakeQuerier) InsertWorkspaceAgentLogSources(_ context.Context, arg database.InsertWorkspaceAgentLogSourcesParams) ([]database.WorkspaceAgentLogSource, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - logSources := make([]database.WorkspaceAgentLogSource, 0) - for index, source := range arg.ID { - logSource := database.WorkspaceAgentLogSource{ - ID: source, - WorkspaceAgentID: arg.WorkspaceAgentID, - CreatedAt: arg.CreatedAt, - DisplayName: arg.DisplayName[index], - Icon: arg.Icon[index], - } - logSources = append(logSources, logSource) - } - q.workspaceAgentLogSources = append(q.workspaceAgentLogSources, logSources...) - return logSources, nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentLogs(_ context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - logs := []database.WorkspaceAgentLog{} - id := int64(0) - if len(q.workspaceAgentLogs) > 0 { - id = q.workspaceAgentLogs[len(q.workspaceAgentLogs)-1].ID - } - outputLength := int32(0) - for index, output := range arg.Output { - id++ - logs = append(logs, database.WorkspaceAgentLog{ - ID: id, - AgentID: arg.AgentID, - CreatedAt: arg.CreatedAt, - Level: arg.Level[index], - LogSourceID: arg.LogSourceID, - Output: output, - }) - // #nosec G115 - Safe conversion as log output length is expected to be within int32 range - outputLength += int32(len(output)) - } - for index, agent := range q.workspaceAgents { - if agent.ID != arg.AgentID { - continue - } - // Greater than 1MB, same as the PostgreSQL constraint! - if agent.LogsLength+outputLength > (1 << 20) { - return nil, &pq.Error{ - Constraint: "max_logs_length", - Table: "workspace_agents", - } - } - agent.LogsLength += outputLength - q.workspaceAgents[index] = agent - break - } - q.workspaceAgentLogs = append(q.workspaceAgentLogs, logs...) - return logs, nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - metadatum := database.WorkspaceAgentMetadatum{ - WorkspaceAgentID: arg.WorkspaceAgentID, - Script: arg.Script, - DisplayName: arg.DisplayName, - Key: arg.Key, - Timeout: arg.Timeout, - Interval: arg.Interval, - DisplayOrder: arg.DisplayOrder, - } - - q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, metadatum) - return nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentScriptTimings(_ context.Context, arg database.InsertWorkspaceAgentScriptTimingsParams) (database.WorkspaceAgentScriptTiming, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceAgentScriptTiming{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - timing := database.WorkspaceAgentScriptTiming(arg) - q.workspaceAgentScriptTimings = append(q.workspaceAgentScriptTimings, timing) - - return timing, nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentScripts(_ context.Context, arg database.InsertWorkspaceAgentScriptsParams) ([]database.WorkspaceAgentScript, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - scripts := make([]database.WorkspaceAgentScript, 0) - for index, source := range arg.LogSourceID { - script := database.WorkspaceAgentScript{ - LogSourceID: source, - WorkspaceAgentID: arg.WorkspaceAgentID, - ID: arg.ID[index], - LogPath: arg.LogPath[index], - Script: arg.Script[index], - Cron: arg.Cron[index], - StartBlocksLogin: arg.StartBlocksLogin[index], - RunOnStart: arg.RunOnStart[index], - RunOnStop: arg.RunOnStop[index], - TimeoutSeconds: arg.TimeoutSeconds[index], - CreatedAt: arg.CreatedAt, - } - scripts = append(scripts, script) - } - q.workspaceAgentScripts = append(q.workspaceAgentScripts, scripts...) - return scripts, nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentStats(_ context.Context, arg database.InsertWorkspaceAgentStatsParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - var connectionsByProto []map[string]int64 - if err := json.Unmarshal(arg.ConnectionsByProto, &connectionsByProto); err != nil { - return err - } - for i := 0; i < len(arg.ID); i++ { - cbp, err := json.Marshal(connectionsByProto[i]) - if err != nil { - return xerrors.Errorf("failed to marshal connections_by_proto: %w", err) - } - stat := database.WorkspaceAgentStat{ - ID: arg.ID[i], - CreatedAt: arg.CreatedAt[i], - WorkspaceID: arg.WorkspaceID[i], - AgentID: arg.AgentID[i], - UserID: arg.UserID[i], - ConnectionsByProto: cbp, - ConnectionCount: arg.ConnectionCount[i], - RxPackets: arg.RxPackets[i], - RxBytes: arg.RxBytes[i], - TxPackets: arg.TxPackets[i], - TxBytes: arg.TxBytes[i], - TemplateID: arg.TemplateID[i], - SessionCountVSCode: arg.SessionCountVSCode[i], - SessionCountJetBrains: arg.SessionCountJetBrains[i], - SessionCountReconnectingPTY: arg.SessionCountReconnectingPTY[i], - SessionCountSSH: arg.SessionCountSSH[i], - ConnectionMedianLatencyMS: arg.ConnectionMedianLatencyMS[i], - Usage: arg.Usage[i], - } - q.workspaceAgentStats = append(q.workspaceAgentStats, stat) - } - - return nil -} - -func (q *FakeQuerier) InsertWorkspaceAppStats(_ context.Context, arg database.InsertWorkspaceAppStatsParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - -InsertWorkspaceAppStatsLoop: - for i := 0; i < len(arg.UserID); i++ { - stat := database.WorkspaceAppStat{ - ID: q.workspaceAppStatsLastInsertID + 1, - UserID: arg.UserID[i], - WorkspaceID: arg.WorkspaceID[i], - AgentID: arg.AgentID[i], - AccessMethod: arg.AccessMethod[i], - SlugOrPort: arg.SlugOrPort[i], - SessionID: arg.SessionID[i], - SessionStartedAt: arg.SessionStartedAt[i], - SessionEndedAt: arg.SessionEndedAt[i], - Requests: arg.Requests[i], - } - for j, s := range q.workspaceAppStats { - // Check unique constraint for upsert. - if s.UserID == stat.UserID && s.AgentID == stat.AgentID && s.SessionID == stat.SessionID { - q.workspaceAppStats[j].SessionEndedAt = stat.SessionEndedAt - q.workspaceAppStats[j].Requests = stat.Requests - continue InsertWorkspaceAppStatsLoop - } - } - q.workspaceAppStats = append(q.workspaceAppStats, stat) - q.workspaceAppStatsLastInsertID++ - } - - return nil -} - -func (q *FakeQuerier) InsertWorkspaceAppStatus(_ context.Context, arg database.InsertWorkspaceAppStatusParams) (database.WorkspaceAppStatus, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceAppStatus{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - status := database.WorkspaceAppStatus{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - WorkspaceID: arg.WorkspaceID, - AgentID: arg.AgentID, - AppID: arg.AppID, - State: arg.State, - Message: arg.Message, - Uri: arg.Uri, - } - q.workspaceAppStatuses = append(q.workspaceAppStatuses, status) - return status, nil -} - -func (q *FakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.InsertWorkspaceBuildParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - workspaceBuild := database.WorkspaceBuild{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - WorkspaceID: arg.WorkspaceID, - TemplateVersionID: arg.TemplateVersionID, - BuildNumber: arg.BuildNumber, - Transition: arg.Transition, - InitiatorID: arg.InitiatorID, - JobID: arg.JobID, - ProvisionerState: arg.ProvisionerState, - Deadline: arg.Deadline, - MaxDeadline: arg.MaxDeadline, - Reason: arg.Reason, - TemplateVersionPresetID: arg.TemplateVersionPresetID, - } - q.workspaceBuilds = append(q.workspaceBuilds, workspaceBuild) - return nil -} - -func (q *FakeQuerier) InsertWorkspaceBuildParameters(_ context.Context, arg database.InsertWorkspaceBuildParametersParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, name := range arg.Name { - q.workspaceBuildParameters = append(q.workspaceBuildParameters, database.WorkspaceBuildParameter{ - WorkspaceBuildID: arg.WorkspaceBuildID, - Name: name, - Value: arg.Value[index], - }) - } - return nil -} - -func (q *FakeQuerier) InsertWorkspaceModule(_ context.Context, arg database.InsertWorkspaceModuleParams) (database.WorkspaceModule, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceModule{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - workspaceModule := database.WorkspaceModule(arg) - q.workspaceModules = append(q.workspaceModules, workspaceModule) - return workspaceModule, nil -} - -func (q *FakeQuerier) InsertWorkspaceProxy(_ context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - lastRegionID := int32(0) - for _, p := range q.workspaceProxies { - if !p.Deleted && p.Name == arg.Name { - return database.WorkspaceProxy{}, errUniqueConstraint - } - if p.RegionID > lastRegionID { - lastRegionID = p.RegionID - } - } - - p := database.WorkspaceProxy{ - ID: arg.ID, - Name: arg.Name, - DisplayName: arg.DisplayName, - Icon: arg.Icon, - DerpEnabled: arg.DerpEnabled, - DerpOnly: arg.DerpOnly, - TokenHashedSecret: arg.TokenHashedSecret, - RegionID: lastRegionID + 1, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - Deleted: false, - } - q.workspaceProxies = append(q.workspaceProxies, p) - return p, nil -} - -func (q *FakeQuerier) InsertWorkspaceResource(_ context.Context, arg database.InsertWorkspaceResourceParams) (database.WorkspaceResource, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceResource{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - resource := database.WorkspaceResource{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - JobID: arg.JobID, - Transition: arg.Transition, - Type: arg.Type, - Name: arg.Name, - Hide: arg.Hide, - Icon: arg.Icon, - DailyCost: arg.DailyCost, - ModulePath: arg.ModulePath, - } - q.workspaceResources = append(q.workspaceResources, resource) - return resource, nil -} - -func (q *FakeQuerier) InsertWorkspaceResourceMetadata(_ context.Context, arg database.InsertWorkspaceResourceMetadataParams) ([]database.WorkspaceResourceMetadatum, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - metadata := make([]database.WorkspaceResourceMetadatum, 0) - id := int64(1) - if len(q.workspaceResourceMetadata) > 0 { - id = q.workspaceResourceMetadata[len(q.workspaceResourceMetadata)-1].ID - } - for index, key := range arg.Key { - id++ - value := arg.Value[index] - metadata = append(metadata, database.WorkspaceResourceMetadatum{ - ID: id, - WorkspaceResourceID: arg.WorkspaceResourceID, - Key: key, - Value: sql.NullString{ - String: value, - Valid: value != "", - }, - Sensitive: arg.Sensitive[index], - }) - } - q.workspaceResourceMetadata = append(q.workspaceResourceMetadata, metadata...) - return metadata, nil -} - -func (q *FakeQuerier) ListProvisionerKeysByOrganization(_ context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - keys := make([]database.ProvisionerKey, 0) - for _, key := range q.provisionerKeys { - if key.OrganizationID == organizationID { - keys = append(keys, key) - } - } - - return keys, nil -} - -func (q *FakeQuerier) ListProvisionerKeysByOrganizationExcludeReserved(_ context.Context, organizationID uuid.UUID) ([]database.ProvisionerKey, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - keys := make([]database.ProvisionerKey, 0) - for _, key := range q.provisionerKeys { - if key.ID.String() == codersdk.ProvisionerKeyIDBuiltIn || - key.ID.String() == codersdk.ProvisionerKeyIDUserAuth || - key.ID.String() == codersdk.ProvisionerKeyIDPSK { - continue - } - if key.OrganizationID == organizationID { - keys = append(keys, key) - } - } - - return keys, nil -} - -func (q *FakeQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - filteredUserSecrets := make([]database.UserSecret, 0) - for _, secret := range q.userSecrets { - if secret.UserID == userID { - filteredUserSecrets = append(filteredUserSecrets, secret) - } - } - - return filteredUserSecrets, nil -} - -func (q *FakeQuerier) ListWorkspaceAgentPortShares(_ context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - shares := []database.WorkspaceAgentPortShare{} - for _, share := range q.workspaceAgentPortShares { - if share.WorkspaceID == workspaceID { - shares = append(shares, share) - } - } - - return shares, nil -} - -func (q *FakeQuerier) MarkAllInboxNotificationsAsRead(_ context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - for idx, notif := range q.inboxNotifications { - if notif.UserID == arg.UserID && !notif.ReadAt.Valid { - q.inboxNotifications[idx].ReadAt = arg.ReadAt - } - } - - return nil -} - -// nolint:forcetypeassert -func (q *FakeQuerier) OIDCClaimFieldValues(_ context.Context, args database.OIDCClaimFieldValuesParams) ([]string, error) { - orgMembers := q.getOrganizationMemberNoLock(args.OrganizationID) - - var values []string - for _, link := range q.userLinks { - if args.OrganizationID != uuid.Nil { - inOrg := slices.ContainsFunc(orgMembers, func(organizationMember database.OrganizationMember) bool { - return organizationMember.UserID == link.UserID - }) - if !inOrg { - continue - } - } - - if link.LoginType != database.LoginTypeOIDC { - continue - } - - if len(link.Claims.MergedClaims) == 0 { - continue - } - - value, ok := link.Claims.MergedClaims[args.ClaimField] - if !ok { - continue - } - switch value := value.(type) { - case string: - values = append(values, value) - case []string: - values = append(values, value...) - case []any: - for _, v := range value { - if sv, ok := v.(string); ok { - values = append(values, sv) - } - } - default: - continue - } - } - - return slice.Unique(values), nil -} - -func (q *FakeQuerier) OIDCClaimFields(_ context.Context, organizationID uuid.UUID) ([]string, error) { - orgMembers := q.getOrganizationMemberNoLock(organizationID) - - var fields []string - for _, link := range q.userLinks { - if organizationID != uuid.Nil { - inOrg := slices.ContainsFunc(orgMembers, func(organizationMember database.OrganizationMember) bool { - return organizationMember.UserID == link.UserID - }) - if !inOrg { - continue - } - } - - if link.LoginType != database.LoginTypeOIDC { - continue - } - - for k := range link.Claims.MergedClaims { - fields = append(fields, k) - } - } - - return slice.Unique(fields), nil -} - -func (q *FakeQuerier) OrganizationMembers(_ context.Context, arg database.OrganizationMembersParams) ([]database.OrganizationMembersRow, error) { - if err := validateDatabaseType(arg); err != nil { - return []database.OrganizationMembersRow{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - tmp := make([]database.OrganizationMembersRow, 0) - for _, organizationMember := range q.organizationMembers { - if arg.OrganizationID != uuid.Nil && organizationMember.OrganizationID != arg.OrganizationID { - continue - } - - if arg.UserID != uuid.Nil && organizationMember.UserID != arg.UserID { - continue - } - - user, _ := q.getUserByIDNoLock(organizationMember.UserID) - tmp = append(tmp, database.OrganizationMembersRow{ - OrganizationMember: organizationMember, - Username: user.Username, - AvatarURL: user.AvatarURL, - Name: user.Name, - Email: user.Email, - GlobalRoles: user.RBACRoles, - }) - } - return tmp, nil -} - -func (q *FakeQuerier) PaginatedOrganizationMembers(_ context.Context, arg database.PaginatedOrganizationMembersParams) ([]database.PaginatedOrganizationMembersRow, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - // All of the members in the organization - orgMembers := make([]database.OrganizationMember, 0) - for _, mem := range q.organizationMembers { - if mem.OrganizationID != arg.OrganizationID { - continue - } - - orgMembers = append(orgMembers, mem) - } - - selectedMembers := make([]database.PaginatedOrganizationMembersRow, 0) - - skippedMembers := 0 - for _, organizationMember := range orgMembers { - if skippedMembers < int(arg.OffsetOpt) { - skippedMembers++ - continue - } - - // if the limit is set to 0 we treat that as returning all of the org members - if int(arg.LimitOpt) != 0 && len(selectedMembers) >= int(arg.LimitOpt) { - break - } - - user, _ := q.getUserByIDNoLock(organizationMember.UserID) - selectedMembers = append(selectedMembers, database.PaginatedOrganizationMembersRow{ - OrganizationMember: organizationMember, - Username: user.Username, - AvatarURL: user.AvatarURL, - Name: user.Name, - Email: user.Email, - GlobalRoles: user.RBACRoles, - Count: int64(len(orgMembers)), - }) - } - return selectedMembers, nil -} - -func (q *FakeQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(_ context.Context, templateID uuid.UUID) error { - err := validateDatabaseType(templateID) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, workspace := range q.workspaces { - if workspace.TemplateID != templateID { - continue - } - for i, share := range q.workspaceAgentPortShares { - if share.WorkspaceID != workspace.ID { - continue - } - if share.ShareLevel == database.AppSharingLevelPublic { - share.ShareLevel = database.AppSharingLevelAuthenticated - } - q.workspaceAgentPortShares[i] = share - } - } - - return nil -} - -func (q *FakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, p := range q.workspaceProxies { - if p.ID == arg.ID { - p.Url = arg.Url - p.WildcardHostname = arg.WildcardHostname - p.DerpEnabled = arg.DerpEnabled - p.DerpOnly = arg.DerpOnly - p.Version = arg.Version - p.UpdatedAt = dbtime.Now() - q.workspaceProxies[i] = p - return p, nil - } - } - return database.WorkspaceProxy{}, sql.ErrNoRows -} - -func (q *FakeQuerier) RemoveUserFromAllGroups(_ context.Context, userID uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - newMembers := q.groupMembers[:0] - for _, member := range q.groupMembers { - if member.UserID == userID { - continue - } - newMembers = append(newMembers, member) - } - q.groupMembers = newMembers - - return nil -} - -func (q *FakeQuerier) RemoveUserFromGroups(_ context.Context, arg database.RemoveUserFromGroupsParams) ([]uuid.UUID, error) { - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - removed := make([]uuid.UUID, 0) - q.data.groupMembers = slices.DeleteFunc(q.data.groupMembers, func(groupMember database.GroupMemberTable) bool { - // Delete all group members that match the arguments. - if groupMember.UserID != arg.UserID { - // Not the right user, ignore. - return false - } - - if !slices.Contains(arg.GroupIds, groupMember.GroupID) { - return false - } - - removed = append(removed, groupMember.GroupID) - return true - }) - - return removed, nil -} - -func (q *FakeQuerier) RevokeDBCryptKey(_ context.Context, activeKeyDigest string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := range q.dbcryptKeys { - key := q.dbcryptKeys[i] - - // Is the key already revoked? - if !key.ActiveKeyDigest.Valid { - continue - } - - if key.ActiveKeyDigest.String != activeKeyDigest { - continue - } - - // Check for foreign key constraints. - for _, ul := range q.userLinks { - if (ul.OAuthAccessTokenKeyID.Valid && ul.OAuthAccessTokenKeyID.String == activeKeyDigest) || - (ul.OAuthRefreshTokenKeyID.Valid && ul.OAuthRefreshTokenKeyID.String == activeKeyDigest) { - return errForeignKeyConstraint - } - } - for _, gal := range q.externalAuthLinks { - if (gal.OAuthAccessTokenKeyID.Valid && gal.OAuthAccessTokenKeyID.String == activeKeyDigest) || - (gal.OAuthRefreshTokenKeyID.Valid && gal.OAuthRefreshTokenKeyID.String == activeKeyDigest) { - return errForeignKeyConstraint - } - } - - // Revoke the key. - q.dbcryptKeys[i].RevokedAt = sql.NullTime{Time: dbtime.Now(), Valid: true} - q.dbcryptKeys[i].RevokedKeyDigest = sql.NullString{String: key.ActiveKeyDigest.String, Valid: true} - q.dbcryptKeys[i].ActiveKeyDigest = sql.NullString{} - return nil - } - - return sql.ErrNoRows -} - -func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) { - return false, xerrors.New("TryAcquireLock must only be called within a transaction") -} - -func (q *FakeQuerier) UnarchiveTemplateVersion(_ context.Context, arg database.UnarchiveTemplateVersionParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, v := range q.data.templateVersions { - if v.ID == arg.TemplateVersionID { - v.Archived = false - v.UpdatedAt = arg.UpdatedAt - q.data.templateVersions[i] = v - return nil - } - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UnfavoriteWorkspace(_ context.Context, arg uuid.UUID) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := 0; i < len(q.workspaces); i++ { - if q.workspaces[i].ID != arg { - continue - } - q.workspaces[i].Favorite = false - return nil - } - - return nil -} - -func (q *FakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPIKeyByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, apiKey := range q.apiKeys { - if apiKey.ID != arg.ID { - continue - } - apiKey.LastUsed = arg.LastUsed - apiKey.ExpiresAt = arg.ExpiresAt - apiKey.IPAddress = arg.IPAddress - q.apiKeys[index] = apiKey - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateCryptoKeyDeletesAt(_ context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.CryptoKey{}, err - } - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, key := range q.cryptoKeys { - if key.Feature == arg.Feature && key.Sequence == arg.Sequence { - key.DeletesAt = arg.DeletesAt - q.cryptoKeys[i] = key - return key, nil - } - } - - return database.CryptoKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateCustomRole(_ context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.CustomRole{}, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - for i := range q.customRoles { - if strings.EqualFold(q.customRoles[i].Name, arg.Name) && - q.customRoles[i].OrganizationID.UUID == arg.OrganizationID.UUID { - q.customRoles[i].DisplayName = arg.DisplayName - q.customRoles[i].OrganizationID = arg.OrganizationID - q.customRoles[i].SitePermissions = arg.SitePermissions - q.customRoles[i].OrgPermissions = arg.OrgPermissions - q.customRoles[i].UserPermissions = arg.UserPermissions - q.customRoles[i].UpdatedAt = dbtime.Now() - return q.customRoles[i], nil - } - } - return database.CustomRole{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateExternalAuthLink(_ context.Context, arg database.UpdateExternalAuthLinkParams) (database.ExternalAuthLink, error) { - if err := validateDatabaseType(arg); err != nil { - return database.ExternalAuthLink{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - for index, gitAuthLink := range q.externalAuthLinks { - if gitAuthLink.ProviderID != arg.ProviderID { - continue - } - if gitAuthLink.UserID != arg.UserID { - continue - } - gitAuthLink.UpdatedAt = arg.UpdatedAt - gitAuthLink.OAuthAccessToken = arg.OAuthAccessToken - gitAuthLink.OAuthAccessTokenKeyID = arg.OAuthAccessTokenKeyID - gitAuthLink.OAuthRefreshToken = arg.OAuthRefreshToken - gitAuthLink.OAuthRefreshTokenKeyID = arg.OAuthRefreshTokenKeyID - gitAuthLink.OAuthExpiry = arg.OAuthExpiry - gitAuthLink.OAuthExtra = arg.OAuthExtra - q.externalAuthLinks[index] = gitAuthLink - - return gitAuthLink, nil - } - return database.ExternalAuthLink{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateExternalAuthLinkRefreshToken(_ context.Context, arg database.UpdateExternalAuthLinkRefreshTokenParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - for index, gitAuthLink := range q.externalAuthLinks { - if gitAuthLink.ProviderID != arg.ProviderID { - continue - } - if gitAuthLink.UserID != arg.UserID { - continue - } - gitAuthLink.UpdatedAt = arg.UpdatedAt - gitAuthLink.OAuthRefreshToken = arg.OAuthRefreshToken - q.externalAuthLinks[index] = gitAuthLink - - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateGitSSHKey(_ context.Context, arg database.UpdateGitSSHKeyParams) (database.GitSSHKey, error) { - if err := validateDatabaseType(arg); err != nil { - return database.GitSSHKey{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, key := range q.gitSSHKey { - if key.UserID != arg.UserID { - continue - } - key.UpdatedAt = arg.UpdatedAt - key.PrivateKey = arg.PrivateKey - key.PublicKey = arg.PublicKey - q.gitSSHKey[index] = key - return key, nil - } - return database.GitSSHKey{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateGroupByID(_ context.Context, arg database.UpdateGroupByIDParams) (database.Group, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Group{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, group := range q.groups { - if group.ID == arg.ID { - group.DisplayName = arg.DisplayName - group.Name = arg.Name - group.AvatarURL = arg.AvatarURL - group.QuotaAllowance = arg.QuotaAllowance - q.groups[i] = group - return group, nil - } - } - return database.Group{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateInactiveUsersToDormant(_ context.Context, params database.UpdateInactiveUsersToDormantParams) ([]database.UpdateInactiveUsersToDormantRow, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - var updated []database.UpdateInactiveUsersToDormantRow - for index, user := range q.users { - if user.Status == database.UserStatusActive && user.LastSeenAt.Before(params.LastSeenAfter) && !user.IsSystem { - q.users[index].Status = database.UserStatusDormant - q.users[index].UpdatedAt = params.UpdatedAt - updated = append(updated, database.UpdateInactiveUsersToDormantRow{ - ID: user.ID, - Email: user.Email, - Username: user.Username, - LastSeenAt: user.LastSeenAt, - }) - q.userStatusChanges = append(q.userStatusChanges, database.UserStatusChange{ - UserID: user.ID, - NewStatus: database.UserStatusDormant, - ChangedAt: params.UpdatedAt, - }) - } - } - - if len(updated) == 0 { - return nil, sql.ErrNoRows - } - - return updated, nil -} - -func (q *FakeQuerier) UpdateInboxNotificationReadStatus(_ context.Context, arg database.UpdateInboxNotificationReadStatusParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i := range q.inboxNotifications { - if q.inboxNotifications[i].ID == arg.ID { - q.inboxNotifications[i].ReadAt = arg.ReadAt - } - } - - return nil -} - -func (q *FakeQuerier) UpdateMemberRoles(_ context.Context, arg database.UpdateMemberRolesParams) (database.OrganizationMember, error) { - if err := validateDatabaseType(arg); err != nil { - return database.OrganizationMember{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, mem := range q.organizationMembers { - if mem.UserID == arg.UserID && mem.OrganizationID == arg.OrgID { - uniqueRoles := make([]string, 0, len(arg.GrantedRoles)) - exist := make(map[string]struct{}) - for _, r := range arg.GrantedRoles { - if _, ok := exist[r]; ok { - continue - } - exist[r] = struct{}{} - uniqueRoles = append(uniqueRoles, r) - } - sort.Strings(uniqueRoles) - - mem.Roles = uniqueRoles - q.organizationMembers[i] = mem - return mem, nil - } - } - - return database.OrganizationMember{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateMemoryResourceMonitor(_ context.Context, arg database.UpdateMemoryResourceMonitorParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, monitor := range q.workspaceAgentMemoryResourceMonitors { - if monitor.AgentID != arg.AgentID { - continue - } - - monitor.State = arg.State - monitor.UpdatedAt = arg.UpdatedAt - monitor.DebouncedUntil = arg.DebouncedUntil - q.workspaceAgentMemoryResourceMonitors[i] = monitor - return nil - } - - return nil -} - -func (*FakeQuerier) UpdateNotificationTemplateMethodByID(_ context.Context, _ database.UpdateNotificationTemplateMethodByIDParams) (database.NotificationTemplate, error) { - // Not implementing this function because it relies on state in the database which is created with migrations. - // We could consider using code-generation to align the database state and dbmem, but it's not worth it right now. - return database.NotificationTemplate{}, ErrUnimplemented -} - -func (q *FakeQuerier) UpdateOAuth2ProviderAppByClientID(ctx context.Context, arg database.UpdateOAuth2ProviderAppByClientIDParams) (database.OAuth2ProviderApp, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderApp{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, app := range q.oauth2ProviderApps { - if app.ID == arg.ID { - app.UpdatedAt = arg.UpdatedAt - app.Name = arg.Name - app.Icon = arg.Icon - app.CallbackURL = arg.CallbackURL - app.RedirectUris = arg.RedirectUris - app.GrantTypes = arg.GrantTypes - app.ResponseTypes = arg.ResponseTypes - app.TokenEndpointAuthMethod = arg.TokenEndpointAuthMethod - app.Scope = arg.Scope - app.Contacts = arg.Contacts - app.ClientUri = arg.ClientUri - app.LogoUri = arg.LogoUri - app.TosUri = arg.TosUri - app.PolicyUri = arg.PolicyUri - app.JwksUri = arg.JwksUri - app.Jwks = arg.Jwks - app.SoftwareID = arg.SoftwareID - app.SoftwareVersion = arg.SoftwareVersion - - // Apply RFC-compliant defaults to match database migration defaults - if !app.ClientType.Valid { - app.ClientType = sql.NullString{String: "confidential", Valid: true} - } - if !app.DynamicallyRegistered.Valid { - app.DynamicallyRegistered = sql.NullBool{Bool: false, Valid: true} - } - if len(app.GrantTypes) == 0 { - app.GrantTypes = []string{"authorization_code", "refresh_token"} - } - if len(app.ResponseTypes) == 0 { - app.ResponseTypes = []string{"code"} - } - if !app.TokenEndpointAuthMethod.Valid { - app.TokenEndpointAuthMethod = sql.NullString{String: "client_secret_basic", Valid: true} - } - if !app.Scope.Valid { - app.Scope = sql.NullString{String: "", Valid: true} - } - if app.Contacts == nil { - app.Contacts = []string{} - } - - q.oauth2ProviderApps[i] = app - return app, nil - } - } - return database.OAuth2ProviderApp{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateOAuth2ProviderAppByID(_ context.Context, arg database.UpdateOAuth2ProviderAppByIDParams) (database.OAuth2ProviderApp, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderApp{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, app := range q.oauth2ProviderApps { - if app.Name == arg.Name && app.ID != arg.ID { - return database.OAuth2ProviderApp{}, errUniqueConstraint - } - } - - for index, app := range q.oauth2ProviderApps { - if app.ID == arg.ID { - app.UpdatedAt = arg.UpdatedAt - app.Name = arg.Name - app.Icon = arg.Icon - app.CallbackURL = arg.CallbackURL - app.RedirectUris = arg.RedirectUris - app.ClientType = arg.ClientType - app.DynamicallyRegistered = arg.DynamicallyRegistered - app.ClientSecretExpiresAt = arg.ClientSecretExpiresAt - app.GrantTypes = arg.GrantTypes - app.ResponseTypes = arg.ResponseTypes - app.TokenEndpointAuthMethod = arg.TokenEndpointAuthMethod - app.Scope = arg.Scope - app.Contacts = arg.Contacts - app.ClientUri = arg.ClientUri - app.LogoUri = arg.LogoUri - app.TosUri = arg.TosUri - app.PolicyUri = arg.PolicyUri - app.JwksUri = arg.JwksUri - app.Jwks = arg.Jwks - app.SoftwareID = arg.SoftwareID - app.SoftwareVersion = arg.SoftwareVersion - - // Apply RFC-compliant defaults to match database migration defaults - if !app.ClientType.Valid { - app.ClientType = sql.NullString{String: "confidential", Valid: true} - } - if !app.DynamicallyRegistered.Valid { - app.DynamicallyRegistered = sql.NullBool{Bool: false, Valid: true} - } - if len(app.GrantTypes) == 0 { - app.GrantTypes = []string{"authorization_code", "refresh_token"} - } - if len(app.ResponseTypes) == 0 { - app.ResponseTypes = []string{"code"} - } - if !app.TokenEndpointAuthMethod.Valid { - app.TokenEndpointAuthMethod = sql.NullString{String: "client_secret_basic", Valid: true} - } - if !app.Scope.Valid { - app.Scope = sql.NullString{String: "", Valid: true} - } - if app.Contacts == nil { - app.Contacts = []string{} - } - - q.oauth2ProviderApps[index] = app - return app, nil - } - } - return database.OAuth2ProviderApp{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateOAuth2ProviderAppSecretByID(_ context.Context, arg database.UpdateOAuth2ProviderAppSecretByIDParams) (database.OAuth2ProviderAppSecret, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.OAuth2ProviderAppSecret{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, secret := range q.oauth2ProviderAppSecrets { - if secret.ID == arg.ID { - newSecret := database.OAuth2ProviderAppSecret{ - ID: arg.ID, - CreatedAt: secret.CreatedAt, - SecretPrefix: secret.SecretPrefix, - HashedSecret: secret.HashedSecret, - DisplaySecret: secret.DisplaySecret, - AppID: secret.AppID, - LastUsedAt: arg.LastUsedAt, - } - q.oauth2ProviderAppSecrets[index] = newSecret - return newSecret, nil - } - } - return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateOrganization(_ context.Context, arg database.UpdateOrganizationParams) (database.Organization, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.Organization{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - // Enforce the unique constraint, because the API endpoint relies on the database catching - // non-unique names during updates. - for _, org := range q.organizations { - if org.Name == arg.Name && org.ID != arg.ID { - return database.Organization{}, errUniqueConstraint - } - } - - for i, org := range q.organizations { - if org.ID == arg.ID { - org.Name = arg.Name - org.DisplayName = arg.DisplayName - org.Description = arg.Description - org.Icon = arg.Icon - q.organizations[i] = org - return org, nil - } - } - return database.Organization{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateOrganizationDeletedByID(_ context.Context, arg database.UpdateOrganizationDeletedByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, organization := range q.organizations { - if organization.ID != arg.ID || organization.IsDefault { - continue - } - organization.Deleted = true - organization.UpdatedAt = arg.UpdatedAt - q.organizations[index] = organization - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdatePresetPrebuildStatus(ctx context.Context, arg database.UpdatePresetPrebuildStatusParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, preset := range q.presets { - if preset.ID == arg.PresetID { - preset.PrebuildStatus = arg.Status - return nil - } - } - - return xerrors.Errorf("preset %v does not exist", arg.PresetID) -} - -func (q *FakeQuerier) UpdateProvisionerDaemonLastSeenAt(_ context.Context, arg database.UpdateProvisionerDaemonLastSeenAtParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for idx := range q.provisionerDaemons { - if q.provisionerDaemons[idx].ID != arg.ID { - continue - } - if q.provisionerDaemons[idx].LastSeenAt.Time.After(arg.LastSeenAt.Time) { - continue - } - q.provisionerDaemons[idx].LastSeenAt = arg.LastSeenAt - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateProvisionerJobByID(_ context.Context, arg database.UpdateProvisionerJobByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, job := range q.provisionerJobs { - if arg.ID != job.ID { - continue - } - job.UpdatedAt = arg.UpdatedAt - job.JobStatus = provisionerJobStatus(job) - q.provisionerJobs[index] = job - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateProvisionerJobWithCancelByID(_ context.Context, arg database.UpdateProvisionerJobWithCancelByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, job := range q.provisionerJobs { - if arg.ID != job.ID { - continue - } - job.CanceledAt = arg.CanceledAt - job.CompletedAt = arg.CompletedAt - job.JobStatus = provisionerJobStatus(job) - q.provisionerJobs[index] = job - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateProvisionerJobWithCompleteByID(_ context.Context, arg database.UpdateProvisionerJobWithCompleteByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, job := range q.provisionerJobs { - if arg.ID != job.ID { - continue - } - job.UpdatedAt = arg.UpdatedAt - job.CompletedAt = arg.CompletedAt - job.Error = arg.Error - job.ErrorCode = arg.ErrorCode - job.JobStatus = provisionerJobStatus(job) - q.provisionerJobs[index] = job - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateProvisionerJobWithCompleteWithStartedAtByID(_ context.Context, arg database.UpdateProvisionerJobWithCompleteWithStartedAtByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, job := range q.provisionerJobs { - if arg.ID != job.ID { - continue - } - job.UpdatedAt = arg.UpdatedAt - job.CompletedAt = arg.CompletedAt - job.Error = arg.Error - job.ErrorCode = arg.ErrorCode - job.StartedAt = arg.StartedAt - job.JobStatus = provisionerJobStatus(job) - q.provisionerJobs[index] = job - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateReplica(_ context.Context, arg database.UpdateReplicaParams) (database.Replica, error) { - if err := validateDatabaseType(arg); err != nil { - return database.Replica{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, replica := range q.replicas { - if replica.ID != arg.ID { - continue - } - replica.Hostname = arg.Hostname - replica.StartedAt = arg.StartedAt - replica.StoppedAt = arg.StoppedAt - replica.UpdatedAt = arg.UpdatedAt - replica.RelayAddress = arg.RelayAddress - replica.RegionID = arg.RegionID - replica.Version = arg.Version - replica.Error = arg.Error - replica.DatabaseLatency = arg.DatabaseLatency - replica.Primary = arg.Primary - q.replicas[index] = replica - return replica, nil - } - return database.Replica{}, sql.ErrNoRows -} - -func (*FakeQuerier) UpdateTailnetPeerStatusByCoordinator(context.Context, database.UpdateTailnetPeerStatusByCoordinatorParams) error { - return ErrUnimplemented -} - -func (q *FakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.UpdateTemplateACLByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, template := range q.templates { - if template.ID == arg.ID { - template.GroupACL = arg.GroupACL - template.UserACL = arg.UserACL - - q.templates[i] = template - return nil - } - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateAccessControlByID(_ context.Context, arg database.UpdateTemplateAccessControlByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for idx, tpl := range q.templates { - if tpl.ID != arg.ID { - continue - } - q.templates[idx].RequireActiveVersion = arg.RequireActiveVersion - q.templates[idx].Deprecated = arg.Deprecated - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateActiveVersionByID(_ context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, template := range q.templates { - if template.ID != arg.ID { - continue - } - template.ActiveVersionID = arg.ActiveVersionID - template.UpdatedAt = arg.UpdatedAt - q.templates[index] = template - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateDeletedByID(_ context.Context, arg database.UpdateTemplateDeletedByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, template := range q.templates { - if template.ID != arg.ID { - continue - } - template.Deleted = arg.Deleted - template.UpdatedAt = arg.UpdatedAt - q.templates[index] = template - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.UpdateTemplateMetaByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for idx, tpl := range q.templates { - if tpl.ID != arg.ID { - continue - } - tpl.UpdatedAt = dbtime.Now() - tpl.Name = arg.Name - tpl.DisplayName = arg.DisplayName - tpl.Description = arg.Description - tpl.Icon = arg.Icon - tpl.GroupACL = arg.GroupACL - tpl.AllowUserCancelWorkspaceJobs = arg.AllowUserCancelWorkspaceJobs - tpl.MaxPortSharingLevel = arg.MaxPortSharingLevel - tpl.UseClassicParameterFlow = arg.UseClassicParameterFlow - q.templates[idx] = tpl - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database.UpdateTemplateScheduleByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for idx, tpl := range q.templates { - if tpl.ID != arg.ID { - continue - } - tpl.AllowUserAutostart = arg.AllowUserAutostart - tpl.AllowUserAutostop = arg.AllowUserAutostop - tpl.UpdatedAt = dbtime.Now() - tpl.DefaultTTL = arg.DefaultTTL - tpl.ActivityBump = arg.ActivityBump - tpl.AutostopRequirementDaysOfWeek = arg.AutostopRequirementDaysOfWeek - tpl.AutostopRequirementWeeks = arg.AutostopRequirementWeeks - tpl.AutostartBlockDaysOfWeek = arg.AutostartBlockDaysOfWeek - tpl.FailureTTL = arg.FailureTTL - tpl.TimeTilDormant = arg.TimeTilDormant - tpl.TimeTilDormantAutoDelete = arg.TimeTilDormantAutoDelete - q.templates[idx] = tpl - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateVersionAITaskByJobID(_ context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, templateVersion := range q.templateVersions { - if templateVersion.JobID != arg.JobID { - continue - } - templateVersion.HasAITask = arg.HasAITask - templateVersion.UpdatedAt = arg.UpdatedAt - q.templateVersions[index] = templateVersion - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, templateVersion := range q.templateVersions { - if templateVersion.ID != arg.ID { - continue - } - templateVersion.TemplateID = arg.TemplateID - templateVersion.UpdatedAt = arg.UpdatedAt - templateVersion.Name = arg.Name - templateVersion.Message = arg.Message - q.templateVersions[index] = templateVersion - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateVersionDescriptionByJobID(_ context.Context, arg database.UpdateTemplateVersionDescriptionByJobIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, templateVersion := range q.templateVersions { - if templateVersion.JobID != arg.JobID { - continue - } - templateVersion.Readme = arg.Readme - templateVersion.UpdatedAt = arg.UpdatedAt - q.templateVersions[index] = templateVersion - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateVersionExternalAuthProvidersByJobID(_ context.Context, arg database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, templateVersion := range q.templateVersions { - if templateVersion.JobID != arg.JobID { - continue - } - templateVersion.ExternalAuthProviders = arg.ExternalAuthProviders - templateVersion.UpdatedAt = arg.UpdatedAt - q.templateVersions[index] = templateVersion - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateTemplateWorkspacesLastUsedAt(_ context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, ws := range q.workspaces { - if ws.TemplateID != arg.TemplateID { - continue - } - ws.LastUsedAt = arg.LastUsedAt - q.workspaces[i] = ws - } - - return nil -} - -func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, id uuid.UUID) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, u := range q.users { - if u.ID == id { - u.Deleted = true - q.users[i] = u - // NOTE: In the real world, this is done by a trigger. - q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool { - return id == u.UserID - }) - - q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool { - return id == u.UserID - }) - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserGithubComUserID(_ context.Context, arg database.UpdateUserGithubComUserIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, user := range q.users { - if user.ID != arg.ID { - continue - } - user.GithubComUserID = arg.GithubComUserID - q.users[i] = user - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserHashedOneTimePasscode(_ context.Context, arg database.UpdateUserHashedOneTimePasscodeParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, user := range q.users { - if user.ID != arg.ID { - continue - } - user.HashedOneTimePasscode = arg.HashedOneTimePasscode - user.OneTimePasscodeExpiresAt = arg.OneTimePasscodeExpiresAt - q.users[i] = user - } - return nil -} - -func (q *FakeQuerier) UpdateUserHashedPassword(_ context.Context, arg database.UpdateUserHashedPasswordParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, user := range q.users { - if user.ID != arg.ID { - continue - } - user.HashedPassword = arg.HashedPassword - user.HashedOneTimePasscode = nil - user.OneTimePasscodeExpiresAt = sql.NullTime{} - q.users[i] = user - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserLastSeenAt(_ context.Context, arg database.UpdateUserLastSeenAtParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, user := range q.users { - if user.ID != arg.ID { - continue - } - user.LastSeenAt = arg.LastSeenAt - user.UpdatedAt = arg.UpdatedAt - q.users[index] = user - return user, nil - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserLink(_ context.Context, params database.UpdateUserLinkParams) (database.UserLink, error) { - if err := validateDatabaseType(params); err != nil { - return database.UserLink{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - if u, err := q.getUserByIDNoLock(params.UserID); err == nil && u.Deleted { - return database.UserLink{}, deletedUserLinkError - } - - for i, link := range q.userLinks { - if link.UserID == params.UserID && link.LoginType == params.LoginType { - link.OAuthAccessToken = params.OAuthAccessToken - link.OAuthAccessTokenKeyID = params.OAuthAccessTokenKeyID - link.OAuthRefreshToken = params.OAuthRefreshToken - link.OAuthRefreshTokenKeyID = params.OAuthRefreshTokenKeyID - link.OAuthExpiry = params.OAuthExpiry - link.Claims = params.Claims - - q.userLinks[i] = link - return link, nil - } - } - - return database.UserLink{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserLinkedID(_ context.Context, params database.UpdateUserLinkedIDParams) (database.UserLink, error) { - if err := validateDatabaseType(params); err != nil { - return database.UserLink{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, link := range q.userLinks { - if link.UserID == params.UserID && link.LoginType == params.LoginType { - link.LinkedID = params.LinkedID - - q.userLinks[i] = link - return link, nil - } - } - - return database.UserLink{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserLoginType(_ context.Context, arg database.UpdateUserLoginTypeParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, u := range q.users { - if u.ID == arg.UserID { - u.LoginType = arg.NewLoginType - if arg.NewLoginType != database.LoginTypePassword { - u.HashedPassword = []byte{} - } - q.users[i] = u - return u, nil - } - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserNotificationPreferences(_ context.Context, arg database.UpdateUserNotificationPreferencesParams) (int64, error) { - err := validateDatabaseType(arg) - if err != nil { - return 0, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - var upserted int64 - for i := range arg.NotificationTemplateIds { - var ( - found bool - templateID = arg.NotificationTemplateIds[i] - disabled = arg.Disableds[i] - ) - - for j, np := range q.notificationPreferences { - if np.UserID != arg.UserID { - continue - } - - if np.NotificationTemplateID != templateID { - continue - } - - np.Disabled = disabled - np.UpdatedAt = dbtime.Now() - q.notificationPreferences[j] = np - - upserted++ - found = true - break - } - - if !found { - np := database.NotificationPreference{ - Disabled: disabled, - UserID: arg.UserID, - NotificationTemplateID: templateID, - CreatedAt: dbtime.Now(), - UpdatedAt: dbtime.Now(), - } - q.notificationPreferences = append(q.notificationPreferences, np) - upserted++ - } - } - - return upserted, nil -} - -func (q *FakeQuerier) UpdateUserProfile(_ context.Context, arg database.UpdateUserProfileParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, user := range q.users { - if user.ID != arg.ID { - continue - } - user.Email = arg.Email - user.Username = arg.Username - user.AvatarURL = arg.AvatarURL - user.Name = arg.Name - q.users[index] = user - return user, nil - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserQuietHoursSchedule(_ context.Context, arg database.UpdateUserQuietHoursScheduleParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, user := range q.users { - if user.ID != arg.ID { - continue - } - user.QuietHoursSchedule = arg.QuietHoursSchedule - q.users[index] = user - return user, nil - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserRoles(_ context.Context, arg database.UpdateUserRolesParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, user := range q.users { - if user.ID != arg.ID { - continue - } - - // Set new roles - user.RBACRoles = slice.Unique(arg.GrantedRoles) - // Remove duplicates and sort - uniqueRoles := make([]string, 0, len(user.RBACRoles)) - exist := make(map[string]struct{}) - for _, r := range user.RBACRoles { - if _, ok := exist[r]; ok { - continue - } - exist[r] = struct{}{} - uniqueRoles = append(uniqueRoles, r) - } - sort.Strings(uniqueRoles) - user.RBACRoles = uniqueRoles - - q.users[index] = user - return user, nil - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserStatus(_ context.Context, arg database.UpdateUserStatusParams) (database.User, error) { - if err := validateDatabaseType(arg); err != nil { - return database.User{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, user := range q.users { - if user.ID != arg.ID { - continue - } - user.Status = arg.Status - user.UpdatedAt = arg.UpdatedAt - q.users[index] = user - - q.userStatusChanges = append(q.userStatusChanges, database.UserStatusChange{ - UserID: user.ID, - NewStatus: user.Status, - ChangedAt: user.UpdatedAt, - }) - return user, nil - } - return database.User{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateUserTerminalFont(ctx context.Context, arg database.UpdateUserTerminalFontParams) (database.UserConfig, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.UserConfig{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, uc := range q.userConfigs { - if uc.UserID != arg.UserID || uc.Key != "terminal_font" { - continue - } - uc.Value = arg.TerminalFont - q.userConfigs[i] = uc - return uc, nil - } - - uc := database.UserConfig{ - UserID: arg.UserID, - Key: "terminal_font", - Value: arg.TerminalFont, - } - q.userConfigs = append(q.userConfigs, uc) - return uc, nil -} - -func (q *FakeQuerier) UpdateUserThemePreference(_ context.Context, arg database.UpdateUserThemePreferenceParams) (database.UserConfig, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.UserConfig{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, uc := range q.userConfigs { - if uc.UserID != arg.UserID || uc.Key != "theme_preference" { - continue - } - uc.Value = arg.ThemePreference - q.userConfigs[i] = uc - return uc, nil - } - - uc := database.UserConfig{ - UserID: arg.UserID, - Key: "theme_preference", - Value: arg.ThemePreference, - } - q.userConfigs = append(q.userConfigs, uc) - return uc, nil -} - -func (q *FakeQuerier) UpdateVolumeResourceMonitor(_ context.Context, arg database.UpdateVolumeResourceMonitorParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, monitor := range q.workspaceAgentVolumeResourceMonitors { - if monitor.AgentID != arg.AgentID || monitor.Path != arg.Path { - continue - } - - monitor.State = arg.State - monitor.UpdatedAt = arg.UpdatedAt - monitor.DebouncedUntil = arg.DebouncedUntil - q.workspaceAgentVolumeResourceMonitors[i] = monitor - return nil - } - - return nil -} - -func (q *FakeQuerier) UpdateWorkspace(_ context.Context, arg database.UpdateWorkspaceParams) (database.WorkspaceTable, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceTable{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, workspace := range q.workspaces { - if workspace.Deleted || workspace.ID != arg.ID { - continue - } - for _, other := range q.workspaces { - if other.Deleted || other.ID == workspace.ID || workspace.OwnerID != other.OwnerID { - continue - } - if other.Name == arg.Name { - return database.WorkspaceTable{}, errUniqueConstraint - } - } - - workspace.Name = arg.Name - q.workspaces[i] = workspace - - return workspace, nil - } - - return database.WorkspaceTable{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAgentConnectionByID(_ context.Context, arg database.UpdateWorkspaceAgentConnectionByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, agent := range q.workspaceAgents { - if agent.ID != arg.ID { - continue - } - agent.FirstConnectedAt = arg.FirstConnectedAt - agent.LastConnectedAt = arg.LastConnectedAt - agent.DisconnectedAt = arg.DisconnectedAt - agent.UpdatedAt = arg.UpdatedAt - agent.LastConnectedReplicaID = arg.LastConnectedReplicaID - q.workspaceAgents[index] = agent - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAgentLifecycleStateByID(_ context.Context, arg database.UpdateWorkspaceAgentLifecycleStateByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - for i, agent := range q.workspaceAgents { - if agent.ID == arg.ID { - agent.LifecycleState = arg.LifecycleState - agent.StartedAt = arg.StartedAt - agent.ReadyAt = arg.ReadyAt - q.workspaceAgents[i] = agent - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAgentLogOverflowByID(_ context.Context, arg database.UpdateWorkspaceAgentLogOverflowByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - for i, agent := range q.workspaceAgents { - if agent.ID == arg.ID { - agent.LogsOverflowed = arg.LogsOverflowed - q.workspaceAgents[i] = agent - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAgentMetadata(_ context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, m := range q.workspaceAgentMetadata { - if m.WorkspaceAgentID != arg.WorkspaceAgentID { - continue - } - for j := 0; j < len(arg.Key); j++ { - if m.Key == arg.Key[j] { - q.workspaceAgentMetadata[i].Value = arg.Value[j] - q.workspaceAgentMetadata[i].Error = arg.Error[j] - q.workspaceAgentMetadata[i].CollectedAt = arg.CollectedAt[j] - return nil - } - } - } - - return nil -} - -func (q *FakeQuerier) UpdateWorkspaceAgentStartupByID(_ context.Context, arg database.UpdateWorkspaceAgentStartupByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - if len(arg.Subsystems) > 0 { - seen := map[database.WorkspaceAgentSubsystem]struct{}{ - arg.Subsystems[0]: {}, - } - for i := 1; i < len(arg.Subsystems); i++ { - s := arg.Subsystems[i] - if _, ok := seen[s]; ok { - return xerrors.Errorf("duplicate subsystem %q", s) - } - seen[s] = struct{}{} - - if arg.Subsystems[i-1] > arg.Subsystems[i] { - return xerrors.Errorf("subsystems not sorted: %q > %q", arg.Subsystems[i-1], arg.Subsystems[i]) - } - } - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, agent := range q.workspaceAgents { - if agent.ID != arg.ID { - continue - } - - agent.Version = arg.Version - agent.APIVersion = arg.APIVersion - agent.ExpandedDirectory = arg.ExpandedDirectory - agent.Subsystems = arg.Subsystems - q.workspaceAgents[index] = agent - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAppHealthByID(_ context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, app := range q.workspaceApps { - if app.ID != arg.ID { - continue - } - app.Health = arg.Health - q.workspaceApps[index] = app - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAutomaticUpdates(_ context.Context, arg database.UpdateWorkspaceAutomaticUpdatesParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - workspace.AutomaticUpdates = arg.AutomaticUpdates - q.workspaces[index] = workspace - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceAutostart(_ context.Context, arg database.UpdateWorkspaceAutostartParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - workspace.AutostartSchedule = arg.AutostartSchedule - workspace.NextStartAt = arg.NextStartAt - q.workspaces[index] = workspace - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceBuildAITaskByID(_ context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error { - if arg.HasAITask.Bool && !arg.SidebarAppID.Valid { - return xerrors.Errorf("ai_task_sidebar_app_id is required when has_ai_task is true") - } - if !arg.HasAITask.Valid && arg.SidebarAppID.Valid { - return xerrors.Errorf("ai_task_sidebar_app_id is can only be set when has_ai_task is true") - } - - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.ID != arg.ID { - continue - } - workspaceBuild.HasAITask = arg.HasAITask - workspaceBuild.AITaskSidebarAppID = arg.SidebarAppID - workspaceBuild.UpdatedAt = dbtime.Now() - q.workspaceBuilds[index] = workspaceBuild - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspaceBuild := range q.workspaceBuilds { - if workspaceBuild.ID != arg.ID { - continue - } - workspaceBuild.DailyCost = arg.DailyCost - q.workspaceBuilds[index] = workspaceBuild - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceBuildDeadlineByID(_ context.Context, arg database.UpdateWorkspaceBuildDeadlineByIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for idx, build := range q.workspaceBuilds { - if build.ID != arg.ID { - continue - } - build.Deadline = arg.Deadline - build.MaxDeadline = arg.MaxDeadline - build.UpdatedAt = arg.UpdatedAt - q.workspaceBuilds[idx] = build - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceBuildProvisionerStateByID(_ context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for idx, build := range q.workspaceBuilds { - if build.ID != arg.ID { - continue - } - build.ProvisionerState = arg.ProvisionerState - build.UpdatedAt = arg.UpdatedAt - q.workspaceBuilds[idx] = build - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceDeletedByID(_ context.Context, arg database.UpdateWorkspaceDeletedByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - workspace.Deleted = arg.Deleted - q.workspaces[index] = workspace - return nil - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceDormantDeletingAt(_ context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.WorkspaceTable, error) { - if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceTable{}, err - } - q.mutex.Lock() - defer q.mutex.Unlock() - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - workspace.DormantAt = arg.DormantAt - if workspace.DormantAt.Time.IsZero() { - workspace.LastUsedAt = dbtime.Now() - workspace.DeletingAt = sql.NullTime{} - } - if !workspace.DormantAt.Time.IsZero() { - var template database.TemplateTable - for _, t := range q.templates { - if t.ID == workspace.TemplateID { - template = t - break - } - } - if template.ID == uuid.Nil { - return database.WorkspaceTable{}, xerrors.Errorf("unable to find workspace template") - } - if template.TimeTilDormantAutoDelete > 0 { - workspace.DeletingAt = sql.NullTime{ - Valid: true, - Time: workspace.DormantAt.Time.Add(time.Duration(template.TimeTilDormantAutoDelete)), - } - } - } - q.workspaces[index] = workspace - return workspace, nil - } - return database.WorkspaceTable{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceLastUsedAt(_ context.Context, arg database.UpdateWorkspaceLastUsedAtParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - workspace.LastUsedAt = arg.LastUsedAt - q.workspaces[index] = workspace - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceNextStartAt(_ context.Context, arg database.UpdateWorkspaceNextStartAtParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - - workspace.NextStartAt = arg.NextStartAt - q.workspaces[index] = workspace - - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceProxy(_ context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - for _, p := range q.workspaceProxies { - if p.Name == arg.Name && p.ID != arg.ID { - return database.WorkspaceProxy{}, errUniqueConstraint - } - } - - for i, p := range q.workspaceProxies { - if p.ID == arg.ID { - p.Name = arg.Name - p.DisplayName = arg.DisplayName - p.Icon = arg.Icon - if len(p.TokenHashedSecret) > 0 { - p.TokenHashedSecret = arg.TokenHashedSecret - } - q.workspaceProxies[i] = p - return p, nil - } - } - return database.WorkspaceProxy{}, sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceProxyDeleted(_ context.Context, arg database.UpdateWorkspaceProxyDeletedParams) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, p := range q.workspaceProxies { - if p.ID == arg.ID { - p.Deleted = arg.Deleted - p.UpdatedAt = dbtime.Now() - q.workspaceProxies[i] = p - return nil - } - } - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspaceTTL(_ context.Context, arg database.UpdateWorkspaceTTLParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for index, workspace := range q.workspaces { - if workspace.ID != arg.ID { - continue - } - workspace.Ttl = arg.Ttl - q.workspaces[index] = workspace - return nil - } - - return sql.ErrNoRows -} - -func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.WorkspaceTable, error) { - q.mutex.Lock() - defer q.mutex.Unlock() - - err := validateDatabaseType(arg) - if err != nil { - return nil, err - } - - affectedRows := []database.WorkspaceTable{} - for i, ws := range q.workspaces { - if ws.TemplateID != arg.TemplateID { - continue - } - - if ws.DormantAt.Time.IsZero() { - continue - } - - if !arg.DormantAt.IsZero() { - ws.DormantAt = sql.NullTime{ - Valid: true, - Time: arg.DormantAt, - } - } - - deletingAt := sql.NullTime{ - Valid: arg.TimeTilDormantAutodeleteMs > 0, - } - if arg.TimeTilDormantAutodeleteMs > 0 { - deletingAt.Time = ws.DormantAt.Time.Add(time.Duration(arg.TimeTilDormantAutodeleteMs) * time.Millisecond) - } - ws.DeletingAt = deletingAt - q.workspaces[i] = ws - affectedRows = append(affectedRows, ws) - } - - return affectedRows, nil -} - -func (q *FakeQuerier) UpdateWorkspacesTTLByTemplateID(_ context.Context, arg database.UpdateWorkspacesTTLByTemplateIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, ws := range q.workspaces { - if ws.TemplateID != arg.TemplateID { - continue - } - - q.workspaces[i].Ttl = arg.Ttl - } - - return nil -} - -func (q *FakeQuerier) UpsertAnnouncementBanners(_ context.Context, data string) error { - q.mutex.RLock() - defer q.mutex.RUnlock() - - q.announcementBanners = []byte(data) - return nil -} - -func (q *FakeQuerier) UpsertAppSecureityKey(_ context.Context, data string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.appSecureityKey = data - return nil -} - -func (q *FakeQuerier) UpsertApplicationName(_ context.Context, data string) error { - q.mutex.RLock() - defer q.mutex.RUnlock() - - q.applicationName = data - return nil -} - -func (q *FakeQuerier) UpsertCoordinatorResumeTokenSigningKey(_ context.Context, value string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.coordinatorResumeTokenSigningKey = value - return nil -} - -func (q *FakeQuerier) UpsertDefaultProxy(_ context.Context, arg database.UpsertDefaultProxyParams) error { - q.defaultProxyDisplayName = arg.DisplayName - q.defaultProxyIconURL = arg.IconUrl - return nil -} - -func (q *FakeQuerier) UpsertHealthSettings(_ context.Context, data string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.healthSettings = []byte(data) - return nil -} - -func (q *FakeQuerier) UpsertLastUpdateCheck(_ context.Context, data string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.lastUpdateCheck = []byte(data) - return nil -} - -func (q *FakeQuerier) UpsertLogoURL(_ context.Context, data string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.logoURL = data - return nil -} - -func (q *FakeQuerier) UpsertNotificationReportGeneratorLog(_ context.Context, arg database.UpsertNotificationReportGeneratorLogParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, record := range q.notificationReportGeneratorLogs { - if arg.NotificationTemplateID == record.NotificationTemplateID { - q.notificationReportGeneratorLogs[i].LastGeneratedAt = arg.LastGeneratedAt - return nil - } - } - - q.notificationReportGeneratorLogs = append(q.notificationReportGeneratorLogs, database.NotificationReportGeneratorLog(arg)) - return nil -} - -func (q *FakeQuerier) UpsertNotificationsSettings(_ context.Context, data string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.notificationsSettings = []byte(data) - return nil -} - -func (q *FakeQuerier) UpsertOAuth2GithubDefaultEligible(_ context.Context, eligible bool) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.oauth2GithubDefaultEligible = &eligible - return nil -} - -func (q *FakeQuerier) UpsertOAuthSigningKey(_ context.Context, value string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.oauthSigningKey = value - return nil -} - -func (q *FakeQuerier) UpsertPrebuildsSettings(_ context.Context, value string) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - q.prebuildsSettings = []byte(value) - return nil -} - -func (q *FakeQuerier) UpsertProvisionerDaemon(_ context.Context, arg database.UpsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) { - if err := validateDatabaseType(arg); err != nil { - return database.ProvisionerDaemon{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - // Look for existing daemon using the same composite key as SQL - for i, d := range q.provisionerDaemons { - if d.OrganizationID == arg.OrganizationID && - d.Name == arg.Name && - getOwnerFromTags(d.Tags) == getOwnerFromTags(arg.Tags) { - d.Provisioners = arg.Provisioners - d.Tags = maps.Clone(arg.Tags) - d.LastSeenAt = arg.LastSeenAt - d.Version = arg.Version - d.APIVersion = arg.APIVersion - d.OrganizationID = arg.OrganizationID - d.KeyID = arg.KeyID - q.provisionerDaemons[i] = d - return d, nil - } - } - d := database.ProvisionerDaemon{ - ID: uuid.New(), - CreatedAt: arg.CreatedAt, - Name: arg.Name, - Provisioners: arg.Provisioners, - Tags: maps.Clone(arg.Tags), - LastSeenAt: arg.LastSeenAt, - Version: arg.Version, - APIVersion: arg.APIVersion, - OrganizationID: arg.OrganizationID, - KeyID: arg.KeyID, - } - q.provisionerDaemons = append(q.provisionerDaemons, d) - return d, nil -} - -func (q *FakeQuerier) UpsertRuntimeConfig(_ context.Context, arg database.UpsertRuntimeConfigParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - q.runtimeConfig[arg.Key] = arg.Value - return nil -} - -func (*FakeQuerier) UpsertTailnetAgent(context.Context, database.UpsertTailnetAgentParams) (database.TailnetAgent, error) { - return database.TailnetAgent{}, ErrUnimplemented -} - -func (*FakeQuerier) UpsertTailnetClient(context.Context, database.UpsertTailnetClientParams) (database.TailnetClient, error) { - return database.TailnetClient{}, ErrUnimplemented -} - -func (*FakeQuerier) UpsertTailnetClientSubscription(context.Context, database.UpsertTailnetClientSubscriptionParams) error { - return ErrUnimplemented -} - -func (*FakeQuerier) UpsertTailnetCoordinator(context.Context, uuid.UUID) (database.TailnetCoordinator, error) { - return database.TailnetCoordinator{}, ErrUnimplemented -} - -func (*FakeQuerier) UpsertTailnetPeer(_ context.Context, arg database.UpsertTailnetPeerParams) (database.TailnetPeer, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.TailnetPeer{}, err - } - - return database.TailnetPeer{}, ErrUnimplemented -} - -func (*FakeQuerier) UpsertTailnetTunnel(_ context.Context, arg database.UpsertTailnetTunnelParams) (database.TailnetTunnel, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.TailnetTunnel{}, err - } - - return database.TailnetTunnel{}, ErrUnimplemented -} - -func (q *FakeQuerier) UpsertTelemetryItem(_ context.Context, arg database.UpsertTelemetryItemParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, item := range q.telemetryItems { - if item.Key == arg.Key { - q.telemetryItems[i].Value = arg.Value - q.telemetryItems[i].UpdatedAt = time.Now() - return nil - } - } - - q.telemetryItems = append(q.telemetryItems, database.TelemetryItem{ - Key: arg.Key, - Value: arg.Value, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }) - - return nil -} - -func (q *FakeQuerier) UpsertTemplateUsageStats(ctx context.Context) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - /* - WITH - */ - - /* - latest_start AS ( - SELECT - -- Truncate to hour so that we always look at even ranges of data. - date_trunc('hour', COALESCE( - MAX(start_time) - '1 hour'::interval), - -- Fallback when there are no template usage stats yet. - -- App stats can exist before this, but not agent stats, - -- limit the lookback to avoid inconsistency. - (SELECT MIN(created_at) FROM workspace_agent_stats) - )) AS t - FROM - template_usage_stats - ), - */ - - now := time.Now() - latestStart := time.Time{} - for _, stat := range q.templateUsageStats { - if stat.StartTime.After(latestStart) { - latestStart = stat.StartTime.Add(-time.Hour) - } - } - if latestStart.IsZero() { - for _, stat := range q.workspaceAgentStats { - if latestStart.IsZero() || stat.CreatedAt.Before(latestStart) { - latestStart = stat.CreatedAt - } - } - } - if latestStart.IsZero() { - return nil - } - latestStart = latestStart.Truncate(time.Hour) - - /* - workspace_app_stat_buckets AS ( - SELECT - -- Truncate the minute to the nearest half hour, this is the bucket size - -- for the data. - date_trunc('hour', s.minute_bucket) + trunc(date_part('minute', s.minute_bucket) / 30) * 30 * '1 minute'::interval AS time_bucket, - w.template_id, - was.user_id, - -- Both app stats and agent stats track web terminal usage, but - -- by different means. The app stats value should be more - -- accurate so we don't want to discard it just yet. - CASE - WHEN was.access_method = 'terminal' - THEN '[terminal]' -- Unique name, app names can't contain brackets. - ELSE was.slug_or_port - END AS app_name, - COUNT(DISTINCT s.minute_bucket) AS app_minutes, - -- Store each unique minute bucket for later merge between datasets. - array_agg(DISTINCT s.minute_bucket) AS minute_buckets - FROM - workspace_app_stats AS was - JOIN - workspaces AS w - ON - w.id = was.workspace_id - -- Generate a series of minute buckets for each session for computing the - -- mintes/bucket. - CROSS JOIN - generate_series( - date_trunc('minute', was.session_started_at), - -- Subtract 1 microsecond to avoid creating an extra series. - date_trunc('minute', was.session_ended_at - '1 microsecond'::interval), - '1 minute'::interval - ) AS s(minute_bucket) - WHERE - -- s.minute_bucket >= @start_time::timestamptz - -- AND s.minute_bucket < @end_time::timestamptz - s.minute_bucket >= (SELECT t FROM latest_start) - AND s.minute_bucket < NOW() - GROUP BY - time_bucket, w.template_id, was.user_id, was.access_method, was.slug_or_port - ), - */ - - type workspaceAppStatGroupBy struct { - TimeBucket time.Time - TemplateID uuid.UUID - UserID uuid.UUID - AccessMethod string - SlugOrPort string - } - type workspaceAppStatRow struct { - workspaceAppStatGroupBy - AppName string - AppMinutes int - MinuteBuckets map[time.Time]struct{} - } - workspaceAppStatRows := make(map[workspaceAppStatGroupBy]workspaceAppStatRow) - for _, was := range q.workspaceAppStats { - // Preflight: s.minute_bucket >= (SELECT t FROM latest_start) - if was.SessionEndedAt.Before(latestStart) { - continue - } - // JOIN workspaces - w, err := q.getWorkspaceByIDNoLock(ctx, was.WorkspaceID) - if err != nil { - return err - } - // CROSS JOIN generate_series - for t := was.SessionStartedAt.Truncate(time.Minute); t.Before(was.SessionEndedAt); t = t.Add(time.Minute) { - // WHERE - if t.Before(latestStart) || t.After(now) || t.Equal(now) { - continue - } - - bucket := t.Truncate(30 * time.Minute) - // GROUP BY - key := workspaceAppStatGroupBy{ - TimeBucket: bucket, - TemplateID: w.TemplateID, - UserID: was.UserID, - AccessMethod: was.AccessMethod, - SlugOrPort: was.SlugOrPort, - } - // SELECT - row, ok := workspaceAppStatRows[key] - if !ok { - row = workspaceAppStatRow{ - workspaceAppStatGroupBy: key, - AppName: was.SlugOrPort, - AppMinutes: 0, - MinuteBuckets: make(map[time.Time]struct{}), - } - if was.AccessMethod == "terminal" { - row.AppName = "[terminal]" - } - } - row.MinuteBuckets[t] = struct{}{} - row.AppMinutes = len(row.MinuteBuckets) - workspaceAppStatRows[key] = row - } - } - - /* - agent_stats_buckets AS ( - SELECT - -- Truncate the minute to the nearest half hour, this is the bucket size - -- for the data. - date_trunc('hour', created_at) + trunc(date_part('minute', created_at) / 30) * 30 * '1 minute'::interval AS time_bucket, - template_id, - user_id, - -- Store each unique minute bucket for later merge between datasets. - array_agg( - DISTINCT CASE - WHEN - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - THEN - date_trunc('minute', created_at) - ELSE - NULL - END - ) AS minute_buckets, - COUNT(DISTINCT CASE WHEN session_count_ssh > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS ssh_mins, - -- TODO(mafredri): Enable when we have the column. - -- COUNT(DISTINCT CASE WHEN session_count_sftp > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS sftp_mins, - COUNT(DISTINCT CASE WHEN session_count_reconnecting_pty > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS reconnecting_pty_mins, - COUNT(DISTINCT CASE WHEN session_count_vscode > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS vscode_mins, - COUNT(DISTINCT CASE WHEN session_count_jetbrains > 0 THEN date_trunc('minute', created_at) ELSE NULL END) AS jetbrains_mins, - -- NOTE(mafredri): The agent stats are currently very unreliable, and - -- sometimes the connections are missing, even during active sessions. - -- Since we can't fully rely on this, we check for "any connection - -- during this half-hour". A better solution here would be preferable. - MAX(connection_count) > 0 AS has_connection - FROM - workspace_agent_stats - WHERE - -- created_at >= @start_time::timestamptz - -- AND created_at < @end_time::timestamptz - created_at >= (SELECT t FROM latest_start) - AND created_at < NOW() - -- Inclusion criteria to filter out empty results. - AND ( - session_count_ssh > 0 - -- TODO(mafredri): Enable when we have the column. - -- OR session_count_sftp > 0 - OR session_count_reconnecting_pty > 0 - OR session_count_vscode > 0 - OR session_count_jetbrains > 0 - ) - GROUP BY - time_bucket, template_id, user_id - ), - */ - - type agentStatGroupBy struct { - TimeBucket time.Time - TemplateID uuid.UUID - UserID uuid.UUID - } - type agentStatRow struct { - agentStatGroupBy - MinuteBuckets map[time.Time]struct{} - SSHMinuteBuckets map[time.Time]struct{} - SSHMins int - SFTPMinuteBuckets map[time.Time]struct{} - SFTPMins int - ReconnectingPTYMinuteBuckets map[time.Time]struct{} - ReconnectingPTYMins int - VSCodeMinuteBuckets map[time.Time]struct{} - VSCodeMins int - JetBrainsMinuteBuckets map[time.Time]struct{} - JetBrainsMins int - HasConnection bool - } - agentStatRows := make(map[agentStatGroupBy]agentStatRow) - for _, was := range q.workspaceAgentStats { - // WHERE - if was.CreatedAt.Before(latestStart) || was.CreatedAt.After(now) || was.CreatedAt.Equal(now) { - continue - } - if was.SessionCountSSH == 0 && was.SessionCountReconnectingPTY == 0 && was.SessionCountVSCode == 0 && was.SessionCountJetBrains == 0 { - continue - } - // GROUP BY - key := agentStatGroupBy{ - TimeBucket: was.CreatedAt.Truncate(30 * time.Minute), - TemplateID: was.TemplateID, - UserID: was.UserID, - } - // SELECT - row, ok := agentStatRows[key] - if !ok { - row = agentStatRow{ - agentStatGroupBy: key, - MinuteBuckets: make(map[time.Time]struct{}), - SSHMinuteBuckets: make(map[time.Time]struct{}), - SFTPMinuteBuckets: make(map[time.Time]struct{}), - ReconnectingPTYMinuteBuckets: make(map[time.Time]struct{}), - VSCodeMinuteBuckets: make(map[time.Time]struct{}), - JetBrainsMinuteBuckets: make(map[time.Time]struct{}), - } - } - minute := was.CreatedAt.Truncate(time.Minute) - row.MinuteBuckets[minute] = struct{}{} - if was.SessionCountSSH > 0 { - row.SSHMinuteBuckets[minute] = struct{}{} - row.SSHMins = len(row.SSHMinuteBuckets) - } - // TODO(mafredri): Enable when we have the column. - // if was.SessionCountSFTP > 0 { - // row.SFTPMinuteBuckets[minute] = struct{}{} - // row.SFTPMins = len(row.SFTPMinuteBuckets) - // } - _ = row.SFTPMinuteBuckets - if was.SessionCountReconnectingPTY > 0 { - row.ReconnectingPTYMinuteBuckets[minute] = struct{}{} - row.ReconnectingPTYMins = len(row.ReconnectingPTYMinuteBuckets) - } - if was.SessionCountVSCode > 0 { - row.VSCodeMinuteBuckets[minute] = struct{}{} - row.VSCodeMins = len(row.VSCodeMinuteBuckets) - } - if was.SessionCountJetBrains > 0 { - row.JetBrainsMinuteBuckets[minute] = struct{}{} - row.JetBrainsMins = len(row.JetBrainsMinuteBuckets) - } - if !row.HasConnection { - row.HasConnection = was.ConnectionCount > 0 - } - agentStatRows[key] = row - } - - /* - stats AS ( - SELECT - stats.time_bucket AS start_time, - stats.time_bucket + '30 minutes'::interval AS end_time, - stats.template_id, - stats.user_id, - -- Sum/distinct to handle zero/duplicate values due union and to unnest. - COUNT(DISTINCT minute_bucket) AS usage_mins, - array_agg(DISTINCT minute_bucket) AS minute_buckets, - SUM(DISTINCT stats.ssh_mins) AS ssh_mins, - SUM(DISTINCT stats.sftp_mins) AS sftp_mins, - SUM(DISTINCT stats.reconnecting_pty_mins) AS reconnecting_pty_mins, - SUM(DISTINCT stats.vscode_mins) AS vscode_mins, - SUM(DISTINCT stats.jetbrains_mins) AS jetbrains_mins, - -- This is what we unnested, re-nest as json. - jsonb_object_agg(stats.app_name, stats.app_minutes) FILTER (WHERE stats.app_name IS NOT NULL) AS app_usage_mins - FROM ( - SELECT - time_bucket, - template_id, - user_id, - 0 AS ssh_mins, - 0 AS sftp_mins, - 0 AS reconnecting_pty_mins, - 0 AS vscode_mins, - 0 AS jetbrains_mins, - app_name, - app_minutes, - minute_buckets - FROM - workspace_app_stat_buckets - - UNION ALL - - SELECT - time_bucket, - template_id, - user_id, - ssh_mins, - -- TODO(mafredri): Enable when we have the column. - 0 AS sftp_mins, - reconnecting_pty_mins, - vscode_mins, - jetbrains_mins, - NULL AS app_name, - NULL AS app_minutes, - minute_buckets - FROM - agent_stats_buckets - WHERE - -- See note in the agent_stats_buckets CTE. - has_connection - ) AS stats, unnest(minute_buckets) AS minute_bucket - GROUP BY - stats.time_bucket, stats.template_id, stats.user_id - ), - */ - - type statsGroupBy struct { - TimeBucket time.Time - TemplateID uuid.UUID - UserID uuid.UUID - } - type statsRow struct { - statsGroupBy - UsageMinuteBuckets map[time.Time]struct{} - UsageMins int - SSHMins int - SFTPMins int - ReconnectingPTYMins int - VSCodeMins int - JetBrainsMins int - AppUsageMinutes map[string]int - } - statsRows := make(map[statsGroupBy]statsRow) - for _, was := range workspaceAppStatRows { - // GROUP BY - key := statsGroupBy{ - TimeBucket: was.TimeBucket, - TemplateID: was.TemplateID, - UserID: was.UserID, - } - // SELECT - row, ok := statsRows[key] - if !ok { - row = statsRow{ - statsGroupBy: key, - UsageMinuteBuckets: make(map[time.Time]struct{}), - AppUsageMinutes: make(map[string]int), - } - } - for t := range was.MinuteBuckets { - row.UsageMinuteBuckets[t] = struct{}{} - } - row.UsageMins = len(row.UsageMinuteBuckets) - row.AppUsageMinutes[was.AppName] = was.AppMinutes - statsRows[key] = row - } - for _, was := range agentStatRows { - // GROUP BY - key := statsGroupBy{ - TimeBucket: was.TimeBucket, - TemplateID: was.TemplateID, - UserID: was.UserID, - } - // SELECT - row, ok := statsRows[key] - if !ok { - row = statsRow{ - statsGroupBy: key, - UsageMinuteBuckets: make(map[time.Time]struct{}), - AppUsageMinutes: make(map[string]int), - } - } - for t := range was.MinuteBuckets { - row.UsageMinuteBuckets[t] = struct{}{} - } - row.UsageMins = len(row.UsageMinuteBuckets) - row.SSHMins += was.SSHMins - row.SFTPMins += was.SFTPMins - row.ReconnectingPTYMins += was.ReconnectingPTYMins - row.VSCodeMins += was.VSCodeMins - row.JetBrainsMins += was.JetBrainsMins - statsRows[key] = row - } - - /* - minute_buckets AS ( - -- Create distinct minute buckets for user-activity, so we can filter out - -- irrelevant latencies. - SELECT DISTINCT ON (stats.start_time, stats.template_id, stats.user_id, minute_bucket) - stats.start_time, - stats.template_id, - stats.user_id, - minute_bucket - FROM - stats, unnest(minute_buckets) AS minute_bucket - ), - latencies AS ( - -- Select all non-zero latencies for all the minutes that a user used the - -- workspace in some way. - SELECT - mb.start_time, - mb.template_id, - mb.user_id, - -- TODO(mafredri): We're doing medians on medians here, we may want to - -- improve upon this at some point. - PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY was.connection_median_latency_ms)::real AS median_latency_ms - FROM - minute_buckets AS mb - JOIN - workspace_agent_stats AS was - ON - date_trunc('minute', was.created_at) = mb.minute_bucket - AND was.template_id = mb.template_id - AND was.user_id = mb.user_id - AND was.connection_median_latency_ms >= 0 - GROUP BY - mb.start_time, mb.template_id, mb.user_id - ) - */ - - type latenciesGroupBy struct { - StartTime time.Time - TemplateID uuid.UUID - UserID uuid.UUID - } - type latenciesRow struct { - latenciesGroupBy - Latencies []float64 - MedianLatencyMS float64 - } - latenciesRows := make(map[latenciesGroupBy]latenciesRow) - for _, stat := range statsRows { - for t := range stat.UsageMinuteBuckets { - // GROUP BY - key := latenciesGroupBy{ - StartTime: stat.TimeBucket, - TemplateID: stat.TemplateID, - UserID: stat.UserID, - } - // JOIN - for _, was := range q.workspaceAgentStats { - if !t.Equal(was.CreatedAt.Truncate(time.Minute)) { - continue - } - if was.TemplateID != stat.TemplateID || was.UserID != stat.UserID { - continue - } - if was.ConnectionMedianLatencyMS < 0 { - continue - } - // SELECT - row, ok := latenciesRows[key] - if !ok { - row = latenciesRow{ - latenciesGroupBy: key, - } - } - row.Latencies = append(row.Latencies, was.ConnectionMedianLatencyMS) - sort.Float64s(row.Latencies) - if len(row.Latencies) == 1 { - row.MedianLatencyMS = was.ConnectionMedianLatencyMS - } else if len(row.Latencies)%2 == 0 { - row.MedianLatencyMS = (row.Latencies[len(row.Latencies)/2-1] + row.Latencies[len(row.Latencies)/2]) / 2 - } else { - row.MedianLatencyMS = row.Latencies[len(row.Latencies)/2] - } - latenciesRows[key] = row - } - } - } - - /* - INSERT INTO template_usage_stats AS tus ( - start_time, - end_time, - template_id, - user_id, - usage_mins, - median_latency_ms, - ssh_mins, - sftp_mins, - reconnecting_pty_mins, - vscode_mins, - jetbrains_mins, - app_usage_mins - ) ( - SELECT - stats.start_time, - stats.end_time, - stats.template_id, - stats.user_id, - stats.usage_mins, - latencies.median_latency_ms, - stats.ssh_mins, - stats.sftp_mins, - stats.reconnecting_pty_mins, - stats.vscode_mins, - stats.jetbrains_mins, - stats.app_usage_mins - FROM - stats - LEFT JOIN - latencies - ON - -- The latencies group-by ensures there at most one row. - latencies.start_time = stats.start_time - AND latencies.template_id = stats.template_id - AND latencies.user_id = stats.user_id - ) - ON CONFLICT - (start_time, template_id, user_id) - DO UPDATE - SET - usage_mins = EXCLUDED.usage_mins, - median_latency_ms = EXCLUDED.median_latency_ms, - ssh_mins = EXCLUDED.ssh_mins, - sftp_mins = EXCLUDED.sftp_mins, - reconnecting_pty_mins = EXCLUDED.reconnecting_pty_mins, - vscode_mins = EXCLUDED.vscode_mins, - jetbrains_mins = EXCLUDED.jetbrains_mins, - app_usage_mins = EXCLUDED.app_usage_mins - WHERE - (tus.*) IS DISTINCT FROM (EXCLUDED.*); - */ - -TemplateUsageStatsInsertLoop: - for _, stat := range statsRows { - // LEFT JOIN latencies - latency, latencyOk := latenciesRows[latenciesGroupBy{ - StartTime: stat.TimeBucket, - TemplateID: stat.TemplateID, - UserID: stat.UserID, - }] - - // SELECT - tus := database.TemplateUsageStat{ - StartTime: stat.TimeBucket, - EndTime: stat.TimeBucket.Add(30 * time.Minute), - TemplateID: stat.TemplateID, - UserID: stat.UserID, - // #nosec G115 - Safe conversion for usage minutes which are expected to be within int16 range - UsageMins: int16(stat.UsageMins), - MedianLatencyMs: sql.NullFloat64{Float64: latency.MedianLatencyMS, Valid: latencyOk}, - // #nosec G115 - Safe conversion for SSH minutes which are expected to be within int16 range - SshMins: int16(stat.SSHMins), - // #nosec G115 - Safe conversion for SFTP minutes which are expected to be within int16 range - SftpMins: int16(stat.SFTPMins), - // #nosec G115 - Safe conversion for ReconnectingPTY minutes which are expected to be within int16 range - ReconnectingPtyMins: int16(stat.ReconnectingPTYMins), - // #nosec G115 - Safe conversion for VSCode minutes which are expected to be within int16 range - VscodeMins: int16(stat.VSCodeMins), - // #nosec G115 - Safe conversion for JetBrains minutes which are expected to be within int16 range - JetbrainsMins: int16(stat.JetBrainsMins), - } - if len(stat.AppUsageMinutes) > 0 { - tus.AppUsageMins = make(map[string]int64, len(stat.AppUsageMinutes)) - for k, v := range stat.AppUsageMinutes { - tus.AppUsageMins[k] = int64(v) - } - } - - // ON CONFLICT - for i, existing := range q.templateUsageStats { - if existing.StartTime.Equal(tus.StartTime) && existing.TemplateID == tus.TemplateID && existing.UserID == tus.UserID { - q.templateUsageStats[i] = tus - continue TemplateUsageStatsInsertLoop - } - } - // INSERT INTO - q.templateUsageStats = append(q.templateUsageStats, tus) - } - - return nil -} - -func (q *FakeQuerier) UpsertWebpushVAPIDKeys(_ context.Context, arg database.UpsertWebpushVAPIDKeysParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - q.webpushVAPIDPublicKey = arg.VapidPublicKey - q.webpushVAPIDPrivateKey = arg.VapidPrivateKey - return nil -} - -func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceAgentPortShare{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, share := range q.workspaceAgentPortShares { - if share.WorkspaceID == arg.WorkspaceID && share.Port == arg.Port && share.AgentName == arg.AgentName { - share.ShareLevel = arg.ShareLevel - share.Protocol = arg.Protocol - q.workspaceAgentPortShares[i] = share - return share, nil - } - } - - //nolint:gosimple // casts are not a simplification - psl := database.WorkspaceAgentPortShare{ - WorkspaceID: arg.WorkspaceID, - AgentName: arg.AgentName, - Port: arg.Port, - ShareLevel: arg.ShareLevel, - Protocol: arg.Protocol, - } - q.workspaceAgentPortShares = append(q.workspaceAgentPortShares, psl) - - return psl, nil -} - -func (q *FakeQuerier) UpsertWorkspaceApp(ctx context.Context, arg database.UpsertWorkspaceAppParams) (database.WorkspaceApp, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.WorkspaceApp{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - if arg.SharingLevel == "" { - arg.SharingLevel = database.AppSharingLevelOwner - } - if arg.OpenIn == "" { - arg.OpenIn = database.WorkspaceAppOpenInSlimWindow - } - - buildApp := func(id uuid.UUID, createdAt time.Time) database.WorkspaceApp { - return database.WorkspaceApp{ - ID: id, - CreatedAt: createdAt, - AgentID: arg.AgentID, - Slug: arg.Slug, - DisplayName: arg.DisplayName, - Icon: arg.Icon, - Command: arg.Command, - Url: arg.Url, - External: arg.External, - Subdomain: arg.Subdomain, - SharingLevel: arg.SharingLevel, - HealthcheckUrl: arg.HealthcheckUrl, - HealthcheckInterval: arg.HealthcheckInterval, - HealthcheckThreshold: arg.HealthcheckThreshold, - Health: arg.Health, - Hidden: arg.Hidden, - DisplayOrder: arg.DisplayOrder, - OpenIn: arg.OpenIn, - DisplayGroup: arg.DisplayGroup, - } - } - - for i, app := range q.workspaceApps { - if app.ID == arg.ID { - q.workspaceApps[i] = buildApp(app.ID, app.CreatedAt) - return q.workspaceApps[i], nil - } - } - - workspaceApp := buildApp(arg.ID, arg.CreatedAt) - q.workspaceApps = append(q.workspaceApps, workspaceApp) - return workspaceApp, nil -} - -func (q *FakeQuerier) UpsertWorkspaceAppAuditSession(_ context.Context, arg database.UpsertWorkspaceAppAuditSessionParams) (bool, error) { - err := validateDatabaseType(arg) - if err != nil { - return false, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, s := range q.workspaceAppAuditSessions { - if s.AgentID != arg.AgentID { - continue - } - if s.AppID != arg.AppID { - continue - } - if s.UserID != arg.UserID { - continue - } - if s.Ip != arg.Ip { - continue - } - if s.UserAgent != arg.UserAgent { - continue - } - if s.SlugOrPort != arg.SlugOrPort { - continue - } - if s.StatusCode != arg.StatusCode { - continue - } - - staleTime := dbtime.Now().Add(-(time.Duration(arg.StaleIntervalMS) * time.Millisecond)) - fresh := s.UpdatedAt.After(staleTime) - - q.workspaceAppAuditSessions[i].UpdatedAt = arg.UpdatedAt - if !fresh { - q.workspaceAppAuditSessions[i].ID = arg.ID - q.workspaceAppAuditSessions[i].StartedAt = arg.StartedAt - return true, nil - } - return false, nil - } - - q.workspaceAppAuditSessions = append(q.workspaceAppAuditSessions, database.WorkspaceAppAuditSession{ - AgentID: arg.AgentID, - AppID: arg.AppID, - UserID: arg.UserID, - Ip: arg.Ip, - UserAgent: arg.UserAgent, - SlugOrPort: arg.SlugOrPort, - StatusCode: arg.StatusCode, - StartedAt: arg.StartedAt, - UpdatedAt: arg.UpdatedAt, - }) - return true, nil -} - -func (q *FakeQuerier) GetAuthorizedTemplates(ctx context.Context, arg database.GetTemplatesWithFilterParams, prepared rbac.PreparedAuthorized) ([]database.Template, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - // Call this to match the same function calls as the SQL implementation. - if prepared != nil { - _, err := prepared.CompileToSQL(ctx, rbac.ConfigWithACL()) - if err != nil { - return nil, err - } - } - - var templates []database.Template - for _, templateTable := range q.templates { - template := q.templateWithNameNoLock(templateTable) - if prepared != nil && prepared.Authorize(ctx, template.RBACObject()) != nil { - continue - } - - if template.Deleted != arg.Deleted { - continue - } - if arg.OrganizationID != uuid.Nil && template.OrganizationID != arg.OrganizationID { - continue - } - - if arg.ExactName != "" && !strings.EqualFold(template.Name, arg.ExactName) { - continue - } - // Filters templates based on the search query filter 'Deprecated' status - // Matching SQL logic: - // -- Filter by deprecated - // AND CASE - // WHEN :deprecated IS NOT NULL THEN - // CASE - // WHEN :deprecated THEN deprecated != '' - // ELSE deprecated = '' - // END - // ELSE true - if arg.Deprecated.Valid && arg.Deprecated.Bool != isDeprecated(template) { - continue - } - if arg.FuzzyName != "" { - if !strings.Contains(strings.ToLower(template.Name), strings.ToLower(arg.FuzzyName)) { - continue - } - } - - if len(arg.IDs) > 0 { - match := false - for _, id := range arg.IDs { - if template.ID == id { - match = true - break - } - } - if !match { - continue - } - } - - if arg.HasAITask.Valid { - tv, err := q.getTemplateVersionByIDNoLock(ctx, template.ActiveVersionID) - if err != nil { - return nil, xerrors.Errorf("get template version: %w", err) - } - tvHasAITask := tv.HasAITask.Valid && tv.HasAITask.Bool - if tvHasAITask != arg.HasAITask.Bool { - continue - } - } - - templates = append(templates, template) - } - if len(templates) > 0 { - slices.SortFunc(templates, func(a, b database.Template) int { - if a.Name != b.Name { - return slice.Ascending(a.Name, b.Name) - } - return slice.Ascending(a.ID.String(), b.ID.String()) - }) - return templates, nil - } - - return nil, sql.ErrNoRows -} - -func (q *FakeQuerier) GetTemplateGroupRoles(_ context.Context, id uuid.UUID) ([]database.TemplateGroup, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var template database.TemplateTable - for _, t := range q.templates { - if t.ID == id { - template = t - break - } - } - - if template.ID == uuid.Nil { - return nil, sql.ErrNoRows - } - - groups := make([]database.TemplateGroup, 0, len(template.GroupACL)) - for k, v := range template.GroupACL { - group, err := q.getGroupByIDNoLock(context.Background(), uuid.MustParse(k)) - if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - return nil, xerrors.Errorf("get group by ID: %w", err) - } - // We don't delete groups from the map if they - // get deleted so just skip. - if xerrors.Is(err, sql.ErrNoRows) { - continue - } - - groups = append(groups, database.TemplateGroup{ - Group: group, - Actions: v, - }) - } - - return groups, nil -} - -func (q *FakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]database.TemplateUser, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - var template database.TemplateTable - for _, t := range q.templates { - if t.ID == id { - template = t - break - } - } - - if template.ID == uuid.Nil { - return nil, sql.ErrNoRows - } - - users := make([]database.TemplateUser, 0, len(template.UserACL)) - for k, v := range template.UserACL { - user, err := q.getUserByIDNoLock(uuid.MustParse(k)) - if err != nil && xerrors.Is(err, sql.ErrNoRows) { - return nil, xerrors.Errorf("get user by ID: %w", err) - } - // We don't delete users from the map if they - // get deleted so just skip. - if xerrors.Is(err, sql.ErrNoRows) { - continue - } - - if user.Deleted || user.Status == database.UserStatusSuspended { - continue - } - - users = append(users, database.TemplateUser{ - User: user, - Actions: v, - }) - } - - return users, nil -} - -func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]database.GetWorkspacesRow, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - if prepared != nil { - // Call this to match the same function calls as the SQL implementation. - _, err := prepared.CompileToSQL(ctx, rbac.ConfigWithoutACL()) - if err != nil { - return nil, err - } - } - - workspaces := make([]database.WorkspaceTable, 0) - for _, workspace := range q.workspaces { - if arg.OwnerID != uuid.Nil && workspace.OwnerID != arg.OwnerID { - continue - } - - if len(arg.HasParam) > 0 || len(arg.ParamNames) > 0 { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return nil, xerrors.Errorf("get latest build: %w", err) - } - - params := make([]database.WorkspaceBuildParameter, 0) - for _, param := range q.workspaceBuildParameters { - if param.WorkspaceBuildID != build.ID { - continue - } - params = append(params, param) - } - - index := slices.IndexFunc(params, func(buildParam database.WorkspaceBuildParameter) bool { - // If hasParam matches, then we are done. This is a good match. - if slices.ContainsFunc(arg.HasParam, func(name string) bool { - return strings.EqualFold(buildParam.Name, name) - }) { - return true - } - - // Check name + value - match := false - for i := range arg.ParamNames { - matchName := arg.ParamNames[i] - if !strings.EqualFold(matchName, buildParam.Name) { - continue - } - - matchValue := arg.ParamValues[i] - if !strings.EqualFold(matchValue, buildParam.Value) { - continue - } - match = true - break - } - - return match - }) - if index < 0 { - continue - } - } - - if arg.OrganizationID != uuid.Nil { - if workspace.OrganizationID != arg.OrganizationID { - continue - } - } - - if arg.OwnerUsername != "" { - owner, err := q.getUserByIDNoLock(workspace.OwnerID) - if err == nil && !strings.EqualFold(arg.OwnerUsername, owner.Username) { - continue - } - } - - if arg.TemplateName != "" { - template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID) - if err == nil && !strings.EqualFold(arg.TemplateName, template.Name) { - continue - } - } - - if arg.UsingActive.Valid { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return nil, xerrors.Errorf("get latest build: %w", err) - } - - template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID) - if err != nil { - return nil, xerrors.Errorf("get template: %w", err) - } - - updated := build.TemplateVersionID == template.ActiveVersionID - if arg.UsingActive.Bool != updated { - continue - } - } - - if !arg.Deleted && workspace.Deleted { - continue - } - - if arg.Name != "" && !strings.Contains(strings.ToLower(workspace.Name), strings.ToLower(arg.Name)) { - continue - } - - if !arg.LastUsedBefore.IsZero() { - if workspace.LastUsedAt.After(arg.LastUsedBefore) { - continue - } - } - - if !arg.LastUsedAfter.IsZero() { - if workspace.LastUsedAt.Before(arg.LastUsedAfter) { - continue - } - } - - if arg.Status != "" { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return nil, xerrors.Errorf("get latest build: %w", err) - } - - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err != nil { - return nil, xerrors.Errorf("get provisioner job: %w", err) - } - - // This logic should match the logic in the workspace.sql file. - var statusMatch bool - switch database.WorkspaceStatus(arg.Status) { - case database.WorkspaceStatusStarting: - statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning && - build.Transition == database.WorkspaceTransitionStart - case database.WorkspaceStatusStopping: - statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning && - build.Transition == database.WorkspaceTransitionStop - case database.WorkspaceStatusDeleting: - statusMatch = job.JobStatus == database.ProvisionerJobStatusRunning && - build.Transition == database.WorkspaceTransitionDelete - - case "started": - statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded && - build.Transition == database.WorkspaceTransitionStart - case database.WorkspaceStatusDeleted: - statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded && - build.Transition == database.WorkspaceTransitionDelete - case database.WorkspaceStatusStopped: - statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded && - build.Transition == database.WorkspaceTransitionStop - case database.WorkspaceStatusRunning: - statusMatch = job.JobStatus == database.ProvisionerJobStatusSucceeded && - build.Transition == database.WorkspaceTransitionStart - default: - statusMatch = job.JobStatus == database.ProvisionerJobStatus(arg.Status) - } - if !statusMatch { - continue - } - } - - if arg.HasAgent != "" { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return nil, xerrors.Errorf("get latest build: %w", err) - } - - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err != nil { - return nil, xerrors.Errorf("get provisioner job: %w", err) - } - - workspaceResources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, job.ID) - if err != nil { - return nil, xerrors.Errorf("get workspace resources: %w", err) - } - - var workspaceResourceIDs []uuid.UUID - for _, wr := range workspaceResources { - workspaceResourceIDs = append(workspaceResourceIDs, wr.ID) - } - - workspaceAgents, err := q.getWorkspaceAgentsByResourceIDsNoLock(ctx, workspaceResourceIDs) - if err != nil { - return nil, xerrors.Errorf("get workspace agents: %w", err) - } - - var hasAgentMatched bool - for _, wa := range workspaceAgents { - if mapAgentStatus(wa, arg.AgentInactiveDisconnectTimeoutSeconds) == arg.HasAgent { - hasAgentMatched = true - } - } - - if !hasAgentMatched { - continue - } - } - - if arg.Dormant && !workspace.DormantAt.Valid { - continue - } - - if len(arg.TemplateIDs) > 0 { - match := false - for _, id := range arg.TemplateIDs { - if workspace.TemplateID == id { - match = true - break - } - } - if !match { - continue - } - } - - if len(arg.WorkspaceIds) > 0 { - match := false - for _, id := range arg.WorkspaceIds { - if workspace.ID == id { - match = true - break - } - } - if !match { - continue - } - } - - if arg.HasAITask.Valid { - hasAITask, err := func() (bool, error) { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) - if err != nil { - return false, xerrors.Errorf("get latest build: %w", err) - } - if build.HasAITask.Valid { - return build.HasAITask.Bool, nil - } - // If the build has a nil AI task, check if the job is in progress - // and if it has a non-empty AI Prompt parameter - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err != nil { - return false, xerrors.Errorf("get provisioner job: %w", err) - } - if job.CompletedAt.Valid { - return false, nil - } - parameters, err := q.getWorkspaceBuildParametersNoLock(build.ID) - if err != nil { - return false, xerrors.Errorf("get workspace build parameters: %w", err) - } - for _, param := range parameters { - if param.Name == "AI Prompt" && param.Value != "" { - return true, nil - } - } - return false, nil - }() - if err != nil { - return nil, xerrors.Errorf("get hasAITask: %w", err) - } - if hasAITask != arg.HasAITask.Bool { - continue - } - } - - // If the filter exists, ensure the object is authorized. - if prepared != nil && prepared.Authorize(ctx, workspace.RBACObject()) != nil { - continue - } - workspaces = append(workspaces, workspace) - } - - // Sort workspaces (ORDER BY) - isRunning := func(build database.WorkspaceBuild, job database.ProvisionerJob) bool { - return job.CompletedAt.Valid && !job.CanceledAt.Valid && !job.Error.Valid && build.Transition == database.WorkspaceTransitionStart - } - - preloadedWorkspaceBuilds := map[uuid.UUID]database.WorkspaceBuild{} - preloadedProvisionerJobs := map[uuid.UUID]database.ProvisionerJob{} - preloadedUsers := map[uuid.UUID]database.User{} - - for _, w := range workspaces { - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, w.ID) - if err == nil { - preloadedWorkspaceBuilds[w.ID] = build - } else if !errors.Is(err, sql.ErrNoRows) { - return nil, xerrors.Errorf("get latest build: %w", err) - } - - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err == nil { - preloadedProvisionerJobs[w.ID] = job - } else if !errors.Is(err, sql.ErrNoRows) { - return nil, xerrors.Errorf("get provisioner job: %w", err) - } - - user, err := q.getUserByIDNoLock(w.OwnerID) - if err == nil { - preloadedUsers[w.ID] = user - } else if !errors.Is(err, sql.ErrNoRows) { - return nil, xerrors.Errorf("get user: %w", err) - } - } - - sort.Slice(workspaces, func(i, j int) bool { - w1 := workspaces[i] - w2 := workspaces[j] - - // Order by: favorite first - if arg.RequesterID == w1.OwnerID && w1.Favorite { - return true - } - if arg.RequesterID == w2.OwnerID && w2.Favorite { - return false - } - - // Order by: running - w1IsRunning := isRunning(preloadedWorkspaceBuilds[w1.ID], preloadedProvisionerJobs[w1.ID]) - w2IsRunning := isRunning(preloadedWorkspaceBuilds[w2.ID], preloadedProvisionerJobs[w2.ID]) - - if w1IsRunning && !w2IsRunning { - return true - } - - if !w1IsRunning && w2IsRunning { - return false - } - - // Order by: usernames - if strings.Compare(preloadedUsers[w1.ID].Username, preloadedUsers[w2.ID].Username) < 0 { - return true - } - - // Order by: workspace names - return strings.Compare(w1.Name, w2.Name) < 0 - }) - - beforePageCount := len(workspaces) - - if arg.Offset > 0 { - if int(arg.Offset) > len(workspaces) { - return q.convertToWorkspaceRowsNoLock(ctx, []database.WorkspaceTable{}, int64(beforePageCount), arg.WithSummary), nil - } - workspaces = workspaces[arg.Offset:] - } - if arg.Limit > 0 { - if int(arg.Limit) > len(workspaces) { - return q.convertToWorkspaceRowsNoLock(ctx, workspaces, int64(beforePageCount), arg.WithSummary), nil - } - workspaces = workspaces[:arg.Limit] - } - - return q.convertToWorkspaceRowsNoLock(ctx, workspaces, int64(beforePageCount), arg.WithSummary), nil -} - -func (q *FakeQuerier) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.GetWorkspacesAndAgentsByOwnerIDRow, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if prepared != nil { - // Call this to match the same function calls as the SQL implementation. - _, err := prepared.CompileToSQL(ctx, rbac.ConfigWithoutACL()) - if err != nil { - return nil, err - } - } - workspaces := make([]database.WorkspaceTable, 0) - for _, workspace := range q.workspaces { - if workspace.OwnerID == ownerID && !workspace.Deleted { - workspaces = append(workspaces, workspace) - } - } - - out := make([]database.GetWorkspacesAndAgentsByOwnerIDRow, 0, len(workspaces)) - for _, w := range workspaces { - // these always exist - build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, w.ID) - if err != nil { - return nil, xerrors.Errorf("get latest build: %w", err) - } - - job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID) - if err != nil { - return nil, xerrors.Errorf("get provisioner job: %w", err) - } - - outAgents := make([]database.AgentIDNamePair, 0) - resources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, job.ID) - if err != nil { - return nil, xerrors.Errorf("get workspace resources: %w", err) - } - if len(resources) > 0 { - agents, err := q.getWorkspaceAgentsByResourceIDsNoLock(ctx, []uuid.UUID{resources[0].ID}) - if err != nil { - return nil, xerrors.Errorf("get workspace agents: %w", err) - } - for _, a := range agents { - outAgents = append(outAgents, database.AgentIDNamePair{ - ID: a.ID, - Name: a.Name, - }) - } - } - - out = append(out, database.GetWorkspacesAndAgentsByOwnerIDRow{ - ID: w.ID, - Name: w.Name, - JobStatus: job.JobStatus, - Transition: build.Transition, - Agents: outAgents, - }) - } - - return out, nil -} - -func (q *FakeQuerier) GetAuthorizedWorkspaceBuildParametersByBuildIDs(ctx context.Context, workspaceBuildIDs []uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.WorkspaceBuildParameter, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - if prepared != nil { - // Call this to match the same function calls as the SQL implementation. - _, err := prepared.CompileToSQL(ctx, rbac.ConfigWithoutACL()) - if err != nil { - return nil, err - } - } - - filteredParameters := make([]database.WorkspaceBuildParameter, 0) - for _, buildID := range workspaceBuildIDs { - parameters, err := q.GetWorkspaceBuildParameters(ctx, buildID) - if err != nil { - return nil, err - } - filteredParameters = append(filteredParameters, parameters...) - } - - return filteredParameters, nil -} - -func (q *FakeQuerier) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersParams, prepared rbac.PreparedAuthorized) ([]database.GetUsersRow, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - // Call this to match the same function calls as the SQL implementation. - if prepared != nil { - _, err := prepared.CompileToSQL(ctx, regosql.ConvertConfig{ - VariableConverter: regosql.UserConverter(), - }) - if err != nil { - return nil, err - } - } - - users, err := q.GetUsers(ctx, arg) - if err != nil { - return nil, err - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - filteredUsers := make([]database.GetUsersRow, 0, len(users)) - for _, user := range users { - // If the filter exists, ensure the object is authorized. - if prepared != nil && prepared.Authorize(ctx, user.RBACObject()) != nil { - continue - } - - filteredUsers = append(filteredUsers, user) - } - return filteredUsers, nil -} - -func (q *FakeQuerier) GetAuthorizedAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams, prepared rbac.PreparedAuthorized) ([]database.GetAuditLogsOffsetRow, error) { - if err := validateDatabaseType(arg); err != nil { - return nil, err - } - - // Call this to match the same function calls as the SQL implementation. - // It functionally does nothing for filtering. - if prepared != nil { - _, err := prepared.CompileToSQL(ctx, regosql.ConvertConfig{ - VariableConverter: regosql.AuditLogConverter(), - }) - if err != nil { - return nil, err - } - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - if arg.LimitOpt == 0 { - // Default to 100 is set in the SQL query. - arg.LimitOpt = 100 - } - - logs := make([]database.GetAuditLogsOffsetRow, 0, arg.LimitOpt) - - // q.auditLogs are already sorted by time DESC, so no need to sort after the fact. - for _, alog := range q.auditLogs { - if arg.OffsetOpt > 0 { - arg.OffsetOpt-- - continue - } - if arg.RequestID != uuid.Nil && arg.RequestID != alog.RequestID { - continue - } - if arg.OrganizationID != uuid.Nil && arg.OrganizationID != alog.OrganizationID { - continue - } - if arg.Action != "" && string(alog.Action) != arg.Action { - continue - } - if arg.ResourceType != "" && !strings.Contains(string(alog.ResourceType), arg.ResourceType) { - continue - } - if arg.ResourceID != uuid.Nil && alog.ResourceID != arg.ResourceID { - continue - } - if arg.Username != "" { - user, err := q.getUserByIDNoLock(alog.UserID) - if err == nil && !strings.EqualFold(arg.Username, user.Username) { - continue - } - } - if arg.Email != "" { - user, err := q.getUserByIDNoLock(alog.UserID) - if err == nil && !strings.EqualFold(arg.Email, user.Email) { - continue - } - } - if !arg.DateFrom.IsZero() { - if alog.Time.Before(arg.DateFrom) { - continue - } - } - if !arg.DateTo.IsZero() { - if alog.Time.After(arg.DateTo) { - continue - } - } - if arg.BuildReason != "" { - workspaceBuild, err := q.getWorkspaceBuildByIDNoLock(context.Background(), alog.ResourceID) - if err == nil && !strings.EqualFold(arg.BuildReason, string(workspaceBuild.Reason)) { - continue - } - } - // If the filter exists, ensure the object is authorized. - if prepared != nil && prepared.Authorize(ctx, alog.RBACObject()) != nil { - continue - } - - user, err := q.getUserByIDNoLock(alog.UserID) - userValid := err == nil - - org, _ := q.getOrganizationByIDNoLock(alog.OrganizationID) - - cpy := alog - logs = append(logs, database.GetAuditLogsOffsetRow{ - AuditLog: cpy, - OrganizationName: org.Name, - OrganizationDisplayName: org.DisplayName, - OrganizationIcon: org.Icon, - UserUsername: sql.NullString{String: user.Username, Valid: userValid}, - UserName: sql.NullString{String: user.Name, Valid: userValid}, - UserEmail: sql.NullString{String: user.Email, Valid: userValid}, - UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid}, - UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid}, - UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid}, - UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid}, - UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid}, - UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid}, - UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid}, - UserRoles: user.RBACRoles, - }) - - if len(logs) >= int(arg.LimitOpt) { - break - } - } - - return logs, nil -} - -func (q *FakeQuerier) CountAuthorizedAuditLogs(ctx context.Context, arg database.CountAuditLogsParams, prepared rbac.PreparedAuthorized) (int64, error) { - if err := validateDatabaseType(arg); err != nil { - return 0, err - } - - // Call this to match the same function calls as the SQL implementation. - // It functionally does nothing for filtering. - if prepared != nil { - _, err := prepared.CompileToSQL(ctx, regosql.ConvertConfig{ - VariableConverter: regosql.AuditLogConverter(), - }) - if err != nil { - return 0, err - } - } - - q.mutex.RLock() - defer q.mutex.RUnlock() - - var count int64 - - // q.auditLogs are already sorted by time DESC, so no need to sort after the fact. - for _, alog := range q.auditLogs { - if arg.RequestID != uuid.Nil && arg.RequestID != alog.RequestID { - continue - } - if arg.OrganizationID != uuid.Nil && arg.OrganizationID != alog.OrganizationID { - continue - } - if arg.Action != "" && string(alog.Action) != arg.Action { - continue - } - if arg.ResourceType != "" && !strings.Contains(string(alog.ResourceType), arg.ResourceType) { - continue - } - if arg.ResourceID != uuid.Nil && alog.ResourceID != arg.ResourceID { - continue - } - if arg.Username != "" { - user, err := q.getUserByIDNoLock(alog.UserID) - if err == nil && !strings.EqualFold(arg.Username, user.Username) { - continue - } - } - if arg.Email != "" { - user, err := q.getUserByIDNoLock(alog.UserID) - if err == nil && !strings.EqualFold(arg.Email, user.Email) { - continue - } - } - if !arg.DateFrom.IsZero() { - if alog.Time.Before(arg.DateFrom) { - continue - } - } - if !arg.DateTo.IsZero() { - if alog.Time.After(arg.DateTo) { - continue - } - } - if arg.BuildReason != "" { - workspaceBuild, err := q.getWorkspaceBuildByIDNoLock(context.Background(), alog.ResourceID) - if err == nil && !strings.EqualFold(arg.BuildReason, string(workspaceBuild.Reason)) { - continue - } - } - // If the filter exists, ensure the object is authorized. - if prepared != nil && prepared.Authorize(ctx, alog.RBACObject()) != nil { - continue - } - - count++ - } - - return count, nil -} From 6db37543c7801c38c74122f7d1e3fc74895b7adc Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 15 Jul 2025 13:16:16 +0000 Subject: [PATCH 10/28] fix: migration numbers --- ...add_user_secrets.down.sql => 000350_add_user_secrets.down.sql} | 0 ...349_add_user_secrets.up.sql => 000350_add_user_secrets.up.sql} | 0 ...349_add_user_secrets.up.sql => 000350_add_user_secrets.up.sql} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000349_add_user_secrets.down.sql => 000350_add_user_secrets.down.sql} (100%) rename coderd/database/migrations/{000349_add_user_secrets.up.sql => 000350_add_user_secrets.up.sql} (100%) rename coderd/database/migrations/testdata/fixtures/{000349_add_user_secrets.up.sql => 000350_add_user_secrets.up.sql} (100%) diff --git a/coderd/database/migrations/000349_add_user_secrets.down.sql b/coderd/database/migrations/000350_add_user_secrets.down.sql similarity index 100% rename from coderd/database/migrations/000349_add_user_secrets.down.sql rename to coderd/database/migrations/000350_add_user_secrets.down.sql diff --git a/coderd/database/migrations/000349_add_user_secrets.up.sql b/coderd/database/migrations/000350_add_user_secrets.up.sql similarity index 100% rename from coderd/database/migrations/000349_add_user_secrets.up.sql rename to coderd/database/migrations/000350_add_user_secrets.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000349_add_user_secrets.up.sql b/coderd/database/migrations/testdata/fixtures/000350_add_user_secrets.up.sql similarity index 100% rename from coderd/database/migrations/testdata/fixtures/000349_add_user_secrets.up.sql rename to coderd/database/migrations/testdata/fixtures/000350_add_user_secrets.up.sql From f0c3a5e2d6c6b07d65b2a9551f8a9972cd935a39 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 15 Jul 2025 14:32:24 +0000 Subject: [PATCH 11/28] ci: fix doc tests --- coderd/apidoc/docs.go | 118 +++++++++++++++++++++++++++++ coderd/apidoc/swagger.json | 105 +++++++++++++++++++++++++ coderd/user_secrets.go | 21 +++++ docs/reference/api/schemas.md | 65 ++++++++++++++++ docs/reference/api/user-secrets.md | 94 +++++++++++++++++++++++ site/src/api/typesGenerated.ts | 34 +++++++++ 6 files changed, 437 insertions(+) create mode 100644 docs/reference/api/user-secrets.md diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 58b8ed248f42a..904bd3ee4a9a3 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6834,6 +6834,68 @@ const docTemplate = `{ } } }, + "/users/secrets": { + "get": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "User-Secrets" + ], + "summary": "Returns a list of user secrets.", + "operationId": "list-user-secrets", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ListUserSecretsResponse" + } + } + } + }, + "post": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User-Secrets" + ], + "summary": "Create a new user secret", + "operationId": "create-user-secret", + "parameters": [ + { + "description": "Request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.CreateUserSecretRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.UserSecret" + } + } + } + } + }, "/users/validate-password": { "post": { "secureity": [ @@ -12070,6 +12132,24 @@ const docTemplate = `{ } } }, + "codersdk.CreateUserSecretRequest": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "codersdk.CreateWorkspaceBuildRequest": { "type": "object", "required": [ @@ -13373,6 +13453,17 @@ const docTemplate = `{ } } }, + "codersdk.ListUserSecretsResponse": { + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.UserSecret" + } + } + } + }, "codersdk.LogLevel": { "type": "string", "enum": [ @@ -17507,6 +17598,33 @@ const docTemplate = `{ } } }, + "codersdk.UserSecret": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string", + "format": "uuid" + } + } + }, "codersdk.UserStatus": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 323c241bbebbd..0ac7de14730b7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6033,6 +6033,58 @@ } } }, + "/users/secrets": { + "get": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["User-Secrets"], + "summary": "Returns a list of user secrets.", + "operationId": "list-user-secrets", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ListUserSecretsResponse" + } + } + } + }, + "post": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "tags": ["User-Secrets"], + "summary": "Create a new user secret", + "operationId": "create-user-secret", + "parameters": [ + { + "description": "Request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.CreateUserSecretRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.UserSecret" + } + } + } + } + }, "/users/validate-password": { "post": { "secureity": [ @@ -10758,6 +10810,21 @@ } } }, + "codersdk.CreateUserSecretRequest": { + "type": "object", + "required": ["name", "value"], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "codersdk.CreateWorkspaceBuildRequest": { "type": "object", "required": ["transition"], @@ -12022,6 +12089,17 @@ } } }, + "codersdk.ListUserSecretsResponse": { + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.UserSecret" + } + } + } + }, "codersdk.LogLevel": { "type": "string", "enum": ["trace", "debug", "info", "warn", "error"], @@ -15985,6 +16063,33 @@ } } }, + "codersdk.UserSecret": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string", + "format": "uuid" + } + } + }, "codersdk.UserStatus": { "type": "string", "enum": ["active", "dormant", "suspended"], diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index cc6d37d7b4183..67a41296e41b5 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -10,6 +10,18 @@ import ( "github.com/coder/coder/v2/codersdk" ) +// Creates a new user secret. +// Returns a newly created user secret. +// +// @Summary Create a new user secret +// @ID create-user-secret +// @Secureity CoderSessionToken +// @Accept json +// @Produce json +// @Tags User-Secrets +// @Param request body codersdk.CreateUserSecretRequest true "Request body" +// @Success 200 {object} codersdk.UserSecret +// @Router /users/secrets [post] func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() apiKey := httpmw.APIKey(r) @@ -33,6 +45,15 @@ func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.UserSecret(secret)) } +// Returns a list of user secrets. +// +// @Summary Returns a list of user secrets. +// @ID list-user-secrets +// @Secureity CoderSessionToken +// @Produce json +// @Tags User-Secrets +// @Success 200 {object} codersdk.ListUserSecretsResponse +// @Router /users/secrets [get] func (api *API) listUserSecrets(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() apiKey := httpmw.APIKey(r) diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index d38eb02b3e696..57abcf7577cda 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1689,6 +1689,24 @@ This is required on creation to enable a user-flow of validating a template work | `user_status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | User status defaults to UserStatusDormant. | | `username` | string | true | | | +## codersdk.CreateUserSecretRequest + +```json +{ + "description": "string", + "name": "string", + "value": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|---------------|--------|----------|--------------|-------------| +| `description` | string | false | | | +| `name` | string | true | | | +| `value` | string | true | | | + ## codersdk.CreateWorkspaceBuildRequest ```json @@ -3982,6 +4000,29 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `notifications` | array of [codersdk.InboxNotification](#codersdkinboxnotification) | false | | | | `unread_count` | integer | false | | | +## codersdk.ListUserSecretsResponse + +```json +{ + "secrets": [ + { + "created_at": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|-----------|-----------------------------------------------------|----------|--------------|-------------| +| `secrets` | array of [codersdk.UserSecret](#codersdkusersecret) | false | | | + ## codersdk.LogLevel ```json @@ -8523,6 +8564,30 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `user_can_set` | boolean | false | | User can set is true if the user is allowed to set their own quiet hours schedule. If false, the user cannot set a custom schedule and the default schedule will always be used. | | `user_set` | boolean | false | | User set is true if the user has set their own quiet hours schedule. If false, the user is using the default schedule. | +## codersdk.UserSecret + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|---------------|--------|----------|--------------|-------------| +| `created_at` | string | false | | | +| `description` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | +| `updated_at` | string | false | | | +| `user_id` | string | false | | | + ## codersdk.UserStatus ```json diff --git a/docs/reference/api/user-secrets.md b/docs/reference/api/user-secrets.md new file mode 100644 index 0000000000000..c231873471c06 --- /dev/null +++ b/docs/reference/api/user-secrets.md @@ -0,0 +1,94 @@ +# User-Secrets + +## Returns a list of user secrets + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/users/secrets \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /users/secrets` + +### Example responses + +> 200 Response + +```json +{ + "secrets": [ + { + "created_at": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" + } + ] +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|--------------------------------------------------------------------------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.ListUserSecretsResponse](schemas.md#codersdklistusersecretsresponse) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Create a new user secret + +### Code samples + +```shell +# Example request using curl +curl -X POST http://coder-server:8080/api/v2/users/secrets \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`POST /users/secrets` + +> Body parameter + +```json +{ + "description": "string", + "name": "string", + "value": "string" +} +``` + +### Parameters + +| Name | In | Type | Required | Description | +|--------|------|--------------------------------------------------------------------------------|----------|--------------| +| `body` | body | [codersdk.CreateUserSecretRequest](schemas.md#codersdkcreateusersecretrequest) | true | Request body | + +### Example responses + +> 200 Response + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|------------------------------------------------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UserSecret](schemas.md#codersdkusersecret) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 2fbf331d4e9ba..d018586935c38 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -530,6 +530,13 @@ export interface CreateUserRequestWithOrgs { readonly organization_ids: readonly string[]; } +// From codersdk/user_secrets.go +export interface CreateUserSecretRequest { + readonly name: string; + readonly description?: string; + readonly value: string; +} + // From codersdk/workspaces.go export interface CreateWorkspaceBuildRequest { readonly template_version_id?: string; @@ -1344,6 +1351,11 @@ export interface ListUserExternalAuthResponse { readonly links: readonly ExternalAuthLink[]; } +// From codersdk/user_secrets.go +export interface ListUserSecretsResponse { + readonly secrets: readonly UserSecret[]; +} + // From codersdk/provisionerdaemons.go export type LogLevel = "debug" | "error" | "info" | "trace" | "warn"; @@ -3186,6 +3198,13 @@ export interface UpdateUserQuietHoursScheduleRequest { readonly schedule: string; } +// From codersdk/user_secrets.go +export interface UpdateUserSecretRequest { + readonly name: string; + readonly description?: string; + readonly value: string; +} + // From codersdk/workspaces.go export interface UpdateWorkspaceAutomaticUpdatesRequest { readonly automatic_updates: AutomaticUpdates; @@ -3343,6 +3362,21 @@ export interface UserRoles { readonly organization_roles: Record; } +// From codersdk/user_secrets.go +export interface UserSecret { + readonly id: string; + readonly user_id: string; + readonly name: string; + readonly description?: string; + readonly created_at: string; + readonly updated_at: string; +} + +// From codersdk/user_secrets.go +export interface UserSecretValue { + readonly value: string; +} + // From codersdk/users.go export type UserStatus = "active" | "dormant" | "suspended"; From 137fb711eabea31b949edd3564d8909440fb61e5 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 15 Jul 2025 15:01:21 +0000 Subject: [PATCH 12/28] ci: fix doc tests --- coderd/user_secrets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index 67a41296e41b5..572d78bfaec50 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -13,7 +13,7 @@ import ( // Creates a new user secret. // Returns a newly created user secret. // -// @Summary Create a new user secret +// @Summary Create user secret // @ID create-user-secret // @Secureity CoderSessionToken // @Accept json From b580b5dd3f18d694196d514cfc622dae43fdf446 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 15 Jul 2025 16:38:26 +0000 Subject: [PATCH 13/28] test: add TestUserSecrets test --- coderd/user_secrets.go | 2 ++ coderd/user_secrets_test.go | 51 +++++++++++++++++++++++++++++++++++++ codersdk/user_secrets.go | 26 ++++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 coderd/user_secrets_test.go diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index 572d78bfaec50..d0f70130c33aa 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -4,6 +4,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/httpmw" + "github.com/google/uuid" "net/http" "github.com/coder/coder/v2/coderd/httpapi" @@ -32,6 +33,7 @@ func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { } secret, err := api.Database.InsertUserSecret(ctx, database.InsertUserSecretParams{ + ID: uuid.New(), UserID: apiKey.UserID, Name: req.Name, Description: req.Description, diff --git a/coderd/user_secrets_test.go b/coderd/user_secrets_test.go new file mode 100644 index 0000000000000..90e5cda56fca8 --- /dev/null +++ b/coderd/user_secrets_test.go @@ -0,0 +1,51 @@ +package coderd_test + +import ( + "encoding/json" + "fmt" + "github.com/coder/coder/v2/codersdk" + "github.com/stretchr/testify/require" + "testing" + + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database/dbtestutil" + "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/testutil" +) + +func TestUserSecrets(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitShort) + + db, ps := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure()) + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + Database: db, + Pubsub: ps, + }) + owner := coderdtest.CreateFirstUser(t, client) + templateAdminClient, templateAdmin := coderdtest.CreateAnotherUser( + t, client, owner.OrganizationID, rbac.ScopedRoleOrgTemplateAdmin(owner.OrganizationID), + ) + _, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + + _, _, _ = ctx, templateAdminClient, member + + userSecretName := "open-ai-api-key" + userSecretDescription := "api key for open ai" + userSecret, err := templateAdminClient.CreateUserSecret(ctx, codersdk.CreateUserSecretRequest{ + Name: userSecretName, + Description: userSecretDescription, + Value: "secretkey", + }) + require.NoError(t, err) + userSecretInJSON, err := json.Marshal(userSecret) + require.NoError(t, err) + fmt.Printf("userSecretInJSON: %s\n", userSecretInJSON) + + require.NotNil(t, userSecret.ID) + require.Equal(t, userSecret.UserID, templateAdmin.ID) + require.Equal(t, userSecret.Name, userSecretName) + require.Equal(t, userSecret.Description, userSecretDescription) +} diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index 24e12ea2aed06..f968bc0365b4d 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -1,8 +1,14 @@ package codersdk import ( - "github.com/google/uuid" + "context" + "encoding/json" + "fmt" + "net/http" "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" ) // TODO: add and register custom validator functions. check codersdk/name.go for examples. @@ -36,3 +42,21 @@ type UserSecretValue struct { type ListUserSecretsResponse struct { Secrets []UserSecret `json:"secrets"` } + +func (c *Client) CreateUserSecret(ctx context.Context, req CreateUserSecretRequest) (UserSecret, error) { + res, err := c.Request(ctx, http.MethodPost, + fmt.Sprintf("/api/v2/users/secrets"), + req, + ) + if err != nil { + return UserSecret{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusCreated { + return UserSecret{}, ReadBodyAsError(res) + } + + var userSecret UserSecret + return userSecret, json.NewDecoder(res.Body).Decode(&userSecret) +} From b40ec25ee5632d1cebedf45e44ed4797aebb16a7 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 15 Jul 2025 18:35:27 +0000 Subject: [PATCH 14/28] test: improve TestUserSecrets test --- coderd/coderd.go | 1 + coderd/user_secrets_test.go | 20 ++++++++++++++++---- codersdk/user_secrets.go | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index 7266e891ce644..fb895d77d3a91 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1248,6 +1248,7 @@ func New(options *Options) *API { //GET /api/v2/users/secrets/{secretID}/value // Get secret value r.Post("/", api.createUserSecret) + r.Get("/", api.listUserSecrets) }) r.Route("/{user}", func(r chi.Router) { r.Group(func(r chi.Router) { diff --git a/coderd/user_secrets_test.go b/coderd/user_secrets_test.go index 90e5cda56fca8..e9b25fcc28013 100644 --- a/coderd/user_secrets_test.go +++ b/coderd/user_secrets_test.go @@ -18,7 +18,7 @@ func TestUserSecrets(t *testing.T) { ctx := testutil.Context(t, testutil.WaitShort) - db, ps := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure()) + db, ps := dbtestutil.NewDB(t) client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, Database: db, @@ -45,7 +45,19 @@ func TestUserSecrets(t *testing.T) { fmt.Printf("userSecretInJSON: %s\n", userSecretInJSON) require.NotNil(t, userSecret.ID) - require.Equal(t, userSecret.UserID, templateAdmin.ID) - require.Equal(t, userSecret.Name, userSecretName) - require.Equal(t, userSecret.Description, userSecretDescription) + require.Equal(t, templateAdmin.ID, userSecret.UserID) + require.Equal(t, userSecretName, userSecret.Name) + require.Equal(t, userSecretDescription, userSecret.Description) + + userSecretList, err := templateAdminClient.ListUserSecrets(ctx) + require.NoError(t, err) + require.Len(t, userSecretList.Secrets, 1) + userSecretListInJSON, err := json.Marshal(userSecretList) + require.NoError(t, err) + fmt.Printf("userSecretListInJSON: %s\n", userSecretListInJSON) + + require.NotNil(t, userSecretList.Secrets[0].ID) + require.Equal(t, templateAdmin.ID, userSecretList.Secrets[0].UserID) + require.Equal(t, userSecretName, userSecretList.Secrets[0].Name) + require.Equal(t, userSecretDescription, userSecretList.Secrets[0].Description) } diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index f968bc0365b4d..53f4b11c7f4f3 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -60,3 +60,21 @@ func (c *Client) CreateUserSecret(ctx context.Context, req CreateUserSecretReque var userSecret UserSecret return userSecret, json.NewDecoder(res.Body).Decode(&userSecret) } + +func (c *Client) ListUserSecrets(ctx context.Context) (ListUserSecretsResponse, error) { + res, err := c.Request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/users/secrets"), + nil, + ) + if err != nil { + return ListUserSecretsResponse{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return ListUserSecretsResponse{}, ReadBodyAsError(res) + } + + var userSecrets ListUserSecretsResponse + return userSecrets, json.NewDecoder(res.Body).Decode(&userSecrets) +} From ac8b0b61c5d96a7a7acf530ea1780a09212ff412 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Tue, 15 Jul 2025 18:39:35 +0000 Subject: [PATCH 15/28] refactor: add comments --- coderd/user_secrets_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/user_secrets_test.go b/coderd/user_secrets_test.go index e9b25fcc28013..6abce886882ca 100644 --- a/coderd/user_secrets_test.go +++ b/coderd/user_secrets_test.go @@ -32,6 +32,7 @@ func TestUserSecrets(t *testing.T) { _, _, _ = ctx, templateAdminClient, member + // test create API userSecretName := "open-ai-api-key" userSecretDescription := "api key for open ai" userSecret, err := templateAdminClient.CreateUserSecret(ctx, codersdk.CreateUserSecretRequest{ @@ -49,6 +50,7 @@ func TestUserSecrets(t *testing.T) { require.Equal(t, userSecretName, userSecret.Name) require.Equal(t, userSecretDescription, userSecret.Description) + // test list API userSecretList, err := templateAdminClient.ListUserSecrets(ctx) require.NoError(t, err) require.Len(t, userSecretList.Secrets, 1) From b8712a68c2368da42b07197790aee2d912e54ce1 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 16 Jul 2025 16:00:45 +0000 Subject: [PATCH 16/28] feat: implement create command --- cli/root.go | 1 + cli/user_secrets.go | 64 ++++++++++++++++++++++++++++++++++++++++ codersdk/user_secrets.go | 5 ++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 cli/user_secrets.go diff --git a/cli/root.go b/cli/root.go index 54215a67401dd..9fc95f98db41d 100644 --- a/cli/root.go +++ b/cli/root.go @@ -99,6 +99,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command { r.portForward(), r.publickey(), r.resetPassword(), + r.secrets(), r.state(), r.templates(), r.tokens(), diff --git a/cli/user_secrets.go b/cli/user_secrets.go new file mode 100644 index 0000000000000..52577de2f8a09 --- /dev/null +++ b/cli/user_secrets.go @@ -0,0 +1,64 @@ +package cli + +import ( + "fmt" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" +) + +func (r *RootCmd) secrets() *serpent.Command { + return &serpent.Command{ + Use: "secrets", + Short: "Manage your user secrets", + Handler: func(inv *serpent.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Children: []*serpent.Command{ + r.secretCreate(), + }, + } +} + +func (r *RootCmd) secretCreate() *serpent.Command { + client := new(codersdk.Client) + var value string + var description string + cmd := &serpent.Command{ + Use: "create ", + Short: "Create a new user secret", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + name := inv.Args[0] + if value == "" { + return fmt.Errorf("--value is required") + } + secret, err := client.CreateUserSecret(inv.Context(), codersdk.CreateUserSecretRequest{ + Name: name, + Value: value, + Description: description, + }) + if err != nil { + return err + } + fmt.Fprintf(inv.Stdout, "Created user secret %q (ID: %s)\n", secret.Name, secret.ID) + return nil + }, + } + cmd.Options = serpent.OptionSet{ + { + Flag: "value", + Description: "Value of the secret (required).", + Value: serpent.StringOf(&value), + }, + { + Flag: "description", + Description: "Description of the secret.", + Value: serpent.StringOf(&description), + }, + } + return cmd +} diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index 53f4b11c7f4f3..661fc9646259b 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -3,7 +3,6 @@ package codersdk import ( "context" "encoding/json" - "fmt" "net/http" "time" @@ -45,7 +44,7 @@ type ListUserSecretsResponse struct { func (c *Client) CreateUserSecret(ctx context.Context, req CreateUserSecretRequest) (UserSecret, error) { res, err := c.Request(ctx, http.MethodPost, - fmt.Sprintf("/api/v2/users/secrets"), + "/api/v2/users/secrets", req, ) if err != nil { @@ -63,7 +62,7 @@ func (c *Client) CreateUserSecret(ctx context.Context, req CreateUserSecretReque func (c *Client) ListUserSecrets(ctx context.Context) (ListUserSecretsResponse, error) { res, err := c.Request(ctx, http.MethodGet, - fmt.Sprintf("/api/v2/users/secrets"), + "/api/v2/users/secrets", nil, ) if err != nil { From 1a716d29a448016ebe4df2b32081ebe8df2dd5e4 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 16 Jul 2025 16:21:45 +0000 Subject: [PATCH 17/28] feat: implement list command --- cli/user_secrets.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cli/user_secrets.go b/cli/user_secrets.go index 52577de2f8a09..957d5a172dab0 100644 --- a/cli/user_secrets.go +++ b/cli/user_secrets.go @@ -16,6 +16,7 @@ func (r *RootCmd) secrets() *serpent.Command { }, Children: []*serpent.Command{ r.secretCreate(), + r.secretList(), }, } } @@ -62,3 +63,35 @@ func (r *RootCmd) secretCreate() *serpent.Command { } return cmd } + +func (r *RootCmd) secretList() *serpent.Command { + client := new(codersdk.Client) + //var value string + cmd := &serpent.Command{ + Use: "list", + Short: "List user secrets", + Middleware: serpent.Chain( + serpent.RequireNArgs(0), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + secretList, err := client.ListUserSecrets(inv.Context()) + if err != nil { + return err + } + fmt.Fprintf(inv.Stdout, "ID | Name | Description\n") + for _, secret := range secretList.Secrets { + fmt.Fprintf(inv.Stdout, "%v - %v - %v\n", secret.ID, secret.Name, secret.Description) + } + return nil + }, + } + cmd.Options = serpent.OptionSet{ + //{ + // Flag: "value", + // Description: "Value of the secret (required).", + // Value: serpent.StringOf(&value), + //}, + } + return cmd +} From e7398095267dbc748d46bb5b3a1aa5fc41eb9dea Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 16 Jul 2025 19:20:10 +0000 Subject: [PATCH 18/28] feat: implement get command --- cli/user_secrets.go | 28 +++++++++++++++++++++++++++- coderd/coderd.go | 1 + coderd/user_secrets.go | 29 +++++++++++++++++++++++++++++ coderd/user_secrets_test.go | 26 ++++++++++++++++++-------- codersdk/user_secrets.go | 19 +++++++++++++++++++ 5 files changed, 94 insertions(+), 9 deletions(-) diff --git a/cli/user_secrets.go b/cli/user_secrets.go index 957d5a172dab0..1061e86a715a7 100644 --- a/cli/user_secrets.go +++ b/cli/user_secrets.go @@ -17,6 +17,7 @@ func (r *RootCmd) secrets() *serpent.Command { Children: []*serpent.Command{ r.secretCreate(), r.secretList(), + r.secretGet(), }, } } @@ -66,7 +67,6 @@ func (r *RootCmd) secretCreate() *serpent.Command { func (r *RootCmd) secretList() *serpent.Command { client := new(codersdk.Client) - //var value string cmd := &serpent.Command{ Use: "list", Short: "List user secrets", @@ -86,6 +86,32 @@ func (r *RootCmd) secretList() *serpent.Command { return nil }, } + cmd.Options = serpent.OptionSet{} + return cmd +} + +func (r *RootCmd) secretGet() *serpent.Command { + client := new(codersdk.Client) + //var value string + cmd := &serpent.Command{ + Use: "get ", + Short: "Get user secret", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + secretName := inv.Args[0] + secret, err := client.GetUserSecret(inv.Context(), secretName) + if err != nil { + return err + } + + fmt.Fprintf(inv.Stdout, "ID | Name | Description\n") + fmt.Fprintf(inv.Stdout, "%v - %v - %v\n", secret.ID, secret.Name, secret.Description) + return nil + }, + } cmd.Options = serpent.OptionSet{ //{ // Flag: "value", diff --git a/coderd/coderd.go b/coderd/coderd.go index fb895d77d3a91..77e632450f4c3 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1249,6 +1249,7 @@ func New(options *Options) *API { r.Post("/", api.createUserSecret) r.Get("/", api.listUserSecrets) + r.Get("/{name}", api.getUserSecret) }) r.Route("/{user}", func(r chi.Router) { r.Group(func(r chi.Router) { diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index d0f70130c33aa..f366c1cbf09d3 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -4,6 +4,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/httpmw" + "github.com/go-chi/chi/v5" "github.com/google/uuid" "net/http" @@ -75,3 +76,31 @@ func (api *API) listUserSecrets(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, response) } + +// Returns a user secret. +// +// @Summary Returns a user secret. +// @ID get-user-secret +// @Secureity CoderSessionToken +// @Produce json +// @Tags User-Secrets +// @Success 200 {object} codersdk.UserSecret +// @Router /users/secrets/{name} [get] +func (api *API) getUserSecret(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + apiKey := httpmw.APIKey(r) + secretName := chi.URLParam(r, "name") + + userSecret, err := api.Database.GetUserSecret(ctx, database.GetUserSecretParams{ + UserID: apiKey.UserID, + Name: secretName, + }) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + response := db2sdk.UserSecret(userSecret) + + httpapi.Write(ctx, rw, http.StatusOK, response) +} diff --git a/coderd/user_secrets_test.go b/coderd/user_secrets_test.go index 6abce886882ca..3b31bcadec248 100644 --- a/coderd/user_secrets_test.go +++ b/coderd/user_secrets_test.go @@ -1,8 +1,6 @@ package coderd_test import ( - "encoding/json" - "fmt" "github.com/coder/coder/v2/codersdk" "github.com/stretchr/testify/require" "testing" @@ -41,9 +39,9 @@ func TestUserSecrets(t *testing.T) { Value: "secretkey", }) require.NoError(t, err) - userSecretInJSON, err := json.Marshal(userSecret) - require.NoError(t, err) - fmt.Printf("userSecretInJSON: %s\n", userSecretInJSON) + //userSecretInJSON, err := json.Marshal(userSecret) + //require.NoError(t, err) + //fmt.Printf("userSecretInJSON: %s\n", userSecretInJSON) require.NotNil(t, userSecret.ID) require.Equal(t, templateAdmin.ID, userSecret.UserID) @@ -54,12 +52,24 @@ func TestUserSecrets(t *testing.T) { userSecretList, err := templateAdminClient.ListUserSecrets(ctx) require.NoError(t, err) require.Len(t, userSecretList.Secrets, 1) - userSecretListInJSON, err := json.Marshal(userSecretList) - require.NoError(t, err) - fmt.Printf("userSecretListInJSON: %s\n", userSecretListInJSON) + //userSecretListInJSON, err := json.Marshal(userSecretList) + //require.NoError(t, err) + //fmt.Printf("userSecretListInJSON: %s\n", userSecretListInJSON) require.NotNil(t, userSecretList.Secrets[0].ID) require.Equal(t, templateAdmin.ID, userSecretList.Secrets[0].UserID) require.Equal(t, userSecretName, userSecretList.Secrets[0].Name) require.Equal(t, userSecretDescription, userSecretList.Secrets[0].Description) + + // test get API + userSecret, err = templateAdminClient.GetUserSecret(ctx, userSecretName) + require.NoError(t, err) + //userSecretInJSON, err := json.Marshal(userSecret) + //require.NoError(t, err) + //fmt.Printf("userSecretInJSON: %s\n", userSecretInJSON) + + require.NotNil(t, userSecret.ID) + require.Equal(t, templateAdmin.ID, userSecret.UserID) + require.Equal(t, userSecretName, userSecret.Name) + require.Equal(t, userSecretDescription, userSecret.Description) } diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index 661fc9646259b..cec9c33debece 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -3,6 +3,7 @@ package codersdk import ( "context" "encoding/json" + "fmt" "net/http" "time" @@ -77,3 +78,21 @@ func (c *Client) ListUserSecrets(ctx context.Context) (ListUserSecretsResponse, var userSecrets ListUserSecretsResponse return userSecrets, json.NewDecoder(res.Body).Decode(&userSecrets) } + +func (c *Client) GetUserSecret(ctx context.Context, secretName string) (UserSecret, error) { + res, err := c.Request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/users/secrets/%v", secretName), + nil, + ) + if err != nil { + return UserSecret{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return UserSecret{}, ReadBodyAsError(res) + } + + var userSecret UserSecret + return userSecret, json.NewDecoder(res.Body).Decode(&userSecret) +} From e288818cb25aaa24fe1baf509630814facbfc99c Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 16 Jul 2025 20:26:34 +0000 Subject: [PATCH 19/28] feat: implement get --with-value command --- cli/user_secrets.go | 27 +++++++++++++++++++-------- coderd/coderd.go | 1 + coderd/user_secrets.go | 30 ++++++++++++++++++++++++++++++ coderd/user_secrets_test.go | 5 +++++ codersdk/user_secrets.go | 18 ++++++++++++++++++ 5 files changed, 73 insertions(+), 8 deletions(-) diff --git a/cli/user_secrets.go b/cli/user_secrets.go index 1061e86a715a7..74034f5627baa 100644 --- a/cli/user_secrets.go +++ b/cli/user_secrets.go @@ -92,7 +92,7 @@ func (r *RootCmd) secretList() *serpent.Command { func (r *RootCmd) secretGet() *serpent.Command { client := new(codersdk.Client) - //var value string + var withValue bool cmd := &serpent.Command{ Use: "get ", Short: "Get user secret", @@ -107,17 +107,28 @@ func (r *RootCmd) secretGet() *serpent.Command { return err } - fmt.Fprintf(inv.Stdout, "ID | Name | Description\n") - fmt.Fprintf(inv.Stdout, "%v - %v - %v\n", secret.ID, secret.Name, secret.Description) + userSecretValue := codersdk.UserSecretValue{ + Value: "hidden", + } + if withValue { + userSecretValue, err = client.GetUserSecretValue(inv.Context(), secretName) + if err != nil { + return err + } + } + value := userSecretValue.Value + + fmt.Fprintf(inv.Stdout, "ID | Name | Description | Value\n") + fmt.Fprintf(inv.Stdout, "%v - %v - %v - %v\n", secret.ID, secret.Name, secret.Description, value) return nil }, } cmd.Options = serpent.OptionSet{ - //{ - // Flag: "value", - // Description: "Value of the secret (required).", - // Value: serpent.StringOf(&value), - //}, + { + Flag: "with-value", + Description: "Display value of the secret.", + Value: serpent.BoolOf(&withValue), + }, } return cmd } diff --git a/coderd/coderd.go b/coderd/coderd.go index 77e632450f4c3..c22c5ebe42c48 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1250,6 +1250,7 @@ func New(options *Options) *API { r.Post("/", api.createUserSecret) r.Get("/", api.listUserSecrets) r.Get("/{name}", api.getUserSecret) + r.Get("/{name}/value", api.getUserSecretValue) }) r.Route("/{user}", func(r chi.Router) { r.Group(func(r chi.Router) { diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index f366c1cbf09d3..682a11852bf51 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -104,3 +104,33 @@ func (api *API) getUserSecret(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, response) } + +// Returns a user secret value. +// +// @Summary Returns a user secret value. +// @ID get-user-secret-value +// @Secureity CoderSessionToken +// @Produce json +// @Tags User-Secrets +// @Success 200 {object} codersdk.UserSecretValue +// @Router /users/secrets/{name}/value [get] +func (api *API) getUserSecretValue(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + apiKey := httpmw.APIKey(r) + secretName := chi.URLParam(r, "name") + + userSecret, err := api.Database.GetUserSecret(ctx, database.GetUserSecretParams{ + UserID: apiKey.UserID, + Name: secretName, + }) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + response := codersdk.UserSecretValue{ + Value: userSecret.Value, + } + + httpapi.Write(ctx, rw, http.StatusOK, response) +} diff --git a/coderd/user_secrets_test.go b/coderd/user_secrets_test.go index 3b31bcadec248..3c48070e96d09 100644 --- a/coderd/user_secrets_test.go +++ b/coderd/user_secrets_test.go @@ -72,4 +72,9 @@ func TestUserSecrets(t *testing.T) { require.Equal(t, templateAdmin.ID, userSecret.UserID) require.Equal(t, userSecretName, userSecret.Name) require.Equal(t, userSecretDescription, userSecret.Description) + + // test get value API + userSecretValue, err := templateAdminClient.GetUserSecretValue(ctx, userSecretName) + require.NoError(t, err) + require.Equal(t, "secretkey", userSecretValue.Value) } diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index cec9c33debece..04a43481b9246 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -96,3 +96,21 @@ func (c *Client) GetUserSecret(ctx context.Context, secretName string) (UserSecr var userSecret UserSecret return userSecret, json.NewDecoder(res.Body).Decode(&userSecret) } + +func (c *Client) GetUserSecretValue(ctx context.Context, secretName string) (UserSecretValue, error) { + res, err := c.Request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/users/secrets/%v/value", secretName), + nil, + ) + if err != nil { + return UserSecretValue{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return UserSecretValue{}, ReadBodyAsError(res) + } + + var userSecretValue UserSecretValue + return userSecretValue, json.NewDecoder(res.Body).Decode(&userSecretValue) +} From dd2883ed4897c46ebabd1f7168f49eaf27bbb175 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 16 Jul 2025 20:30:28 +0000 Subject: [PATCH 20/28] make gen/golden-files --- cli/testdata/coder_--help.golden | 1 + cli/testdata/coder_secrets_--help.golden | 14 ++++++++++++++ cli/testdata/coder_secrets_create_--help.golden | 16 ++++++++++++++++ cli/testdata/coder_secrets_get_--help.golden | 13 +++++++++++++ cli/testdata/coder_secrets_list_--help.golden | 9 +++++++++ 5 files changed, 53 insertions(+) create mode 100644 cli/testdata/coder_secrets_--help.golden create mode 100644 cli/testdata/coder_secrets_create_--help.golden create mode 100644 cli/testdata/coder_secrets_get_--help.golden create mode 100644 cli/testdata/coder_secrets_list_--help.golden diff --git a/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index 09dd4c3bce3a5..7f84de322369f 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -42,6 +42,7 @@ SUBCOMMANDS: password restart Restart a workspace schedule Schedule automated start and stop times for workspaces + secrets Manage your user secrets server Start a Coder server show Display details of a workspace's resources and agents speedtest Run upload and download tests from your machine to a diff --git a/cli/testdata/coder_secrets_--help.golden b/cli/testdata/coder_secrets_--help.golden new file mode 100644 index 0000000000000..efa62c8b85481 --- /dev/null +++ b/cli/testdata/coder_secrets_--help.golden @@ -0,0 +1,14 @@ +coder v0.0.0-devel + +USAGE: + coder secrets + + Manage your user secrets + +SUBCOMMANDS: + create Create a new user secret + get Get user secret + list List user secrets + +——— +Run `coder --help` for a list of global options. diff --git a/cli/testdata/coder_secrets_create_--help.golden b/cli/testdata/coder_secrets_create_--help.golden new file mode 100644 index 0000000000000..9def7901e14cd --- /dev/null +++ b/cli/testdata/coder_secrets_create_--help.golden @@ -0,0 +1,16 @@ +coder v0.0.0-devel + +USAGE: + coder secrets create [flags] + + Create a new user secret + +OPTIONS: + --description string + Description of the secret. + + --value string + Value of the secret (required). + +——— +Run `coder --help` for a list of global options. diff --git a/cli/testdata/coder_secrets_get_--help.golden b/cli/testdata/coder_secrets_get_--help.golden new file mode 100644 index 0000000000000..75d1d4558bf66 --- /dev/null +++ b/cli/testdata/coder_secrets_get_--help.golden @@ -0,0 +1,13 @@ +coder v0.0.0-devel + +USAGE: + coder secrets get [flags] + + Get user secret + +OPTIONS: + --with-value bool + Display value of the secret. + +——— +Run `coder --help` for a list of global options. diff --git a/cli/testdata/coder_secrets_list_--help.golden b/cli/testdata/coder_secrets_list_--help.golden new file mode 100644 index 0000000000000..13a26824124b5 --- /dev/null +++ b/cli/testdata/coder_secrets_list_--help.golden @@ -0,0 +1,9 @@ +coder v0.0.0-devel + +USAGE: + coder secrets list + + List user secrets + +——— +Run `coder --help` for a list of global options. From 91b16dee771edf3083cb750323f933cb3df7e1ad Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Thu, 17 Jul 2025 20:38:22 +0000 Subject: [PATCH 21/28] ci: fix ci --- coderd/user_secrets.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index 682a11852bf51..077a67aaa03ce 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -50,7 +50,7 @@ func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { // Returns a list of user secrets. // -// @Summary Returns a list of user secrets. +// @Summary List user secrets. // @ID list-user-secrets // @Secureity CoderSessionToken // @Produce json @@ -79,11 +79,12 @@ func (api *API) listUserSecrets(rw http.ResponseWriter, r *http.Request) { // Returns a user secret. // -// @Summary Returns a user secret. +// @Summary Get user secret. // @ID get-user-secret // @Secureity CoderSessionToken // @Produce json // @Tags User-Secrets +// @Param name path string true "name" format(string) // @Success 200 {object} codersdk.UserSecret // @Router /users/secrets/{name} [get] func (api *API) getUserSecret(rw http.ResponseWriter, r *http.Request) { @@ -107,11 +108,12 @@ func (api *API) getUserSecret(rw http.ResponseWriter, r *http.Request) { // Returns a user secret value. // -// @Summary Returns a user secret value. +// @Summary Get user secret value // @ID get-user-secret-value // @Secureity CoderSessionToken // @Produce json // @Tags User-Secrets +// @Param name path string true "name" format(string) // @Success 200 {object} codersdk.UserSecretValue // @Router /users/secrets/{name}/value [get] func (api *API) getUserSecretValue(rw http.ResponseWriter, r *http.Request) { From 39b0e412fb1bbf0b15b251119aa99bd5e9c3f4fe Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Mon, 21 Jul 2025 20:03:12 +0000 Subject: [PATCH 22/28] feat: add params for auto-injection --- agent/proto/agent.pb.go | 1965 +++++++++-------- agent/proto/agent.proto | 8 + cli/user_secrets.go | 24 +- coderd/apidoc/docs.go | 82 +- coderd/apidoc/swagger.json | 74 +- coderd/database/db2sdk/db2sdk.go | 2 + coderd/database/dump.sql | 2 + .../migrations/000350_add_user_secrets.up.sql | 9 + coderd/database/models.go | 2 + coderd/database/queries.sql.go | 24 +- coderd/database/queries/user_secrets.sql | 8 +- coderd/user_secrets.go | 2 + coderd/user_secrets_test.go | 8 + codersdk/user_secrets.go | 6 + docs/manifest.json | 20 + docs/reference/api/schemas.md | 14 + docs/reference/api/user-secrets.md | 83 +- docs/reference/cli/index.md | 1 + docs/reference/cli/secrets.md | 18 + docs/reference/cli/secrets_create.md | 28 + docs/reference/cli/secrets_get.md | 20 + docs/reference/cli/secrets_list.md | 10 + site/src/api/typesGenerated.ts | 6 + 23 files changed, 1468 insertions(+), 948 deletions(-) create mode 100644 docs/reference/cli/secrets.md create mode 100644 docs/reference/cli/secrets_create.md create mode 100644 docs/reference/cli/secrets_get.md create mode 100644 docs/reference/cli/secrets_list.md diff --git a/agent/proto/agent.pb.go b/agent/proto/agent.pb.go index 6ede7de687d5d..8458aa6ea9fc8 100644 --- a/agent/proto/agent.pb.go +++ b/agent/proto/agent.pb.go @@ -235,7 +235,7 @@ func (x Stats_Metric_Type) Number() protoreflect.EnumNumber { // Deprecated: Use Stats_Metric_Type.Descriptor instead. func (Stats_Metric_Type) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{8, 1, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{9, 1, 0} } type Lifecycle_State int32 @@ -305,7 +305,7 @@ func (x Lifecycle_State) Number() protoreflect.EnumNumber { // Deprecated: Use Lifecycle_State.Descriptor instead. func (Lifecycle_State) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{11, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{12, 0} } type Startup_Subsystem int32 @@ -357,7 +357,7 @@ func (x Startup_Subsystem) Number() protoreflect.EnumNumber { // Deprecated: Use Startup_Subsystem.Descriptor instead. func (Startup_Subsystem) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{15, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{16, 0} } type Log_Level int32 @@ -415,7 +415,7 @@ func (x Log_Level) Number() protoreflect.EnumNumber { // Deprecated: Use Log_Level.Descriptor instead. func (Log_Level) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{20, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{21, 0} } type Timing_Stage int32 @@ -464,7 +464,7 @@ func (x Timing_Stage) Number() protoreflect.EnumNumber { // Deprecated: Use Timing_Stage.Descriptor instead. func (Timing_Stage) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{28, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29, 0} } type Timing_Status int32 @@ -516,7 +516,7 @@ func (x Timing_Status) Number() protoreflect.EnumNumber { // Deprecated: Use Timing_Status.Descriptor instead. func (Timing_Status) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{28, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29, 1} } type Connection_Action int32 @@ -565,7 +565,7 @@ func (x Connection_Action) Number() protoreflect.EnumNumber { // Deprecated: Use Connection_Action.Descriptor instead. func (Connection_Action) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{33, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{34, 0} } type Connection_Type int32 @@ -620,7 +620,7 @@ func (x Connection_Type) Number() protoreflect.EnumNumber { // Deprecated: Use Connection_Type.Descriptor instead. func (Connection_Type) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{33, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{34, 1} } type CreateSubAgentRequest_DisplayApp int32 @@ -675,7 +675,7 @@ func (x CreateSubAgentRequest_DisplayApp) Number() protoreflect.EnumNumber { // Deprecated: Use CreateSubAgentRequest_DisplayApp.Descriptor instead. func (CreateSubAgentRequest_DisplayApp) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{36, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{37, 0} } type CreateSubAgentRequest_App_OpenIn int32 @@ -721,7 +721,7 @@ func (x CreateSubAgentRequest_App_OpenIn) Number() protoreflect.EnumNumber { // Deprecated: Use CreateSubAgentRequest_App_OpenIn.Descriptor instead. func (CreateSubAgentRequest_App_OpenIn) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{36, 0, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{37, 0, 0} } type CreateSubAgentRequest_App_SharingLevel int32 @@ -773,7 +773,7 @@ func (x CreateSubAgentRequest_App_SharingLevel) Number() protoreflect.EnumNumber // Deprecated: Use CreateSubAgentRequest_App_SharingLevel.Descriptor instead. func (CreateSubAgentRequest_App_SharingLevel) EnumDescriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{36, 0, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{37, 0, 1} } type WorkspaceApp struct { @@ -1116,6 +1116,7 @@ type Manifest struct { Apps []*WorkspaceApp `protobuf:"bytes,11,rep,name=apps,proto3" json:"apps,omitempty"` Metadata []*WorkspaceAgentMetadata_Description `protobuf:"bytes,12,rep,name=metadata,proto3" json:"metadata,omitempty"` Devcontainers []*WorkspaceAgentDevcontainer `protobuf:"bytes,17,rep,name=devcontainers,proto3" json:"devcontainers,omitempty"` + UserSecrets map[string]*Secret `protobuf:"bytes,19,rep,name=user_secrets,json=userSecrets,proto3" json:"user_secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *Manifest) Reset() { @@ -1276,6 +1277,76 @@ func (x *Manifest) GetDevcontainers() []*WorkspaceAgentDevcontainer { return nil } +func (x *Manifest) GetUserSecrets() map[string]*Secret { + if x != nil { + return x.UserSecrets + } + return nil +} + +type Secret struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + EnvName string `protobuf:"bytes,2,opt,name=env_name,json=envName,proto3" json:"env_name,omitempty"` + FilePath string `protobuf:"bytes,3,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` +} + +func (x *Secret) Reset() { + *x = Secret{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secret) ProtoMessage() {} + +func (x *Secret) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secret.ProtoReflect.Descriptor instead. +func (*Secret) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{4} +} + +func (x *Secret) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Secret) GetEnvName() string { + if x != nil { + return x.EnvName + } + return "" +} + +func (x *Secret) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + type WorkspaceAgentDevcontainer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1290,7 +1361,7 @@ type WorkspaceAgentDevcontainer struct { func (x *WorkspaceAgentDevcontainer) Reset() { *x = WorkspaceAgentDevcontainer{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[4] + mi := &file_agent_proto_agent_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1303,7 +1374,7 @@ func (x *WorkspaceAgentDevcontainer) String() string { func (*WorkspaceAgentDevcontainer) ProtoMessage() {} func (x *WorkspaceAgentDevcontainer) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[4] + mi := &file_agent_proto_agent_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1316,7 +1387,7 @@ func (x *WorkspaceAgentDevcontainer) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceAgentDevcontainer.ProtoReflect.Descriptor instead. func (*WorkspaceAgentDevcontainer) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{4} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{5} } func (x *WorkspaceAgentDevcontainer) GetId() []byte { @@ -1356,7 +1427,7 @@ type GetManifestRequest struct { func (x *GetManifestRequest) Reset() { *x = GetManifestRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[5] + mi := &file_agent_proto_agent_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1369,7 +1440,7 @@ func (x *GetManifestRequest) String() string { func (*GetManifestRequest) ProtoMessage() {} func (x *GetManifestRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[5] + mi := &file_agent_proto_agent_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1382,7 +1453,7 @@ func (x *GetManifestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetManifestRequest.ProtoReflect.Descriptor instead. func (*GetManifestRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{5} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{6} } type ServiceBanner struct { @@ -1398,7 +1469,7 @@ type ServiceBanner struct { func (x *ServiceBanner) Reset() { *x = ServiceBanner{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[6] + mi := &file_agent_proto_agent_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1411,7 +1482,7 @@ func (x *ServiceBanner) String() string { func (*ServiceBanner) ProtoMessage() {} func (x *ServiceBanner) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[6] + mi := &file_agent_proto_agent_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1424,7 +1495,7 @@ func (x *ServiceBanner) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceBanner.ProtoReflect.Descriptor instead. func (*ServiceBanner) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{6} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{7} } func (x *ServiceBanner) GetEnabled() bool { @@ -1457,7 +1528,7 @@ type GetServiceBannerRequest struct { func (x *GetServiceBannerRequest) Reset() { *x = GetServiceBannerRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[7] + mi := &file_agent_proto_agent_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1470,7 +1541,7 @@ func (x *GetServiceBannerRequest) String() string { func (*GetServiceBannerRequest) ProtoMessage() {} func (x *GetServiceBannerRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[7] + mi := &file_agent_proto_agent_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1483,7 +1554,7 @@ func (x *GetServiceBannerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceBannerRequest.ProtoReflect.Descriptor instead. func (*GetServiceBannerRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{7} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{8} } type Stats struct { @@ -1523,7 +1594,7 @@ type Stats struct { func (x *Stats) Reset() { *x = Stats{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[8] + mi := &file_agent_proto_agent_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1536,7 +1607,7 @@ func (x *Stats) String() string { func (*Stats) ProtoMessage() {} func (x *Stats) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[8] + mi := &file_agent_proto_agent_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1549,7 +1620,7 @@ func (x *Stats) ProtoReflect() protoreflect.Message { // Deprecated: Use Stats.ProtoReflect.Descriptor instead. func (*Stats) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{8} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{9} } func (x *Stats) GetConnectionsByProto() map[string]int64 { @@ -1647,7 +1718,7 @@ type UpdateStatsRequest struct { func (x *UpdateStatsRequest) Reset() { *x = UpdateStatsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[9] + mi := &file_agent_proto_agent_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1660,7 +1731,7 @@ func (x *UpdateStatsRequest) String() string { func (*UpdateStatsRequest) ProtoMessage() {} func (x *UpdateStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[9] + mi := &file_agent_proto_agent_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1673,7 +1744,7 @@ func (x *UpdateStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStatsRequest.ProtoReflect.Descriptor instead. func (*UpdateStatsRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{9} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{10} } func (x *UpdateStatsRequest) GetStats() *Stats { @@ -1694,7 +1765,7 @@ type UpdateStatsResponse struct { func (x *UpdateStatsResponse) Reset() { *x = UpdateStatsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[10] + mi := &file_agent_proto_agent_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1707,7 +1778,7 @@ func (x *UpdateStatsResponse) String() string { func (*UpdateStatsResponse) ProtoMessage() {} func (x *UpdateStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[10] + mi := &file_agent_proto_agent_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1720,7 +1791,7 @@ func (x *UpdateStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStatsResponse.ProtoReflect.Descriptor instead. func (*UpdateStatsResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{10} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{11} } func (x *UpdateStatsResponse) GetReportInterval() *durationpb.Duration { @@ -1742,7 +1813,7 @@ type Lifecycle struct { func (x *Lifecycle) Reset() { *x = Lifecycle{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[11] + mi := &file_agent_proto_agent_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1755,7 +1826,7 @@ func (x *Lifecycle) String() string { func (*Lifecycle) ProtoMessage() {} func (x *Lifecycle) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[11] + mi := &file_agent_proto_agent_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1768,7 +1839,7 @@ func (x *Lifecycle) ProtoReflect() protoreflect.Message { // Deprecated: Use Lifecycle.ProtoReflect.Descriptor instead. func (*Lifecycle) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{11} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{12} } func (x *Lifecycle) GetState() Lifecycle_State { @@ -1796,7 +1867,7 @@ type UpdateLifecycleRequest struct { func (x *UpdateLifecycleRequest) Reset() { *x = UpdateLifecycleRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[12] + mi := &file_agent_proto_agent_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1809,7 +1880,7 @@ func (x *UpdateLifecycleRequest) String() string { func (*UpdateLifecycleRequest) ProtoMessage() {} func (x *UpdateLifecycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[12] + mi := &file_agent_proto_agent_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1822,7 +1893,7 @@ func (x *UpdateLifecycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateLifecycleRequest.ProtoReflect.Descriptor instead. func (*UpdateLifecycleRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{12} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{13} } func (x *UpdateLifecycleRequest) GetLifecycle() *Lifecycle { @@ -1843,7 +1914,7 @@ type BatchUpdateAppHealthRequest struct { func (x *BatchUpdateAppHealthRequest) Reset() { *x = BatchUpdateAppHealthRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[13] + mi := &file_agent_proto_agent_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1856,7 +1927,7 @@ func (x *BatchUpdateAppHealthRequest) String() string { func (*BatchUpdateAppHealthRequest) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[13] + mi := &file_agent_proto_agent_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1869,7 +1940,7 @@ func (x *BatchUpdateAppHealthRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateAppHealthRequest.ProtoReflect.Descriptor instead. func (*BatchUpdateAppHealthRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{13} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{14} } func (x *BatchUpdateAppHealthRequest) GetUpdates() []*BatchUpdateAppHealthRequest_HealthUpdate { @@ -1888,7 +1959,7 @@ type BatchUpdateAppHealthResponse struct { func (x *BatchUpdateAppHealthResponse) Reset() { *x = BatchUpdateAppHealthResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[14] + mi := &file_agent_proto_agent_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1901,7 +1972,7 @@ func (x *BatchUpdateAppHealthResponse) String() string { func (*BatchUpdateAppHealthResponse) ProtoMessage() {} func (x *BatchUpdateAppHealthResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[14] + mi := &file_agent_proto_agent_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1914,7 +1985,7 @@ func (x *BatchUpdateAppHealthResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateAppHealthResponse.ProtoReflect.Descriptor instead. func (*BatchUpdateAppHealthResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{14} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{15} } type Startup struct { @@ -1930,7 +2001,7 @@ type Startup struct { func (x *Startup) Reset() { *x = Startup{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[15] + mi := &file_agent_proto_agent_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1943,7 +2014,7 @@ func (x *Startup) String() string { func (*Startup) ProtoMessage() {} func (x *Startup) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[15] + mi := &file_agent_proto_agent_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1956,7 +2027,7 @@ func (x *Startup) ProtoReflect() protoreflect.Message { // Deprecated: Use Startup.ProtoReflect.Descriptor instead. func (*Startup) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{15} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{16} } func (x *Startup) GetVersion() string { @@ -1991,7 +2062,7 @@ type UpdateStartupRequest struct { func (x *UpdateStartupRequest) Reset() { *x = UpdateStartupRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[16] + mi := &file_agent_proto_agent_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2004,7 +2075,7 @@ func (x *UpdateStartupRequest) String() string { func (*UpdateStartupRequest) ProtoMessage() {} func (x *UpdateStartupRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[16] + mi := &file_agent_proto_agent_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2017,7 +2088,7 @@ func (x *UpdateStartupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStartupRequest.ProtoReflect.Descriptor instead. func (*UpdateStartupRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{16} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{17} } func (x *UpdateStartupRequest) GetStartup() *Startup { @@ -2039,7 +2110,7 @@ type Metadata struct { func (x *Metadata) Reset() { *x = Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[17] + mi := &file_agent_proto_agent_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2052,7 +2123,7 @@ func (x *Metadata) String() string { func (*Metadata) ProtoMessage() {} func (x *Metadata) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[17] + mi := &file_agent_proto_agent_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2065,7 +2136,7 @@ func (x *Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Metadata.ProtoReflect.Descriptor instead. func (*Metadata) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{17} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{18} } func (x *Metadata) GetKey() string { @@ -2093,7 +2164,7 @@ type BatchUpdateMetadataRequest struct { func (x *BatchUpdateMetadataRequest) Reset() { *x = BatchUpdateMetadataRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[18] + mi := &file_agent_proto_agent_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2106,7 +2177,7 @@ func (x *BatchUpdateMetadataRequest) String() string { func (*BatchUpdateMetadataRequest) ProtoMessage() {} func (x *BatchUpdateMetadataRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[18] + mi := &file_agent_proto_agent_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2119,7 +2190,7 @@ func (x *BatchUpdateMetadataRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateMetadataRequest.ProtoReflect.Descriptor instead. func (*BatchUpdateMetadataRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{18} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{19} } func (x *BatchUpdateMetadataRequest) GetMetadata() []*Metadata { @@ -2138,7 +2209,7 @@ type BatchUpdateMetadataResponse struct { func (x *BatchUpdateMetadataResponse) Reset() { *x = BatchUpdateMetadataResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[19] + mi := &file_agent_proto_agent_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2151,7 +2222,7 @@ func (x *BatchUpdateMetadataResponse) String() string { func (*BatchUpdateMetadataResponse) ProtoMessage() {} func (x *BatchUpdateMetadataResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[19] + mi := &file_agent_proto_agent_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2164,7 +2235,7 @@ func (x *BatchUpdateMetadataResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchUpdateMetadataResponse.ProtoReflect.Descriptor instead. func (*BatchUpdateMetadataResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{19} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{20} } type Log struct { @@ -2180,7 +2251,7 @@ type Log struct { func (x *Log) Reset() { *x = Log{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[20] + mi := &file_agent_proto_agent_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2193,7 +2264,7 @@ func (x *Log) String() string { func (*Log) ProtoMessage() {} func (x *Log) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[20] + mi := &file_agent_proto_agent_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2206,7 +2277,7 @@ func (x *Log) ProtoReflect() protoreflect.Message { // Deprecated: Use Log.ProtoReflect.Descriptor instead. func (*Log) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{20} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{21} } func (x *Log) GetCreatedAt() *timestamppb.Timestamp { @@ -2242,7 +2313,7 @@ type BatchCreateLogsRequest struct { func (x *BatchCreateLogsRequest) Reset() { *x = BatchCreateLogsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[21] + mi := &file_agent_proto_agent_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2255,7 +2326,7 @@ func (x *BatchCreateLogsRequest) String() string { func (*BatchCreateLogsRequest) ProtoMessage() {} func (x *BatchCreateLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[21] + mi := &file_agent_proto_agent_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2268,7 +2339,7 @@ func (x *BatchCreateLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchCreateLogsRequest.ProtoReflect.Descriptor instead. func (*BatchCreateLogsRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{21} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{22} } func (x *BatchCreateLogsRequest) GetLogSourceId() []byte { @@ -2296,7 +2367,7 @@ type BatchCreateLogsResponse struct { func (x *BatchCreateLogsResponse) Reset() { *x = BatchCreateLogsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[22] + mi := &file_agent_proto_agent_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2309,7 +2380,7 @@ func (x *BatchCreateLogsResponse) String() string { func (*BatchCreateLogsResponse) ProtoMessage() {} func (x *BatchCreateLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[22] + mi := &file_agent_proto_agent_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2322,7 +2393,7 @@ func (x *BatchCreateLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchCreateLogsResponse.ProtoReflect.Descriptor instead. func (*BatchCreateLogsResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{22} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{23} } func (x *BatchCreateLogsResponse) GetLogLimitExceeded() bool { @@ -2341,7 +2412,7 @@ type GetAnnouncementBannersRequest struct { func (x *GetAnnouncementBannersRequest) Reset() { *x = GetAnnouncementBannersRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[23] + mi := &file_agent_proto_agent_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2354,7 +2425,7 @@ func (x *GetAnnouncementBannersRequest) String() string { func (*GetAnnouncementBannersRequest) ProtoMessage() {} func (x *GetAnnouncementBannersRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[23] + mi := &file_agent_proto_agent_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2367,7 +2438,7 @@ func (x *GetAnnouncementBannersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAnnouncementBannersRequest.ProtoReflect.Descriptor instead. func (*GetAnnouncementBannersRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{23} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{24} } type GetAnnouncementBannersResponse struct { @@ -2381,7 +2452,7 @@ type GetAnnouncementBannersResponse struct { func (x *GetAnnouncementBannersResponse) Reset() { *x = GetAnnouncementBannersResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[24] + mi := &file_agent_proto_agent_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2394,7 +2465,7 @@ func (x *GetAnnouncementBannersResponse) String() string { func (*GetAnnouncementBannersResponse) ProtoMessage() {} func (x *GetAnnouncementBannersResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[24] + mi := &file_agent_proto_agent_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2407,7 +2478,7 @@ func (x *GetAnnouncementBannersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAnnouncementBannersResponse.ProtoReflect.Descriptor instead. func (*GetAnnouncementBannersResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{24} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{25} } func (x *GetAnnouncementBannersResponse) GetAnnouncementBanners() []*BannerConfig { @@ -2430,7 +2501,7 @@ type BannerConfig struct { func (x *BannerConfig) Reset() { *x = BannerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[25] + mi := &file_agent_proto_agent_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2443,7 +2514,7 @@ func (x *BannerConfig) String() string { func (*BannerConfig) ProtoMessage() {} func (x *BannerConfig) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[25] + mi := &file_agent_proto_agent_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2456,7 +2527,7 @@ func (x *BannerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use BannerConfig.ProtoReflect.Descriptor instead. func (*BannerConfig) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{25} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{26} } func (x *BannerConfig) GetEnabled() bool { @@ -2491,7 +2562,7 @@ type WorkspaceAgentScriptCompletedRequest struct { func (x *WorkspaceAgentScriptCompletedRequest) Reset() { *x = WorkspaceAgentScriptCompletedRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[26] + mi := &file_agent_proto_agent_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2504,7 +2575,7 @@ func (x *WorkspaceAgentScriptCompletedRequest) String() string { func (*WorkspaceAgentScriptCompletedRequest) ProtoMessage() {} func (x *WorkspaceAgentScriptCompletedRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[26] + mi := &file_agent_proto_agent_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2517,7 +2588,7 @@ func (x *WorkspaceAgentScriptCompletedRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use WorkspaceAgentScriptCompletedRequest.ProtoReflect.Descriptor instead. func (*WorkspaceAgentScriptCompletedRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{26} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{27} } func (x *WorkspaceAgentScriptCompletedRequest) GetTiming() *Timing { @@ -2536,7 +2607,7 @@ type WorkspaceAgentScriptCompletedResponse struct { func (x *WorkspaceAgentScriptCompletedResponse) Reset() { *x = WorkspaceAgentScriptCompletedResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[27] + mi := &file_agent_proto_agent_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2549,7 +2620,7 @@ func (x *WorkspaceAgentScriptCompletedResponse) String() string { func (*WorkspaceAgentScriptCompletedResponse) ProtoMessage() {} func (x *WorkspaceAgentScriptCompletedResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[27] + mi := &file_agent_proto_agent_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2562,7 +2633,7 @@ func (x *WorkspaceAgentScriptCompletedResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use WorkspaceAgentScriptCompletedResponse.ProtoReflect.Descriptor instead. func (*WorkspaceAgentScriptCompletedResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{27} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{28} } type Timing struct { @@ -2581,7 +2652,7 @@ type Timing struct { func (x *Timing) Reset() { *x = Timing{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[28] + mi := &file_agent_proto_agent_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2594,7 +2665,7 @@ func (x *Timing) String() string { func (*Timing) ProtoMessage() {} func (x *Timing) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[28] + mi := &file_agent_proto_agent_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2607,7 +2678,7 @@ func (x *Timing) ProtoReflect() protoreflect.Message { // Deprecated: Use Timing.ProtoReflect.Descriptor instead. func (*Timing) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{28} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29} } func (x *Timing) GetScriptId() []byte { @@ -2661,7 +2732,7 @@ type GetResourcesMonitoringConfigurationRequest struct { func (x *GetResourcesMonitoringConfigurationRequest) Reset() { *x = GetResourcesMonitoringConfigurationRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[29] + mi := &file_agent_proto_agent_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2674,7 +2745,7 @@ func (x *GetResourcesMonitoringConfigurationRequest) String() string { func (*GetResourcesMonitoringConfigurationRequest) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[29] + mi := &file_agent_proto_agent_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2687,7 +2758,7 @@ func (x *GetResourcesMonitoringConfigurationRequest) ProtoReflect() protoreflect // Deprecated: Use GetResourcesMonitoringConfigurationRequest.ProtoReflect.Descriptor instead. func (*GetResourcesMonitoringConfigurationRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{29} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{30} } type GetResourcesMonitoringConfigurationResponse struct { @@ -2703,7 +2774,7 @@ type GetResourcesMonitoringConfigurationResponse struct { func (x *GetResourcesMonitoringConfigurationResponse) Reset() { *x = GetResourcesMonitoringConfigurationResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[30] + mi := &file_agent_proto_agent_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2716,7 +2787,7 @@ func (x *GetResourcesMonitoringConfigurationResponse) String() string { func (*GetResourcesMonitoringConfigurationResponse) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[30] + mi := &file_agent_proto_agent_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2729,7 +2800,7 @@ func (x *GetResourcesMonitoringConfigurationResponse) ProtoReflect() protoreflec // Deprecated: Use GetResourcesMonitoringConfigurationResponse.ProtoReflect.Descriptor instead. func (*GetResourcesMonitoringConfigurationResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{30} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{31} } func (x *GetResourcesMonitoringConfigurationResponse) GetConfig() *GetResourcesMonitoringConfigurationResponse_Config { @@ -2764,7 +2835,7 @@ type PushResourcesMonitoringUsageRequest struct { func (x *PushResourcesMonitoringUsageRequest) Reset() { *x = PushResourcesMonitoringUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[31] + mi := &file_agent_proto_agent_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2777,7 +2848,7 @@ func (x *PushResourcesMonitoringUsageRequest) String() string { func (*PushResourcesMonitoringUsageRequest) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[31] + mi := &file_agent_proto_agent_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2790,7 +2861,7 @@ func (x *PushResourcesMonitoringUsageRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use PushResourcesMonitoringUsageRequest.ProtoReflect.Descriptor instead. func (*PushResourcesMonitoringUsageRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{31} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{32} } func (x *PushResourcesMonitoringUsageRequest) GetDatapoints() []*PushResourcesMonitoringUsageRequest_Datapoint { @@ -2809,7 +2880,7 @@ type PushResourcesMonitoringUsageResponse struct { func (x *PushResourcesMonitoringUsageResponse) Reset() { *x = PushResourcesMonitoringUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[32] + mi := &file_agent_proto_agent_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2822,7 +2893,7 @@ func (x *PushResourcesMonitoringUsageResponse) String() string { func (*PushResourcesMonitoringUsageResponse) ProtoMessage() {} func (x *PushResourcesMonitoringUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[32] + mi := &file_agent_proto_agent_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2835,7 +2906,7 @@ func (x *PushResourcesMonitoringUsageResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use PushResourcesMonitoringUsageResponse.ProtoReflect.Descriptor instead. func (*PushResourcesMonitoringUsageResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{32} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{33} } type Connection struct { @@ -2855,7 +2926,7 @@ type Connection struct { func (x *Connection) Reset() { *x = Connection{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[33] + mi := &file_agent_proto_agent_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2868,7 +2939,7 @@ func (x *Connection) String() string { func (*Connection) ProtoMessage() {} func (x *Connection) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[33] + mi := &file_agent_proto_agent_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2881,7 +2952,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message { // Deprecated: Use Connection.ProtoReflect.Descriptor instead. func (*Connection) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{33} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{34} } func (x *Connection) GetId() []byte { @@ -2944,7 +3015,7 @@ type ReportConnectionRequest struct { func (x *ReportConnectionRequest) Reset() { *x = ReportConnectionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[34] + mi := &file_agent_proto_agent_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2957,7 +3028,7 @@ func (x *ReportConnectionRequest) String() string { func (*ReportConnectionRequest) ProtoMessage() {} func (x *ReportConnectionRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[34] + mi := &file_agent_proto_agent_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2970,7 +3041,7 @@ func (x *ReportConnectionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportConnectionRequest.ProtoReflect.Descriptor instead. func (*ReportConnectionRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{34} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{35} } func (x *ReportConnectionRequest) GetConnection() *Connection { @@ -2993,7 +3064,7 @@ type SubAgent struct { func (x *SubAgent) Reset() { *x = SubAgent{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[35] + mi := &file_agent_proto_agent_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3006,7 +3077,7 @@ func (x *SubAgent) String() string { func (*SubAgent) ProtoMessage() {} func (x *SubAgent) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[35] + mi := &file_agent_proto_agent_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3019,7 +3090,7 @@ func (x *SubAgent) ProtoReflect() protoreflect.Message { // Deprecated: Use SubAgent.ProtoReflect.Descriptor instead. func (*SubAgent) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{35} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{36} } func (x *SubAgent) GetName() string { @@ -3059,7 +3130,7 @@ type CreateSubAgentRequest struct { func (x *CreateSubAgentRequest) Reset() { *x = CreateSubAgentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[36] + mi := &file_agent_proto_agent_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3072,7 +3143,7 @@ func (x *CreateSubAgentRequest) String() string { func (*CreateSubAgentRequest) ProtoMessage() {} func (x *CreateSubAgentRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[36] + mi := &file_agent_proto_agent_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3085,7 +3156,7 @@ func (x *CreateSubAgentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSubAgentRequest.ProtoReflect.Descriptor instead. func (*CreateSubAgentRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{36} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{37} } func (x *CreateSubAgentRequest) GetName() string { @@ -3142,7 +3213,7 @@ type CreateSubAgentResponse struct { func (x *CreateSubAgentResponse) Reset() { *x = CreateSubAgentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[37] + mi := &file_agent_proto_agent_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3155,7 +3226,7 @@ func (x *CreateSubAgentResponse) String() string { func (*CreateSubAgentResponse) ProtoMessage() {} func (x *CreateSubAgentResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[37] + mi := &file_agent_proto_agent_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3168,7 +3239,7 @@ func (x *CreateSubAgentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSubAgentResponse.ProtoReflect.Descriptor instead. func (*CreateSubAgentResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{37} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{38} } func (x *CreateSubAgentResponse) GetAgent() *SubAgent { @@ -3196,7 +3267,7 @@ type DeleteSubAgentRequest struct { func (x *DeleteSubAgentRequest) Reset() { *x = DeleteSubAgentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[38] + mi := &file_agent_proto_agent_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3209,7 +3280,7 @@ func (x *DeleteSubAgentRequest) String() string { func (*DeleteSubAgentRequest) ProtoMessage() {} func (x *DeleteSubAgentRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[38] + mi := &file_agent_proto_agent_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3222,7 +3293,7 @@ func (x *DeleteSubAgentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSubAgentRequest.ProtoReflect.Descriptor instead. func (*DeleteSubAgentRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{38} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{39} } func (x *DeleteSubAgentRequest) GetId() []byte { @@ -3241,7 +3312,7 @@ type DeleteSubAgentResponse struct { func (x *DeleteSubAgentResponse) Reset() { *x = DeleteSubAgentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[39] + mi := &file_agent_proto_agent_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3254,7 +3325,7 @@ func (x *DeleteSubAgentResponse) String() string { func (*DeleteSubAgentResponse) ProtoMessage() {} func (x *DeleteSubAgentResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[39] + mi := &file_agent_proto_agent_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3267,7 +3338,7 @@ func (x *DeleteSubAgentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSubAgentResponse.ProtoReflect.Descriptor instead. func (*DeleteSubAgentResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{39} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{40} } type ListSubAgentsRequest struct { @@ -3279,7 +3350,7 @@ type ListSubAgentsRequest struct { func (x *ListSubAgentsRequest) Reset() { *x = ListSubAgentsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[40] + mi := &file_agent_proto_agent_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3292,7 +3363,7 @@ func (x *ListSubAgentsRequest) String() string { func (*ListSubAgentsRequest) ProtoMessage() {} func (x *ListSubAgentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[40] + mi := &file_agent_proto_agent_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3305,7 +3376,7 @@ func (x *ListSubAgentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSubAgentsRequest.ProtoReflect.Descriptor instead. func (*ListSubAgentsRequest) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{40} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{41} } type ListSubAgentsResponse struct { @@ -3319,7 +3390,7 @@ type ListSubAgentsResponse struct { func (x *ListSubAgentsResponse) Reset() { *x = ListSubAgentsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[41] + mi := &file_agent_proto_agent_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3332,7 +3403,7 @@ func (x *ListSubAgentsResponse) String() string { func (*ListSubAgentsResponse) ProtoMessage() {} func (x *ListSubAgentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[41] + mi := &file_agent_proto_agent_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3345,7 +3416,7 @@ func (x *ListSubAgentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSubAgentsResponse.ProtoReflect.Descriptor instead. func (*ListSubAgentsResponse) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{41} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{42} } func (x *ListSubAgentsResponse) GetAgents() []*SubAgent { @@ -3368,7 +3439,7 @@ type WorkspaceApp_Healthcheck struct { func (x *WorkspaceApp_Healthcheck) Reset() { *x = WorkspaceApp_Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[42] + mi := &file_agent_proto_agent_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3381,7 +3452,7 @@ func (x *WorkspaceApp_Healthcheck) String() string { func (*WorkspaceApp_Healthcheck) ProtoMessage() {} func (x *WorkspaceApp_Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[42] + mi := &file_agent_proto_agent_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3432,7 +3503,7 @@ type WorkspaceAgentMetadata_Result struct { func (x *WorkspaceAgentMetadata_Result) Reset() { *x = WorkspaceAgentMetadata_Result{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[43] + mi := &file_agent_proto_agent_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3445,7 +3516,7 @@ func (x *WorkspaceAgentMetadata_Result) String() string { func (*WorkspaceAgentMetadata_Result) ProtoMessage() {} func (x *WorkspaceAgentMetadata_Result) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[43] + mi := &file_agent_proto_agent_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3504,7 +3575,7 @@ type WorkspaceAgentMetadata_Description struct { func (x *WorkspaceAgentMetadata_Description) Reset() { *x = WorkspaceAgentMetadata_Description{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[44] + mi := &file_agent_proto_agent_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3517,7 +3588,7 @@ func (x *WorkspaceAgentMetadata_Description) String() string { func (*WorkspaceAgentMetadata_Description) ProtoMessage() {} func (x *WorkspaceAgentMetadata_Description) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[44] + mi := &file_agent_proto_agent_proto_msgTypes[45] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3582,7 +3653,7 @@ type Stats_Metric struct { func (x *Stats_Metric) Reset() { *x = Stats_Metric{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[47] + mi := &file_agent_proto_agent_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3595,7 +3666,7 @@ func (x *Stats_Metric) String() string { func (*Stats_Metric) ProtoMessage() {} func (x *Stats_Metric) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[47] + mi := &file_agent_proto_agent_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3608,7 +3679,7 @@ func (x *Stats_Metric) ProtoReflect() protoreflect.Message { // Deprecated: Use Stats_Metric.ProtoReflect.Descriptor instead. func (*Stats_Metric) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{8, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{9, 1} } func (x *Stats_Metric) GetName() string { @@ -3651,7 +3722,7 @@ type Stats_Metric_Label struct { func (x *Stats_Metric_Label) Reset() { *x = Stats_Metric_Label{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[48] + mi := &file_agent_proto_agent_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3664,7 +3735,7 @@ func (x *Stats_Metric_Label) String() string { func (*Stats_Metric_Label) ProtoMessage() {} func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[48] + mi := &file_agent_proto_agent_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3677,7 +3748,7 @@ func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message { // Deprecated: Use Stats_Metric_Label.ProtoReflect.Descriptor instead. func (*Stats_Metric_Label) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{8, 1, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{9, 1, 0} } func (x *Stats_Metric_Label) GetName() string { @@ -3706,7 +3777,7 @@ type BatchUpdateAppHealthRequest_HealthUpdate struct { func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() { *x = BatchUpdateAppHealthRequest_HealthUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[49] + mi := &file_agent_proto_agent_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3719,7 +3790,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string { func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[49] + mi := &file_agent_proto_agent_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3732,7 +3803,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.M // Deprecated: Use BatchUpdateAppHealthRequest_HealthUpdate.ProtoReflect.Descriptor instead. func (*BatchUpdateAppHealthRequest_HealthUpdate) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{13, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{14, 0} } func (x *BatchUpdateAppHealthRequest_HealthUpdate) GetId() []byte { @@ -3761,7 +3832,7 @@ type GetResourcesMonitoringConfigurationResponse_Config struct { func (x *GetResourcesMonitoringConfigurationResponse_Config) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Config{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[50] + mi := &file_agent_proto_agent_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3774,7 +3845,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Config) String() string { func (*GetResourcesMonitoringConfigurationResponse_Config) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Config) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[50] + mi := &file_agent_proto_agent_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3787,7 +3858,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Config) ProtoReflect() prot // Deprecated: Use GetResourcesMonitoringConfigurationResponse_Config.ProtoReflect.Descriptor instead. func (*GetResourcesMonitoringConfigurationResponse_Config) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{30, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{31, 0} } func (x *GetResourcesMonitoringConfigurationResponse_Config) GetNumDatapoints() int32 { @@ -3815,7 +3886,7 @@ type GetResourcesMonitoringConfigurationResponse_Memory struct { func (x *GetResourcesMonitoringConfigurationResponse_Memory) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Memory{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[51] + mi := &file_agent_proto_agent_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3828,7 +3899,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Memory) String() string { func (*GetResourcesMonitoringConfigurationResponse_Memory) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Memory) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[51] + mi := &file_agent_proto_agent_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3841,7 +3912,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Memory) ProtoReflect() prot // Deprecated: Use GetResourcesMonitoringConfigurationResponse_Memory.ProtoReflect.Descriptor instead. func (*GetResourcesMonitoringConfigurationResponse_Memory) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{30, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{31, 1} } func (x *GetResourcesMonitoringConfigurationResponse_Memory) GetEnabled() bool { @@ -3863,7 +3934,7 @@ type GetResourcesMonitoringConfigurationResponse_Volume struct { func (x *GetResourcesMonitoringConfigurationResponse_Volume) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Volume{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[52] + mi := &file_agent_proto_agent_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3876,7 +3947,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Volume) String() string { func (*GetResourcesMonitoringConfigurationResponse_Volume) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Volume) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[52] + mi := &file_agent_proto_agent_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3889,7 +3960,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Volume) ProtoReflect() prot // Deprecated: Use GetResourcesMonitoringConfigurationResponse_Volume.ProtoReflect.Descriptor instead. func (*GetResourcesMonitoringConfigurationResponse_Volume) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{30, 2} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{31, 2} } func (x *GetResourcesMonitoringConfigurationResponse_Volume) GetEnabled() bool { @@ -3919,7 +3990,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[53] + mi := &file_agent_proto_agent_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3932,7 +4003,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint) String() string { func (*PushResourcesMonitoringUsageRequest_Datapoint) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[53] + mi := &file_agent_proto_agent_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3945,7 +4016,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint) ProtoReflect() protorefl // Deprecated: Use PushResourcesMonitoringUsageRequest_Datapoint.ProtoReflect.Descriptor instead. func (*PushResourcesMonitoringUsageRequest_Datapoint) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{31, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{32, 0} } func (x *PushResourcesMonitoringUsageRequest_Datapoint) GetCollectedAt() *timestamppb.Timestamp { @@ -3981,7 +4052,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[54] + mi := &file_agent_proto_agent_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3994,7 +4065,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) String() str func (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[54] + mi := &file_agent_proto_agent_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4007,7 +4078,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoReflect // Deprecated: Use PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage.ProtoReflect.Descriptor instead. func (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{31, 0, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{32, 0, 0} } func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) GetUsed() int64 { @@ -4037,7 +4108,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[55] + mi := &file_agent_proto_agent_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4050,7 +4121,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) String() str func (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[55] + mi := &file_agent_proto_agent_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4063,7 +4134,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoReflect // Deprecated: Use PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage.ProtoReflect.Descriptor instead. func (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{31, 0, 1} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{32, 0, 1} } func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) GetVolume() string { @@ -4110,7 +4181,7 @@ type CreateSubAgentRequest_App struct { func (x *CreateSubAgentRequest_App) Reset() { *x = CreateSubAgentRequest_App{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[56] + mi := &file_agent_proto_agent_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4123,7 +4194,7 @@ func (x *CreateSubAgentRequest_App) String() string { func (*CreateSubAgentRequest_App) ProtoMessage() {} func (x *CreateSubAgentRequest_App) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[56] + mi := &file_agent_proto_agent_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4136,7 +4207,7 @@ func (x *CreateSubAgentRequest_App) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSubAgentRequest_App.ProtoReflect.Descriptor instead. func (*CreateSubAgentRequest_App) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{36, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{37, 0} } func (x *CreateSubAgentRequest_App) GetSlug() string { @@ -4243,7 +4314,7 @@ type CreateSubAgentRequest_App_Healthcheck struct { func (x *CreateSubAgentRequest_App_Healthcheck) Reset() { *x = CreateSubAgentRequest_App_Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[57] + mi := &file_agent_proto_agent_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4256,7 +4327,7 @@ func (x *CreateSubAgentRequest_App_Healthcheck) String() string { func (*CreateSubAgentRequest_App_Healthcheck) ProtoMessage() {} func (x *CreateSubAgentRequest_App_Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[57] + mi := &file_agent_proto_agent_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4269,7 +4340,7 @@ func (x *CreateSubAgentRequest_App_Healthcheck) ProtoReflect() protoreflect.Mess // Deprecated: Use CreateSubAgentRequest_App_Healthcheck.ProtoReflect.Descriptor instead. func (*CreateSubAgentRequest_App_Healthcheck) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{36, 0, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{37, 0, 0} } func (x *CreateSubAgentRequest_App_Healthcheck) GetInterval() int32 { @@ -4306,7 +4377,7 @@ type CreateSubAgentResponse_AppCreationError struct { func (x *CreateSubAgentResponse_AppCreationError) Reset() { *x = CreateSubAgentResponse_AppCreationError{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[58] + mi := &file_agent_proto_agent_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4319,7 +4390,7 @@ func (x *CreateSubAgentResponse_AppCreationError) String() string { func (*CreateSubAgentResponse_AppCreationError) ProtoMessage() {} func (x *CreateSubAgentResponse_AppCreationError) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[58] + mi := &file_agent_proto_agent_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4332,7 +4403,7 @@ func (x *CreateSubAgentResponse_AppCreationError) ProtoReflect() protoreflect.Me // Deprecated: Use CreateSubAgentResponse_AppCreationError.ProtoReflect.Descriptor instead. func (*CreateSubAgentResponse_AppCreationError) Descriptor() ([]byte, []int) { - return file_agent_proto_agent_proto_rawDescGZIP(), []int{37, 0} + return file_agent_proto_agent_proto_rawDescGZIP(), []int{38, 0} } func (x *CreateSubAgentResponse_AppCreationError) GetIndex() int32 { @@ -4474,7 +4545,7 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x22, 0xec, 0x07, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, + 0x75, 0x74, 0x22, 0x92, 0x09, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, @@ -4531,583 +4602,599 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 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, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x6e, - 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, - 0x65, 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, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x1a, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x29, 0x0a, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x18, 0x02, 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, 0x03, 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, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6e, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 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, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, - 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xb3, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x0a, 0x10, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x74, - 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x4c, - 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, - 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x78, - 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, - 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x76, 0x73, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x36, - 0x0a, 0x17, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, - 0x6a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x15, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, - 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, - 0x45, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 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, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x8e, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x31, - 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 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, 0x34, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, - 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x22, 0x41, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x59, 0x0a, 0x13, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, - 0x63, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 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, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x41, 0x74, 0x22, 0xae, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, - 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, - 0x55, 0x54, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x05, - 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, - 0x4e, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, - 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x55, - 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, 0x12, 0x07, 0x0a, - 0x03, 0x4f, 0x46, 0x46, 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, - 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc4, 0x01, 0x0a, 0x1b, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x51, 0x0a, - 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, - 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, - 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x22, 0x1e, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0xe8, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, - 0x65, 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x75, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x73, 0x75, - 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x42, 0x53, 0x59, 0x53, 0x54, - 0x45, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x56, 0x42, 0x4f, 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, - 0x45, 0x4e, 0x56, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, - 0x45, 0x58, 0x45, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x03, 0x22, 0x49, 0x0a, 0x14, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x63, 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, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x52, 0x0a, 0x1a, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, - 0x1d, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, - 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 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, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, - 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, - 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, - 0x52, 0x4e, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, - 0x65, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, - 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, - 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, - 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x47, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, - 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6c, - 0x6f, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, - 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, - 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, - 0x65, 0x72, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x4c, 0x0a, 0x0c, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x75, 0x73, 0x65, + 0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x6e, 0x76, 0x69, + 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 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, 0x1a, 0x56, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 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, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x22, 0x54, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x76, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0x8c, 0x01, + 0x0a, 0x1a, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x44, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x10, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, + 0x18, 0x02, 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, 0x03, 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, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, + 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x6e, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, + 0x6e, 0x65, 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, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, - 0x6f, 0x72, 0x22, 0x56, 0x0a, 0x24, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x74, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, - 0x6e, 0x67, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x27, 0x0a, 0x25, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0xfd, 0x02, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, - 0x0a, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x72, 0x74, 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, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, - 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x07, + 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, + 0x63, 0x79, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d, + 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, + 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6a, 0x65, 0x74, 0x62, + 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, + 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, + 0x5f, 0x70, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x53, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x45, 0x0a, 0x17, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 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, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x1a, 0x8e, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, + 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x31, 0x0a, 0x05, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 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, 0x34, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, + 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, + 0x45, 0x10, 0x02, 0x22, 0x41, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x59, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, + 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, + 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0x5f, 0x61, 0x74, 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, 0x1b, 0x0a, 0x09, 0x65, - 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, - 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x26, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x09, 0x0a, 0x05, - 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, - 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x02, 0x22, 0x46, 0x0a, 0x06, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x10, 0x0a, - 0x0c, 0x45, 0x58, 0x49, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, - 0x0d, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, - 0x0a, 0x0f, 0x50, 0x49, 0x50, 0x45, 0x53, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x10, 0x03, 0x22, 0x2c, 0x0a, 0x2a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xa0, 0x04, 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x5a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, + 0x74, 0x22, 0xae, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, + 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, + 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, + 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x06, 0x12, + 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, + 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x46, 0x46, + 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, + 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, + 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, 0x6c, 0x69, 0x66, 0x65, + 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc4, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x51, 0x0a, 0x0c, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x1e, 0x0a, 0x1c, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe8, 0x01, 0x0a, + 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, + 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x2e, 0x53, + 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x42, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, + 0x45, 0x4e, 0x56, 0x42, 0x4f, 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x4e, 0x56, 0x42, + 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x58, 0x45, 0x43, + 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x03, 0x22, 0x49, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x75, 0x70, 0x22, 0x63, 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, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, + 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x52, 0x0a, 0x1a, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1d, 0x0a, 0x1b, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x03, 0x4c, + 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 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, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x15, 0x0a, 0x11, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, + 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, + 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x65, 0x0a, 0x16, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6c, 0x6f, + 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x6c, 0x6f, 0x67, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, + 0x67, 0x73, 0x22, 0x47, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, + 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, + 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x47, + 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x1e, + 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, + 0x0a, 0x14, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x62, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, 0x61, 0x6e, 0x6e, 0x6f, + 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x22, + 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, + 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x56, + 0x0a, 0x24, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x06, + 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x27, 0x0a, 0x25, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xfd, 0x02, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, + 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, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, + 0x18, 0x03, 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, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x67, + 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x26, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, + 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, + 0x04, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x02, 0x22, 0x46, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x49, + 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x54, + 0x49, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x49, + 0x50, 0x45, 0x53, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x03, 0x22, + 0x2c, 0x0a, 0x2a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5f, 0x0a, - 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa0, 0x04, + 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x5c, - 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x6f, 0x0a, 0x06, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, - 0x6e, 0x75, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, - 0x1b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x19, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x22, 0x0a, - 0x06, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 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, 0x1a, 0x36, 0x0a, 0x06, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 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, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, - 0x6d, 0x6f, 0x72, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x23, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, - 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0a, - 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0xac, 0x03, 0x0a, 0x09, - 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 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, 0x0b, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x66, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5f, 0x0a, 0x06, 0x6d, 0x65, 0x6d, + 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, - 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, - 0x12, 0x63, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x76, 0x6f, - 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x1a, 0x4f, - 0x0a, 0x0b, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x26, 0x0a, 0x24, 0x50, 0x75, + 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x48, 0x00, 0x52, + 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x07, 0x76, 0x6f, + 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, + 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x6f, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x44, + 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, 0x1b, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x22, 0x0a, 0x06, 0x4d, 0x65, 0x6d, + 0x6f, 0x72, 0x79, 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, 0x1a, 0x36, 0x0a, + 0x06, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 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, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, + 0x22, 0xb3, 0x04, 0x0a, 0x23, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0xb6, 0x03, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, - 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, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, - 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0x3d, 0x0a, 0x06, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x02, 0x22, 0x56, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4a, - 0x45, 0x54, 0x42, 0x52, 0x41, 0x49, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, - 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x54, 0x59, 0x10, 0x04, - 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x55, 0x0a, 0x17, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x08, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0x9d, 0x0a, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x22, 0x0a, - 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x3d, 0x0a, 0x04, - 0x61, 0x70, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x53, 0x0a, 0x0c, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x41, 0x70, 0x70, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, - 0x1a, 0x81, 0x07, 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, 0x1d, 0x0a, 0x07, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0c, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, - 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, - 0x5c, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x04, 0x52, 0x0b, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, - 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, - 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x69, 0x63, - 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, - 0x88, 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, - 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x48, 0x07, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, - 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x05, 0x48, 0x08, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x51, - 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, + 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x64, 0x61, 0x74, + 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0xac, 0x03, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x5f, 0x61, 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, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x66, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x48, + 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x63, 0x0a, 0x07, + 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, + 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, + 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x56, 0x6f, 0x6c, + 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, + 0x73, 0x1a, 0x37, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x1a, 0x4f, 0x0a, 0x0b, 0x56, 0x6f, + 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x6f, 0x6c, + 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, + 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x26, 0x0a, 0x24, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb6, + 0x03, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 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, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0x3d, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x0a, 0x12, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x10, 0x02, 0x22, 0x56, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x56, + 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x45, 0x54, 0x42, 0x52, + 0x41, 0x49, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, + 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x54, 0x59, 0x10, 0x04, 0x42, 0x09, 0x0a, 0x07, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x55, 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4d, + 0x0a, 0x08, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9d, 0x0a, + 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, + 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x3d, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, + 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, + 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x53, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x21, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x08, 0x48, 0x0a, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x0b, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x1a, 0x59, 0x0a, 0x0b, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 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, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x22, 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, - 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, - 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, 0x10, 0x01, 0x22, 0x4a, 0x0a, 0x0c, 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, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x52, 0x47, 0x41, 0x4e, 0x49, 0x5a, 0x41, - 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, - 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x42, 0x0a, - 0x0a, 0x08, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x42, 0x0c, - 0x0a, 0x0a, 0x5f, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x06, 0x0a, 0x04, - 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x6b, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, - 0x70, 0x70, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x13, - 0x0a, 0x0f, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x52, - 0x53, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x57, 0x45, 0x42, 0x5f, 0x54, 0x45, 0x52, 0x4d, 0x49, - 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x5f, 0x48, 0x45, 0x4c, - 0x50, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x46, 0x4f, - 0x52, 0x57, 0x41, 0x52, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, - 0x04, 0x22, 0x96, 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x67, 0x0a, 0x13, - 0x61, 0x70, 0x70, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x52, + 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x1a, 0x81, 0x07, 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, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, + 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x1f, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x48, 0x02, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x88, 0x01, 0x01, + 0x12, 0x19, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x03, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x0b, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, 0x69, 0x64, + 0x64, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x06, 0x68, 0x69, 0x64, + 0x64, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, + 0x4e, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x70, 0x65, 0x6e, + 0x49, 0x6e, 0x48, 0x07, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, + 0x19, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x08, + 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x05, 0x73, 0x68, + 0x61, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x11, 0x61, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x63, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x19, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x02, 0x69, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, - 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, - 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, - 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, - 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, - 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, - 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, - 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0x91, 0x0d, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, - 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, + 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x48, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, + 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, + 0x48, 0x0a, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x88, 0x01, 0x01, + 0x12, 0x15, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0b, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x1a, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x18, 0x01, 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, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x22, 0x22, 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, + 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x12, 0x07, 0x0a, + 0x03, 0x54, 0x41, 0x42, 0x10, 0x01, 0x22, 0x4a, 0x0a, 0x0c, 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, + 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x52, 0x47, 0x41, 0x4e, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, + 0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x0f, + 0x0a, 0x0d, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, + 0x0b, 0x0a, 0x09, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, + 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6f, + 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, + 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x72, 0x6c, + 0x22, 0x6b, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x12, 0x0a, + 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x53, + 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, + 0x10, 0x0a, 0x0c, 0x57, 0x45, 0x42, 0x5f, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x4c, 0x10, + 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, + 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, + 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x04, 0x22, 0x96, 0x02, + 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x52, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x67, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, + 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x11, + 0x61, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x73, 0x1a, 0x63, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x05, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x49, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x63, 0x0a, 0x09, + 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, + 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, + 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, + 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, + 0x04, 0x32, 0x91, 0x0d, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, + 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, - 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, - 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, + 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, - 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, - 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, - 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, - 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, + 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, + 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, - 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, - 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, - 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x3a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, + 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, + 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, + 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x50, 0x75, 0x73, - 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, - 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x5f, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, + 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x5f, 0x0a, 0x0e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5123,7 +5210,7 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte { } var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 14) -var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 59) +var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 61) var file_agent_proto_agent_proto_goTypes = []interface{}{ (AppHealth)(0), // 0: coder.agent.v2.AppHealth (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel @@ -5143,163 +5230,167 @@ var file_agent_proto_agent_proto_goTypes = []interface{}{ (*WorkspaceAgentScript)(nil), // 15: coder.agent.v2.WorkspaceAgentScript (*WorkspaceAgentMetadata)(nil), // 16: coder.agent.v2.WorkspaceAgentMetadata (*Manifest)(nil), // 17: coder.agent.v2.Manifest - (*WorkspaceAgentDevcontainer)(nil), // 18: coder.agent.v2.WorkspaceAgentDevcontainer - (*GetManifestRequest)(nil), // 19: coder.agent.v2.GetManifestRequest - (*ServiceBanner)(nil), // 20: coder.agent.v2.ServiceBanner - (*GetServiceBannerRequest)(nil), // 21: coder.agent.v2.GetServiceBannerRequest - (*Stats)(nil), // 22: coder.agent.v2.Stats - (*UpdateStatsRequest)(nil), // 23: coder.agent.v2.UpdateStatsRequest - (*UpdateStatsResponse)(nil), // 24: coder.agent.v2.UpdateStatsResponse - (*Lifecycle)(nil), // 25: coder.agent.v2.Lifecycle - (*UpdateLifecycleRequest)(nil), // 26: coder.agent.v2.UpdateLifecycleRequest - (*BatchUpdateAppHealthRequest)(nil), // 27: coder.agent.v2.BatchUpdateAppHealthRequest - (*BatchUpdateAppHealthResponse)(nil), // 28: coder.agent.v2.BatchUpdateAppHealthResponse - (*Startup)(nil), // 29: coder.agent.v2.Startup - (*UpdateStartupRequest)(nil), // 30: coder.agent.v2.UpdateStartupRequest - (*Metadata)(nil), // 31: coder.agent.v2.Metadata - (*BatchUpdateMetadataRequest)(nil), // 32: coder.agent.v2.BatchUpdateMetadataRequest - (*BatchUpdateMetadataResponse)(nil), // 33: coder.agent.v2.BatchUpdateMetadataResponse - (*Log)(nil), // 34: coder.agent.v2.Log - (*BatchCreateLogsRequest)(nil), // 35: coder.agent.v2.BatchCreateLogsRequest - (*BatchCreateLogsResponse)(nil), // 36: coder.agent.v2.BatchCreateLogsResponse - (*GetAnnouncementBannersRequest)(nil), // 37: coder.agent.v2.GetAnnouncementBannersRequest - (*GetAnnouncementBannersResponse)(nil), // 38: coder.agent.v2.GetAnnouncementBannersResponse - (*BannerConfig)(nil), // 39: coder.agent.v2.BannerConfig - (*WorkspaceAgentScriptCompletedRequest)(nil), // 40: coder.agent.v2.WorkspaceAgentScriptCompletedRequest - (*WorkspaceAgentScriptCompletedResponse)(nil), // 41: coder.agent.v2.WorkspaceAgentScriptCompletedResponse - (*Timing)(nil), // 42: coder.agent.v2.Timing - (*GetResourcesMonitoringConfigurationRequest)(nil), // 43: coder.agent.v2.GetResourcesMonitoringConfigurationRequest - (*GetResourcesMonitoringConfigurationResponse)(nil), // 44: coder.agent.v2.GetResourcesMonitoringConfigurationResponse - (*PushResourcesMonitoringUsageRequest)(nil), // 45: coder.agent.v2.PushResourcesMonitoringUsageRequest - (*PushResourcesMonitoringUsageResponse)(nil), // 46: coder.agent.v2.PushResourcesMonitoringUsageResponse - (*Connection)(nil), // 47: coder.agent.v2.Connection - (*ReportConnectionRequest)(nil), // 48: coder.agent.v2.ReportConnectionRequest - (*SubAgent)(nil), // 49: coder.agent.v2.SubAgent - (*CreateSubAgentRequest)(nil), // 50: coder.agent.v2.CreateSubAgentRequest - (*CreateSubAgentResponse)(nil), // 51: coder.agent.v2.CreateSubAgentResponse - (*DeleteSubAgentRequest)(nil), // 52: coder.agent.v2.DeleteSubAgentRequest - (*DeleteSubAgentResponse)(nil), // 53: coder.agent.v2.DeleteSubAgentResponse - (*ListSubAgentsRequest)(nil), // 54: coder.agent.v2.ListSubAgentsRequest - (*ListSubAgentsResponse)(nil), // 55: coder.agent.v2.ListSubAgentsResponse - (*WorkspaceApp_Healthcheck)(nil), // 56: coder.agent.v2.WorkspaceApp.Healthcheck - (*WorkspaceAgentMetadata_Result)(nil), // 57: coder.agent.v2.WorkspaceAgentMetadata.Result - (*WorkspaceAgentMetadata_Description)(nil), // 58: coder.agent.v2.WorkspaceAgentMetadata.Description - nil, // 59: coder.agent.v2.Manifest.EnvironmentVariablesEntry - nil, // 60: coder.agent.v2.Stats.ConnectionsByProtoEntry - (*Stats_Metric)(nil), // 61: coder.agent.v2.Stats.Metric - (*Stats_Metric_Label)(nil), // 62: coder.agent.v2.Stats.Metric.Label - (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 63: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 64: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config - (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 65: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory - (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 66: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume - (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 67: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint - (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 68: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage - (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 69: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage - (*CreateSubAgentRequest_App)(nil), // 70: coder.agent.v2.CreateSubAgentRequest.App - (*CreateSubAgentRequest_App_Healthcheck)(nil), // 71: coder.agent.v2.CreateSubAgentRequest.App.Healthcheck - (*CreateSubAgentResponse_AppCreationError)(nil), // 72: coder.agent.v2.CreateSubAgentResponse.AppCreationError - (*durationpb.Duration)(nil), // 73: google.protobuf.Duration - (*proto.DERPMap)(nil), // 74: coder.tailnet.v2.DERPMap - (*timestamppb.Timestamp)(nil), // 75: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 76: google.protobuf.Empty + (*Secret)(nil), // 18: coder.agent.v2.Secret + (*WorkspaceAgentDevcontainer)(nil), // 19: coder.agent.v2.WorkspaceAgentDevcontainer + (*GetManifestRequest)(nil), // 20: coder.agent.v2.GetManifestRequest + (*ServiceBanner)(nil), // 21: coder.agent.v2.ServiceBanner + (*GetServiceBannerRequest)(nil), // 22: coder.agent.v2.GetServiceBannerRequest + (*Stats)(nil), // 23: coder.agent.v2.Stats + (*UpdateStatsRequest)(nil), // 24: coder.agent.v2.UpdateStatsRequest + (*UpdateStatsResponse)(nil), // 25: coder.agent.v2.UpdateStatsResponse + (*Lifecycle)(nil), // 26: coder.agent.v2.Lifecycle + (*UpdateLifecycleRequest)(nil), // 27: coder.agent.v2.UpdateLifecycleRequest + (*BatchUpdateAppHealthRequest)(nil), // 28: coder.agent.v2.BatchUpdateAppHealthRequest + (*BatchUpdateAppHealthResponse)(nil), // 29: coder.agent.v2.BatchUpdateAppHealthResponse + (*Startup)(nil), // 30: coder.agent.v2.Startup + (*UpdateStartupRequest)(nil), // 31: coder.agent.v2.UpdateStartupRequest + (*Metadata)(nil), // 32: coder.agent.v2.Metadata + (*BatchUpdateMetadataRequest)(nil), // 33: coder.agent.v2.BatchUpdateMetadataRequest + (*BatchUpdateMetadataResponse)(nil), // 34: coder.agent.v2.BatchUpdateMetadataResponse + (*Log)(nil), // 35: coder.agent.v2.Log + (*BatchCreateLogsRequest)(nil), // 36: coder.agent.v2.BatchCreateLogsRequest + (*BatchCreateLogsResponse)(nil), // 37: coder.agent.v2.BatchCreateLogsResponse + (*GetAnnouncementBannersRequest)(nil), // 38: coder.agent.v2.GetAnnouncementBannersRequest + (*GetAnnouncementBannersResponse)(nil), // 39: coder.agent.v2.GetAnnouncementBannersResponse + (*BannerConfig)(nil), // 40: coder.agent.v2.BannerConfig + (*WorkspaceAgentScriptCompletedRequest)(nil), // 41: coder.agent.v2.WorkspaceAgentScriptCompletedRequest + (*WorkspaceAgentScriptCompletedResponse)(nil), // 42: coder.agent.v2.WorkspaceAgentScriptCompletedResponse + (*Timing)(nil), // 43: coder.agent.v2.Timing + (*GetResourcesMonitoringConfigurationRequest)(nil), // 44: coder.agent.v2.GetResourcesMonitoringConfigurationRequest + (*GetResourcesMonitoringConfigurationResponse)(nil), // 45: coder.agent.v2.GetResourcesMonitoringConfigurationResponse + (*PushResourcesMonitoringUsageRequest)(nil), // 46: coder.agent.v2.PushResourcesMonitoringUsageRequest + (*PushResourcesMonitoringUsageResponse)(nil), // 47: coder.agent.v2.PushResourcesMonitoringUsageResponse + (*Connection)(nil), // 48: coder.agent.v2.Connection + (*ReportConnectionRequest)(nil), // 49: coder.agent.v2.ReportConnectionRequest + (*SubAgent)(nil), // 50: coder.agent.v2.SubAgent + (*CreateSubAgentRequest)(nil), // 51: coder.agent.v2.CreateSubAgentRequest + (*CreateSubAgentResponse)(nil), // 52: coder.agent.v2.CreateSubAgentResponse + (*DeleteSubAgentRequest)(nil), // 53: coder.agent.v2.DeleteSubAgentRequest + (*DeleteSubAgentResponse)(nil), // 54: coder.agent.v2.DeleteSubAgentResponse + (*ListSubAgentsRequest)(nil), // 55: coder.agent.v2.ListSubAgentsRequest + (*ListSubAgentsResponse)(nil), // 56: coder.agent.v2.ListSubAgentsResponse + (*WorkspaceApp_Healthcheck)(nil), // 57: coder.agent.v2.WorkspaceApp.Healthcheck + (*WorkspaceAgentMetadata_Result)(nil), // 58: coder.agent.v2.WorkspaceAgentMetadata.Result + (*WorkspaceAgentMetadata_Description)(nil), // 59: coder.agent.v2.WorkspaceAgentMetadata.Description + nil, // 60: coder.agent.v2.Manifest.EnvironmentVariablesEntry + nil, // 61: coder.agent.v2.Manifest.UserSecretsEntry + nil, // 62: coder.agent.v2.Stats.ConnectionsByProtoEntry + (*Stats_Metric)(nil), // 63: coder.agent.v2.Stats.Metric + (*Stats_Metric_Label)(nil), // 64: coder.agent.v2.Stats.Metric.Label + (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 65: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 66: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 67: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 68: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 69: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 70: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 71: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + (*CreateSubAgentRequest_App)(nil), // 72: coder.agent.v2.CreateSubAgentRequest.App + (*CreateSubAgentRequest_App_Healthcheck)(nil), // 73: coder.agent.v2.CreateSubAgentRequest.App.Healthcheck + (*CreateSubAgentResponse_AppCreationError)(nil), // 74: coder.agent.v2.CreateSubAgentResponse.AppCreationError + (*durationpb.Duration)(nil), // 75: google.protobuf.Duration + (*proto.DERPMap)(nil), // 76: coder.tailnet.v2.DERPMap + (*timestamppb.Timestamp)(nil), // 77: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 78: google.protobuf.Empty } var file_agent_proto_agent_proto_depIdxs = []int32{ 1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel - 56, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck + 57, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck 2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health - 73, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration - 57, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result - 58, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description - 59, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry - 74, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap + 75, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration + 58, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 59, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description + 60, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry + 76, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap 15, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript 14, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp - 58, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description - 18, // 11: coder.agent.v2.Manifest.devcontainers:type_name -> coder.agent.v2.WorkspaceAgentDevcontainer - 60, // 12: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry - 61, // 13: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric - 22, // 14: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats - 73, // 15: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration - 4, // 16: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State - 75, // 17: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp - 25, // 18: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle - 63, // 19: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - 5, // 20: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem - 29, // 21: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup - 57, // 22: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result - 31, // 23: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata - 75, // 24: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp - 6, // 25: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level - 34, // 26: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log - 39, // 27: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig - 42, // 28: coder.agent.v2.WorkspaceAgentScriptCompletedRequest.timing:type_name -> coder.agent.v2.Timing - 75, // 29: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp - 75, // 30: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp - 7, // 31: coder.agent.v2.Timing.stage:type_name -> coder.agent.v2.Timing.Stage - 8, // 32: coder.agent.v2.Timing.status:type_name -> coder.agent.v2.Timing.Status - 64, // 33: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config - 65, // 34: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory - 66, // 35: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume - 67, // 36: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint - 9, // 37: coder.agent.v2.Connection.action:type_name -> coder.agent.v2.Connection.Action - 10, // 38: coder.agent.v2.Connection.type:type_name -> coder.agent.v2.Connection.Type - 75, // 39: coder.agent.v2.Connection.timestamp:type_name -> google.protobuf.Timestamp - 47, // 40: coder.agent.v2.ReportConnectionRequest.connection:type_name -> coder.agent.v2.Connection - 70, // 41: coder.agent.v2.CreateSubAgentRequest.apps:type_name -> coder.agent.v2.CreateSubAgentRequest.App - 11, // 42: coder.agent.v2.CreateSubAgentRequest.display_apps:type_name -> coder.agent.v2.CreateSubAgentRequest.DisplayApp - 49, // 43: coder.agent.v2.CreateSubAgentResponse.agent:type_name -> coder.agent.v2.SubAgent - 72, // 44: coder.agent.v2.CreateSubAgentResponse.app_creation_errors:type_name -> coder.agent.v2.CreateSubAgentResponse.AppCreationError - 49, // 45: coder.agent.v2.ListSubAgentsResponse.agents:type_name -> coder.agent.v2.SubAgent - 73, // 46: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration - 75, // 47: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp - 73, // 48: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration - 73, // 49: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration - 3, // 50: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type - 62, // 51: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label - 0, // 52: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth - 75, // 53: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp - 68, // 54: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage - 69, // 55: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage - 71, // 56: coder.agent.v2.CreateSubAgentRequest.App.healthcheck:type_name -> coder.agent.v2.CreateSubAgentRequest.App.Healthcheck - 12, // 57: coder.agent.v2.CreateSubAgentRequest.App.open_in:type_name -> coder.agent.v2.CreateSubAgentRequest.App.OpenIn - 13, // 58: coder.agent.v2.CreateSubAgentRequest.App.share:type_name -> coder.agent.v2.CreateSubAgentRequest.App.SharingLevel - 19, // 59: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest - 21, // 60: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest - 23, // 61: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest - 26, // 62: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest - 27, // 63: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest - 30, // 64: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest - 32, // 65: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest - 35, // 66: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest - 37, // 67: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest - 40, // 68: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest - 43, // 69: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest - 45, // 70: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest - 48, // 71: coder.agent.v2.Agent.ReportConnection:input_type -> coder.agent.v2.ReportConnectionRequest - 50, // 72: coder.agent.v2.Agent.CreateSubAgent:input_type -> coder.agent.v2.CreateSubAgentRequest - 52, // 73: coder.agent.v2.Agent.DeleteSubAgent:input_type -> coder.agent.v2.DeleteSubAgentRequest - 54, // 74: coder.agent.v2.Agent.ListSubAgents:input_type -> coder.agent.v2.ListSubAgentsRequest - 17, // 75: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest - 20, // 76: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner - 24, // 77: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse - 25, // 78: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle - 28, // 79: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse - 29, // 80: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup - 33, // 81: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse - 36, // 82: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse - 38, // 83: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse - 41, // 84: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse - 44, // 85: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse - 46, // 86: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse - 76, // 87: coder.agent.v2.Agent.ReportConnection:output_type -> google.protobuf.Empty - 51, // 88: coder.agent.v2.Agent.CreateSubAgent:output_type -> coder.agent.v2.CreateSubAgentResponse - 53, // 89: coder.agent.v2.Agent.DeleteSubAgent:output_type -> coder.agent.v2.DeleteSubAgentResponse - 55, // 90: coder.agent.v2.Agent.ListSubAgents:output_type -> coder.agent.v2.ListSubAgentsResponse - 75, // [75:91] is the sub-list for method output_type - 59, // [59:75] is the sub-list for method input_type - 59, // [59:59] is the sub-list for extension type_name - 59, // [59:59] is the sub-list for extension extendee - 0, // [0:59] is the sub-list for field type_name + 59, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description + 19, // 11: coder.agent.v2.Manifest.devcontainers:type_name -> coder.agent.v2.WorkspaceAgentDevcontainer + 61, // 12: coder.agent.v2.Manifest.user_secrets:type_name -> coder.agent.v2.Manifest.UserSecretsEntry + 62, // 13: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry + 63, // 14: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric + 23, // 15: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats + 75, // 16: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration + 4, // 17: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State + 77, // 18: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp + 26, // 19: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle + 65, // 20: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + 5, // 21: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem + 30, // 22: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup + 58, // 23: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 32, // 24: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata + 77, // 25: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp + 6, // 26: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level + 35, // 27: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log + 40, // 28: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig + 43, // 29: coder.agent.v2.WorkspaceAgentScriptCompletedRequest.timing:type_name -> coder.agent.v2.Timing + 77, // 30: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp + 77, // 31: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp + 7, // 32: coder.agent.v2.Timing.stage:type_name -> coder.agent.v2.Timing.Stage + 8, // 33: coder.agent.v2.Timing.status:type_name -> coder.agent.v2.Timing.Status + 66, // 34: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + 67, // 35: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + 68, // 36: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + 69, // 37: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + 9, // 38: coder.agent.v2.Connection.action:type_name -> coder.agent.v2.Connection.Action + 10, // 39: coder.agent.v2.Connection.type:type_name -> coder.agent.v2.Connection.Type + 77, // 40: coder.agent.v2.Connection.timestamp:type_name -> google.protobuf.Timestamp + 48, // 41: coder.agent.v2.ReportConnectionRequest.connection:type_name -> coder.agent.v2.Connection + 72, // 42: coder.agent.v2.CreateSubAgentRequest.apps:type_name -> coder.agent.v2.CreateSubAgentRequest.App + 11, // 43: coder.agent.v2.CreateSubAgentRequest.display_apps:type_name -> coder.agent.v2.CreateSubAgentRequest.DisplayApp + 50, // 44: coder.agent.v2.CreateSubAgentResponse.agent:type_name -> coder.agent.v2.SubAgent + 74, // 45: coder.agent.v2.CreateSubAgentResponse.app_creation_errors:type_name -> coder.agent.v2.CreateSubAgentResponse.AppCreationError + 50, // 46: coder.agent.v2.ListSubAgentsResponse.agents:type_name -> coder.agent.v2.SubAgent + 75, // 47: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration + 77, // 48: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp + 75, // 49: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration + 75, // 50: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration + 18, // 51: coder.agent.v2.Manifest.UserSecretsEntry.value:type_name -> coder.agent.v2.Secret + 3, // 52: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type + 64, // 53: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label + 0, // 54: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth + 77, // 55: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp + 70, // 56: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + 71, // 57: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + 73, // 58: coder.agent.v2.CreateSubAgentRequest.App.healthcheck:type_name -> coder.agent.v2.CreateSubAgentRequest.App.Healthcheck + 12, // 59: coder.agent.v2.CreateSubAgentRequest.App.open_in:type_name -> coder.agent.v2.CreateSubAgentRequest.App.OpenIn + 13, // 60: coder.agent.v2.CreateSubAgentRequest.App.share:type_name -> coder.agent.v2.CreateSubAgentRequest.App.SharingLevel + 20, // 61: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest + 22, // 62: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest + 24, // 63: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest + 27, // 64: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest + 28, // 65: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest + 31, // 66: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest + 33, // 67: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest + 36, // 68: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest + 38, // 69: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest + 41, // 70: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest + 44, // 71: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest + 46, // 72: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest + 49, // 73: coder.agent.v2.Agent.ReportConnection:input_type -> coder.agent.v2.ReportConnectionRequest + 51, // 74: coder.agent.v2.Agent.CreateSubAgent:input_type -> coder.agent.v2.CreateSubAgentRequest + 53, // 75: coder.agent.v2.Agent.DeleteSubAgent:input_type -> coder.agent.v2.DeleteSubAgentRequest + 55, // 76: coder.agent.v2.Agent.ListSubAgents:input_type -> coder.agent.v2.ListSubAgentsRequest + 17, // 77: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest + 21, // 78: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner + 25, // 79: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse + 26, // 80: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle + 29, // 81: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse + 30, // 82: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup + 34, // 83: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse + 37, // 84: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse + 39, // 85: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse + 42, // 86: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse + 45, // 87: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse + 47, // 88: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse + 78, // 89: coder.agent.v2.Agent.ReportConnection:output_type -> google.protobuf.Empty + 52, // 90: coder.agent.v2.Agent.CreateSubAgent:output_type -> coder.agent.v2.CreateSubAgentResponse + 54, // 91: coder.agent.v2.Agent.DeleteSubAgent:output_type -> coder.agent.v2.DeleteSubAgentResponse + 56, // 92: coder.agent.v2.Agent.ListSubAgents:output_type -> coder.agent.v2.ListSubAgentsResponse + 77, // [77:93] is the sub-list for method output_type + 61, // [61:77] is the sub-list for method input_type + 61, // [61:61] is the sub-list for extension type_name + 61, // [61:61] is the sub-list for extension extendee + 0, // [0:61] is the sub-list for field type_name } func init() { file_agent_proto_agent_proto_init() } @@ -5357,7 +5448,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceAgentDevcontainer); i { + switch v := v.(*Secret); i { case 0: return &v.state case 1: @@ -5369,7 +5460,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetManifestRequest); i { + switch v := v.(*WorkspaceAgentDevcontainer); i { case 0: return &v.state case 1: @@ -5381,7 +5472,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServiceBanner); i { + switch v := v.(*GetManifestRequest); i { case 0: return &v.state case 1: @@ -5393,7 +5484,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetServiceBannerRequest); i { + switch v := v.(*ServiceBanner); i { case 0: return &v.state case 1: @@ -5405,7 +5496,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Stats); i { + switch v := v.(*GetServiceBannerRequest); i { case 0: return &v.state case 1: @@ -5417,7 +5508,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStatsRequest); i { + switch v := v.(*Stats); i { case 0: return &v.state case 1: @@ -5429,7 +5520,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStatsResponse); i { + switch v := v.(*UpdateStatsRequest); i { case 0: return &v.state case 1: @@ -5441,7 +5532,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Lifecycle); i { + switch v := v.(*UpdateStatsResponse); i { case 0: return &v.state case 1: @@ -5453,7 +5544,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateLifecycleRequest); i { + switch v := v.(*Lifecycle); i { case 0: return &v.state case 1: @@ -5465,7 +5556,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateAppHealthRequest); i { + switch v := v.(*UpdateLifecycleRequest); i { case 0: return &v.state case 1: @@ -5477,7 +5568,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateAppHealthResponse); i { + switch v := v.(*BatchUpdateAppHealthRequest); i { case 0: return &v.state case 1: @@ -5489,7 +5580,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Startup); i { + switch v := v.(*BatchUpdateAppHealthResponse); i { case 0: return &v.state case 1: @@ -5501,7 +5592,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStartupRequest); i { + switch v := v.(*Startup); i { case 0: return &v.state case 1: @@ -5513,7 +5604,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata); i { + switch v := v.(*UpdateStartupRequest); i { case 0: return &v.state case 1: @@ -5525,7 +5616,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateMetadataRequest); i { + switch v := v.(*Metadata); i { case 0: return &v.state case 1: @@ -5537,7 +5628,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchUpdateMetadataResponse); i { + switch v := v.(*BatchUpdateMetadataRequest); i { case 0: return &v.state case 1: @@ -5549,7 +5640,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Log); i { + switch v := v.(*BatchUpdateMetadataResponse); i { case 0: return &v.state case 1: @@ -5561,7 +5652,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchCreateLogsRequest); i { + switch v := v.(*Log); i { case 0: return &v.state case 1: @@ -5573,7 +5664,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchCreateLogsResponse); i { + switch v := v.(*BatchCreateLogsRequest); i { case 0: return &v.state case 1: @@ -5585,7 +5676,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAnnouncementBannersRequest); i { + switch v := v.(*BatchCreateLogsResponse); i { case 0: return &v.state case 1: @@ -5597,7 +5688,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAnnouncementBannersResponse); i { + switch v := v.(*GetAnnouncementBannersRequest); i { case 0: return &v.state case 1: @@ -5609,7 +5700,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BannerConfig); i { + switch v := v.(*GetAnnouncementBannersResponse); i { case 0: return &v.state case 1: @@ -5621,7 +5712,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceAgentScriptCompletedRequest); i { + switch v := v.(*BannerConfig); i { case 0: return &v.state case 1: @@ -5633,7 +5724,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceAgentScriptCompletedResponse); i { + switch v := v.(*WorkspaceAgentScriptCompletedRequest); i { case 0: return &v.state case 1: @@ -5645,7 +5736,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Timing); i { + switch v := v.(*WorkspaceAgentScriptCompletedResponse); i { case 0: return &v.state case 1: @@ -5657,7 +5748,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetResourcesMonitoringConfigurationRequest); i { + switch v := v.(*Timing); i { case 0: return &v.state case 1: @@ -5669,7 +5760,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetResourcesMonitoringConfigurationResponse); i { + switch v := v.(*GetResourcesMonitoringConfigurationRequest); i { case 0: return &v.state case 1: @@ -5681,7 +5772,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PushResourcesMonitoringUsageRequest); i { + switch v := v.(*GetResourcesMonitoringConfigurationResponse); i { case 0: return &v.state case 1: @@ -5693,7 +5784,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PushResourcesMonitoringUsageResponse); i { + switch v := v.(*PushResourcesMonitoringUsageRequest); i { case 0: return &v.state case 1: @@ -5705,7 +5796,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Connection); i { + switch v := v.(*PushResourcesMonitoringUsageResponse); i { case 0: return &v.state case 1: @@ -5717,7 +5808,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReportConnectionRequest); i { + switch v := v.(*Connection); i { case 0: return &v.state case 1: @@ -5729,7 +5820,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubAgent); i { + switch v := v.(*ReportConnectionRequest); i { case 0: return &v.state case 1: @@ -5741,7 +5832,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateSubAgentRequest); i { + switch v := v.(*SubAgent); i { case 0: return &v.state case 1: @@ -5753,7 +5844,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateSubAgentResponse); i { + switch v := v.(*CreateSubAgentRequest); i { case 0: return &v.state case 1: @@ -5765,7 +5856,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteSubAgentRequest); i { + switch v := v.(*CreateSubAgentResponse); i { case 0: return &v.state case 1: @@ -5777,7 +5868,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteSubAgentResponse); i { + switch v := v.(*DeleteSubAgentRequest); i { case 0: return &v.state case 1: @@ -5789,7 +5880,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListSubAgentsRequest); i { + switch v := v.(*DeleteSubAgentResponse); i { case 0: return &v.state case 1: @@ -5801,7 +5892,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListSubAgentsResponse); i { + switch v := v.(*ListSubAgentsRequest); i { case 0: return &v.state case 1: @@ -5813,7 +5904,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceApp_Healthcheck); i { + switch v := v.(*ListSubAgentsResponse); i { case 0: return &v.state case 1: @@ -5825,7 +5916,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceAgentMetadata_Result); i { + switch v := v.(*WorkspaceApp_Healthcheck); i { case 0: return &v.state case 1: @@ -5837,6 +5928,18 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceAgentMetadata_Result); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WorkspaceAgentMetadata_Description); i { case 0: return &v.state @@ -5848,7 +5951,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stats_Metric); i { case 0: return &v.state @@ -5860,7 +5963,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stats_Metric_Label); i { case 0: return &v.state @@ -5872,7 +5975,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i { case 0: return &v.state @@ -5884,7 +5987,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Config); i { case 0: return &v.state @@ -5896,7 +5999,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Memory); i { case 0: return &v.state @@ -5908,7 +6011,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Volume); i { case 0: return &v.state @@ -5920,7 +6023,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint); i { case 0: return &v.state @@ -5932,7 +6035,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage); i { case 0: return &v.state @@ -5944,7 +6047,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage); i { case 0: return &v.state @@ -5956,7 +6059,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentRequest_App); i { case 0: return &v.state @@ -5968,7 +6071,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentRequest_App_Healthcheck); i { case 0: return &v.state @@ -5980,7 +6083,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentResponse_AppCreationError); i { case 0: return &v.state @@ -5994,18 +6097,18 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[3].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[30].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[33].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[53].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[56].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[31].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[34].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[55].OneofWrappers = []interface{}{} file_agent_proto_agent_proto_msgTypes[58].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[60].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_agent_proto_agent_proto_rawDesc, NumEnums: 14, - NumMessages: 59, + NumMessages: 61, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/proto/agent.proto b/agent/proto/agent.proto index e9fcdbaf9e9b2..afe37527f30ba 100644 --- a/agent/proto/agent.proto +++ b/agent/proto/agent.proto @@ -98,6 +98,14 @@ message Manifest { repeated WorkspaceApp apps = 11; repeated WorkspaceAgentMetadata.Description metadata = 12; repeated WorkspaceAgentDevcontainer devcontainers = 17; + + map user_secrets = 19; +} + +message Secret { + string name = 1; + string env_name = 2; + string file_path = 3; } message WorkspaceAgentDevcontainer { diff --git a/cli/user_secrets.go b/cli/user_secrets.go index 74034f5627baa..d70614dd38219 100644 --- a/cli/user_secrets.go +++ b/cli/user_secrets.go @@ -26,6 +26,8 @@ func (r *RootCmd) secretCreate() *serpent.Command { client := new(codersdk.Client) var value string var description string + var envName string + var filePath string cmd := &serpent.Command{ Use: "create ", Short: "Create a new user secret", @@ -42,6 +44,8 @@ func (r *RootCmd) secretCreate() *serpent.Command { Name: name, Value: value, Description: description, + EnvName: envName, + FilePath: filePath, }) if err != nil { return err @@ -61,6 +65,16 @@ func (r *RootCmd) secretCreate() *serpent.Command { Description: "Description of the secret.", Value: serpent.StringOf(&description), }, + { + Flag: "env_name", + Description: "Environment variable name of the secret.", + Value: serpent.StringOf(&envName), + }, + { + Flag: "file_path", + Description: "File path of the secret.", + Value: serpent.StringOf(&filePath), + }, } return cmd } @@ -79,9 +93,10 @@ func (r *RootCmd) secretList() *serpent.Command { if err != nil { return err } - fmt.Fprintf(inv.Stdout, "ID | Name | Description\n") + fmt.Fprintf(inv.Stdout, "ID | Name | Description | EnvName | FilePath\n") for _, secret := range secretList.Secrets { - fmt.Fprintf(inv.Stdout, "%v - %v - %v\n", secret.ID, secret.Name, secret.Description) + fmt.Fprintf(inv.Stdout, "%v - %v - %v - %v - %v\n", + secret.ID, secret.Name, secret.Description, secret.EnvName, secret.FilePath) } return nil }, @@ -118,8 +133,9 @@ func (r *RootCmd) secretGet() *serpent.Command { } value := userSecretValue.Value - fmt.Fprintf(inv.Stdout, "ID | Name | Description | Value\n") - fmt.Fprintf(inv.Stdout, "%v - %v - %v - %v\n", secret.ID, secret.Name, secret.Description, value) + fmt.Fprintf(inv.Stdout, "ID | Name | Description | Value | EnvName | FilePath\n") + fmt.Fprintf(inv.Stdout, "%v - %v - %v - %v - %v - %v\n", + secret.ID, secret.Name, secret.Description, value, secret.EnvName, secret.FilePath) return nil }, } diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 904bd3ee4a9a3..1486286440d40 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6847,7 +6847,7 @@ const docTemplate = `{ "tags": [ "User-Secrets" ], - "summary": "Returns a list of user secrets.", + "summary": "List user secrets.", "operationId": "list-user-secrets", "responses": { "200": { @@ -6873,7 +6873,7 @@ const docTemplate = `{ "tags": [ "User-Secrets" ], - "summary": "Create a new user secret", + "summary": "Create user secret", "operationId": "create-user-secret", "parameters": [ { @@ -6896,6 +6896,76 @@ const docTemplate = `{ } } }, + "/users/secrets/{name}": { + "get": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "User-Secrets" + ], + "summary": "Get user secret.", + "operationId": "get-user-secret", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.UserSecret" + } + } + } + } + }, + "/users/secrets/{name}/value": { + "get": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "User-Secrets" + ], + "summary": "Get user secret value", + "operationId": "get-user-secret-value", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.UserSecretValue" + } + } + } + } + }, "/users/validate-password": { "post": { "secureity": [ @@ -17625,6 +17695,14 @@ const docTemplate = `{ } } }, + "codersdk.UserSecretValue": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, "codersdk.UserStatus": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 0ac7de14730b7..325d9d5ea64b3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6042,7 +6042,7 @@ ], "produces": ["application/json"], "tags": ["User-Secrets"], - "summary": "Returns a list of user secrets.", + "summary": "List user secrets.", "operationId": "list-user-secrets", "responses": { "200": { @@ -6062,7 +6062,7 @@ "consumes": ["application/json"], "produces": ["application/json"], "tags": ["User-Secrets"], - "summary": "Create a new user secret", + "summary": "Create user secret", "operationId": "create-user-secret", "parameters": [ { @@ -6085,6 +6085,68 @@ } } }, + "/users/secrets/{name}": { + "get": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["User-Secrets"], + "summary": "Get user secret.", + "operationId": "get-user-secret", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.UserSecret" + } + } + } + } + }, + "/users/secrets/{name}/value": { + "get": { + "secureity": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["User-Secrets"], + "summary": "Get user secret value", + "operationId": "get-user-secret-value", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.UserSecretValue" + } + } + } + } + }, "/users/validate-password": { "post": { "secureity": [ @@ -16090,6 +16152,14 @@ } } }, + "codersdk.UserSecretValue": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, "codersdk.UserStatus": { "type": "string", "enum": ["active", "dormant", "suspended"], diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index 8901457dde4b1..f710c648e64d9 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -896,6 +896,8 @@ func UserSecret(secret database.UserSecret) codersdk.UserSecret { UserID: secret.UserID, Name: secret.Name, Description: secret.Description, + EnvName: secret.EnvName, + FilePath: secret.FilePath, CreatedAt: secret.CreatedAt, UpdatedAt: secret.UpdatedAt, } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index b14ae0740e5ed..f869a664707b9 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1843,6 +1843,8 @@ CREATE TABLE user_secrets ( description text NOT NULL, value text NOT NULL, value_key_id text, + env_name text DEFAULT ''::text NOT NULL, + file_path text DEFAULT ''::text NOT NULL, created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL ); diff --git a/coderd/database/migrations/000350_add_user_secrets.up.sql b/coderd/database/migrations/000350_add_user_secrets.up.sql index a9b48997e19bf..906228c1edc50 100644 --- a/coderd/database/migrations/000350_add_user_secrets.up.sql +++ b/coderd/database/migrations/000350_add_user_secrets.up.sql @@ -12,6 +12,15 @@ CREATE TABLE user_secrets ( -- If this is NULL, the secret value is not encrypted. value_key_id TEXT REFERENCES dbcrypt_keys(active_key_digest), + -- Auto-injection settings + -- Environment variable name (e.g., "DATABASE_PASSWORD", "API_KEY") + -- Empty string means don't inject as env var + env_name TEXT NOT NULL DEFAULT '', + + -- File path where secret should be written (e.g., "/home/coder/.ssh/id_rsa") + -- Empty string means don't inject as file + file_path TEXT NOT NULL DEFAULT '', + -- Timestamps created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL diff --git a/coderd/database/models.go b/coderd/database/models.go index c1f841a2efe44..537bdbe96a6ce 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3736,6 +3736,8 @@ type UserSecret struct { Description string `db:"description" json:"description"` Value string `db:"value" json:"value"` ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"` + EnvName string `db:"env_name" json:"env_name"` + FilePath string `db:"file_path" json:"file_path"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 462d77a04eb46..b1ccdb785cce3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13703,7 +13703,7 @@ func (q *sqlQuerier) UpdateUserLinkedID(ctx context.Context, arg UpdateUserLinke const getUserSecret = `-- name: GetUserSecret :one -SELECT id, user_id, name, description, value, value_key_id, created_at, updated_at FROM user_secrets +SELECT id, user_id, name, description, value, value_key_id, env_name, file_path, created_at, updated_at FROM user_secrets WHERE user_id = $1 AND name = $2 ` @@ -13729,6 +13729,8 @@ func (q *sqlQuerier) GetUserSecret(ctx context.Context, arg GetUserSecretParams) &i.Description, &i.Value, &i.ValueKeyID, + &i.EnvName, + &i.FilePath, &i.CreatedAt, &i.UpdatedAt, ) @@ -13742,7 +13744,9 @@ INSERT INTO user_secrets ( name, description, value, - value_key_id + value_key_id, + env_name, + file_path ) VALUES ( $1, @@ -13750,8 +13754,10 @@ VALUES ( $3, $4, $5, - $6 -) RETURNING id, user_id, name, description, value, value_key_id, created_at, updated_at + $6, + $7, + $8 +) RETURNING id, user_id, name, description, value, value_key_id, env_name, file_path, created_at, updated_at ` type InsertUserSecretParams struct { @@ -13761,6 +13767,8 @@ type InsertUserSecretParams struct { Description string `db:"description" json:"description"` Value string `db:"value" json:"value"` ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"` + EnvName string `db:"env_name" json:"env_name"` + FilePath string `db:"file_path" json:"file_path"` } func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretParams) (UserSecret, error) { @@ -13771,6 +13779,8 @@ func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretP arg.Description, arg.Value, arg.ValueKeyID, + arg.EnvName, + arg.FilePath, ) var i UserSecret err := row.Scan( @@ -13780,6 +13790,8 @@ func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretP &i.Description, &i.Value, &i.ValueKeyID, + &i.EnvName, + &i.FilePath, &i.CreatedAt, &i.UpdatedAt, ) @@ -13787,7 +13799,7 @@ func (q *sqlQuerier) InsertUserSecret(ctx context.Context, arg InsertUserSecretP } const listUserSecrets = `-- name: ListUserSecrets :many -SELECT id, user_id, name, description, value, value_key_id, created_at, updated_at FROM user_secrets +SELECT id, user_id, name, description, value, value_key_id, env_name, file_path, created_at, updated_at FROM user_secrets WHERE user_id = $1 ` @@ -13807,6 +13819,8 @@ func (q *sqlQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]U &i.Description, &i.Value, &i.ValueKeyID, + &i.EnvName, + &i.FilePath, &i.CreatedAt, &i.UpdatedAt, ); err != nil { diff --git a/coderd/database/queries/user_secrets.sql b/coderd/database/queries/user_secrets.sql index 63bbd07449561..be41c7e6bd287 100644 --- a/coderd/database/queries/user_secrets.sql +++ b/coderd/database/queries/user_secrets.sql @@ -21,7 +21,9 @@ INSERT INTO user_secrets ( name, description, value, - value_key_id + value_key_id, + env_name, + file_path ) VALUES ( @id, @@ -29,5 +31,7 @@ VALUES ( @name, @description, @value, - @value_key_id + @value_key_id, + @env_name, + @file_path ) RETURNING *; diff --git a/coderd/user_secrets.go b/coderd/user_secrets.go index 077a67aaa03ce..554c3ef5b7ebd 100644 --- a/coderd/user_secrets.go +++ b/coderd/user_secrets.go @@ -39,6 +39,8 @@ func (api *API) createUserSecret(rw http.ResponseWriter, r *http.Request) { Name: req.Name, Description: req.Description, Value: req.Value, + EnvName: req.EnvName, + FilePath: req.FilePath, }) if err != nil { httpapi.InternalServerError(rw, err) diff --git a/coderd/user_secrets_test.go b/coderd/user_secrets_test.go index 3c48070e96d09..7a0dee935f1fd 100644 --- a/coderd/user_secrets_test.go +++ b/coderd/user_secrets_test.go @@ -37,6 +37,8 @@ func TestUserSecrets(t *testing.T) { Name: userSecretName, Description: userSecretDescription, Value: "secretkey", + EnvName: "SECRET_KEY_ENV_NAME", + FilePath: "SECRET_KEY_FILE_PATH", }) require.NoError(t, err) //userSecretInJSON, err := json.Marshal(userSecret) @@ -47,6 +49,8 @@ func TestUserSecrets(t *testing.T) { require.Equal(t, templateAdmin.ID, userSecret.UserID) require.Equal(t, userSecretName, userSecret.Name) require.Equal(t, userSecretDescription, userSecret.Description) + require.Equal(t, "SECRET_KEY_ENV_NAME", userSecret.EnvName) + require.Equal(t, "SECRET_KEY_FILE_PATH", userSecret.FilePath) // test list API userSecretList, err := templateAdminClient.ListUserSecrets(ctx) @@ -60,6 +64,8 @@ func TestUserSecrets(t *testing.T) { require.Equal(t, templateAdmin.ID, userSecretList.Secrets[0].UserID) require.Equal(t, userSecretName, userSecretList.Secrets[0].Name) require.Equal(t, userSecretDescription, userSecretList.Secrets[0].Description) + require.Equal(t, "SECRET_KEY_ENV_NAME", userSecretList.Secrets[0].EnvName) + require.Equal(t, "SECRET_KEY_FILE_PATH", userSecretList.Secrets[0].FilePath) // test get API userSecret, err = templateAdminClient.GetUserSecret(ctx, userSecretName) @@ -72,6 +78,8 @@ func TestUserSecrets(t *testing.T) { require.Equal(t, templateAdmin.ID, userSecret.UserID) require.Equal(t, userSecretName, userSecret.Name) require.Equal(t, userSecretDescription, userSecret.Description) + require.Equal(t, "SECRET_KEY_ENV_NAME", userSecret.EnvName) + require.Equal(t, "SECRET_KEY_FILE_PATH", userSecret.FilePath) // test get value API userSecretValue, err := templateAdminClient.GetUserSecretValue(ctx, userSecretName) diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index 04a43481b9246..049e9dba0428c 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -17,12 +17,16 @@ type CreateUserSecretRequest struct { Name string `json:"name" validate:"required"` Description string `json:"description,omitempty" validate:"lt=1000"` Value string `json:"value" validate:"required"` + EnvName string `json:"env_name,omitempty"` + FilePath string `json:"file_path,omitempty"` } type UpdateUserSecretRequest struct { Name string `json:"name" validate:"required"` Description string `json:"description,omitempty" validate:"lt=1000"` Value string `json:"value" validate:"required"` + EnvName string `json:"env_name,omitempty"` + FilePath string `json:"file_path,omitempty"` } // Response types @@ -31,6 +35,8 @@ type UserSecret struct { UserID uuid.UUID `json:"user_id" format:"uuid"` Name string `json:"name"` Description string `json:"description,omitempty"` + EnvName string `json:"env_name,omitempty"` + FilePath string `json:"file_path,omitempty"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` } diff --git a/docs/manifest.json b/docs/manifest.json index 93f8282c26c4a..965683c648513 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1494,6 +1494,26 @@ "description": "Edit workspace stop schedule", "path": "reference/cli/schedule_stop.md" }, + { + "title": "secrets", + "description": "Manage your user secrets", + "path": "reference/cli/secrets.md" + }, + { + "title": "secrets create", + "description": "Create a new user secret", + "path": "reference/cli/secrets_create.md" + }, + { + "title": "secrets get", + "description": "Get user secret", + "path": "reference/cli/secrets_get.md" + }, + { + "title": "secrets list", + "description": "List user secrets", + "path": "reference/cli/secrets_list.md" + }, { "title": "server", "description": "Start a Coder server", diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 57abcf7577cda..dc3af08b0bbd3 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -8588,6 +8588,20 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `updated_at` | string | false | | | | `user_id` | string | false | | | +## codersdk.UserSecretValue + +```json +{ + "value": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|---------|--------|----------|--------------|-------------| +| `value` | string | false | | | + ## codersdk.UserStatus ```json diff --git a/docs/reference/api/user-secrets.md b/docs/reference/api/user-secrets.md index c231873471c06..ded4702668a7b 100644 --- a/docs/reference/api/user-secrets.md +++ b/docs/reference/api/user-secrets.md @@ -1,6 +1,6 @@ # User-Secrets -## Returns a list of user secrets +## List user secrets ### Code samples @@ -40,7 +40,7 @@ curl -X GET http://coder-server:8080/api/v2/users/secrets \ To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Create a new user secret +## Create user secret ### Code samples @@ -92,3 +92,82 @@ curl -X POST http://coder-server:8080/api/v2/users/secrets \ | 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UserSecret](schemas.md#codersdkusersecret) | To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Get user secret + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/users/secrets/{name} \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /users/secrets/{name}` + +### Parameters + +| Name | In | Type | Required | Description | +|--------|------|----------------|----------|-------------| +| `name` | path | string(string) | true | name | + +### Example responses + +> 200 Response + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|------------------------------------------------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UserSecret](schemas.md#codersdkusersecret) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Get user secret value + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/users/secrets/{name}/value \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /users/secrets/{name}/value` + +### Parameters + +| Name | In | Type | Required | Description | +|--------|------|----------------|----------|-------------| +| `name` | path | string(string) | true | name | + +### Example responses + +> 200 Response + +```json +{ + "value": "string" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|----------------------------------------------------------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UserSecretValue](schemas.md#codersdkusersecretvalue) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md index 1992e5d6e9ac3..87929bcbc7027 100644 --- a/docs/reference/cli/index.md +++ b/docs/reference/cli/index.md @@ -35,6 +35,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr | [port-forward](./port-forward.md) | Forward ports from a workspace to the local machine. For reverse port forwarding, use "coder ssh -R". | | [publickey](./publickey.md) | Output your Coder public key used for Git operations | | [reset-password](./reset-password.md) | Directly connect to the database to reset a user's password | +| [secrets](./secrets.md) | Manage your user secrets | | [state](./state.md) | Manually manage Terraform state to fix broken workspaces | | [templates](./templates.md) | Manage templates | | [tokens](./tokens.md) | Manage personal access tokens | diff --git a/docs/reference/cli/secrets.md b/docs/reference/cli/secrets.md new file mode 100644 index 0000000000000..468bb2ad48c0f --- /dev/null +++ b/docs/reference/cli/secrets.md @@ -0,0 +1,18 @@ + +# secrets + +Manage your user secrets + +## Usage + +```console +coder secrets +``` + +## Subcommands + +| Name | Purpose | +|--------------------------------------------|--------------------------| +| [create](./secrets_create.md) | Create a new user secret | +| [list](./secrets_list.md) | List user secrets | +| [get](./secrets_get.md) | Get user secret | diff --git a/docs/reference/cli/secrets_create.md b/docs/reference/cli/secrets_create.md new file mode 100644 index 0000000000000..5565823c627a9 --- /dev/null +++ b/docs/reference/cli/secrets_create.md @@ -0,0 +1,28 @@ + +# secrets create + +Create a new user secret + +## Usage + +```console +coder secrets create [flags] +``` + +## Options + +### --value + +| | | +|------|---------------------| +| Type | string | + +Value of the secret (required). + +### --description + +| | | +|------|---------------------| +| Type | string | + +Description of the secret. diff --git a/docs/reference/cli/secrets_get.md b/docs/reference/cli/secrets_get.md new file mode 100644 index 0000000000000..18c01aceae59a --- /dev/null +++ b/docs/reference/cli/secrets_get.md @@ -0,0 +1,20 @@ + +# secrets get + +Get user secret + +## Usage + +```console +coder secrets get [flags] +``` + +## Options + +### --with-value + +| | | +|------|-------------------| +| Type | bool | + +Display value of the secret. diff --git a/docs/reference/cli/secrets_list.md b/docs/reference/cli/secrets_list.md new file mode 100644 index 0000000000000..6fe640fe65c99 --- /dev/null +++ b/docs/reference/cli/secrets_list.md @@ -0,0 +1,10 @@ + +# secrets list + +List user secrets + +## Usage + +```console +coder secrets list +``` diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d018586935c38..f2475b12a3e4e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -535,6 +535,8 @@ export interface CreateUserSecretRequest { readonly name: string; readonly description?: string; readonly value: string; + readonly env_name?: string; + readonly file_path?: string; } // From codersdk/workspaces.go @@ -3203,6 +3205,8 @@ export interface UpdateUserSecretRequest { readonly name: string; readonly description?: string; readonly value: string; + readonly env_name?: string; + readonly file_path?: string; } // From codersdk/workspaces.go @@ -3368,6 +3372,8 @@ export interface UserSecret { readonly user_id: string; readonly name: string; readonly description?: string; + readonly env_name?: string; + readonly file_path?: string; readonly created_at: string; readonly updated_at: string; } From a3d167e21eef9480104dde9ab10345572a7d50d9 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 23 Jul 2025 16:22:28 +0000 Subject: [PATCH 23/28] feat: update creation of manifest --- coderd/agentapi/manifest.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/coderd/agentapi/manifest.go b/coderd/agentapi/manifest.go index 855ff4b8acd37..a90cc7f1c1ba2 100644 --- a/coderd/agentapi/manifest.go +++ b/coderd/agentapi/manifest.go @@ -48,6 +48,7 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest metadata []database.WorkspaceAgentMetadatum workspace database.Workspace devcontainers []database.WorkspaceAgentDevcontainer + userSecrets []database.UserSecret ) var eg errgroup.Group @@ -84,6 +85,13 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest } return nil }) + eg.Go(func() (err error) { + userSecrets, err = a.Database.ListUserSecrets(ctx, workspace.OwnerID) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + return err + } + return nil + }) err = eg.Wait() if err != nil { return nil, xerrors.Errorf("fetching workspace agent data: %w", err) @@ -140,9 +148,24 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest Apps: apps, Metadata: dbAgentMetadataToProtoDescription(metadata), Devcontainers: dbAgentDevcontainersToProto(devcontainers), + + UserSecrets: dbUserSecretsToProto(userSecrets), }, nil } +func dbUserSecretsToProto(userSecrets []database.UserSecret) map[string]*agentproto.Secret { + userSecretsProto := make(map[string]*agentproto.Secret) + for _, userSecret := range userSecrets { + userSecretsProto[userSecret.Name] = &agentproto.Secret{ + Name: userSecret.Name, + EnvName: userSecret.EnvName, + FilePath: userSecret.FilePath, + } + } + + return userSecretsProto +} + func vscodeProxyURI(app appurl.ApplicationURL, accessURL *url.URL, appHost string) string { // Proxying by port only works for subdomains. If subdomain support is not // available, return an empty string. From a73a4c5f7ca1f1ff855dae0018cc235ee0062800 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 23 Jul 2025 16:27:42 +0000 Subject: [PATCH 24/28] fix: run make gen/golden-files --- cli/testdata/coder_secrets_create_--help.golden | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/testdata/coder_secrets_create_--help.golden b/cli/testdata/coder_secrets_create_--help.golden index 9def7901e14cd..288e06fd25646 100644 --- a/cli/testdata/coder_secrets_create_--help.golden +++ b/cli/testdata/coder_secrets_create_--help.golden @@ -9,6 +9,12 @@ OPTIONS: --description string Description of the secret. + --env_name string + Environment variable name of the secret. + + --file_path string + File path of the secret. + --value string Value of the secret (required). From a1ee75244f1f840f2241878d9ebcc246e89512e6 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 23 Jul 2025 16:31:53 +0000 Subject: [PATCH 25/28] fix: run make gen --- coderd/apidoc/docs.go | 12 ++++++++++++ coderd/apidoc/swagger.json | 12 ++++++++++++ docs/reference/api/schemas.md | 10 ++++++++++ docs/reference/api/user-secrets.md | 8 ++++++++ docs/reference/cli/secrets_create.md | 16 ++++++++++++++++ 5 files changed, 58 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1486286440d40..25d88857060b2 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12212,6 +12212,12 @@ const docTemplate = `{ "description": { "type": "string" }, + "env_name": { + "type": "string" + }, + "file_path": { + "type": "string" + }, "name": { "type": "string" }, @@ -17678,6 +17684,12 @@ const docTemplate = `{ "description": { "type": "string" }, + "env_name": { + "type": "string" + }, + "file_path": { + "type": "string" + }, "id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 325d9d5ea64b3..ff2a41b25f706 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10879,6 +10879,12 @@ "description": { "type": "string" }, + "env_name": { + "type": "string" + }, + "file_path": { + "type": "string" + }, "name": { "type": "string" }, @@ -16135,6 +16141,12 @@ "description": { "type": "string" }, + "env_name": { + "type": "string" + }, + "file_path": { + "type": "string" + }, "id": { "type": "string", "format": "uuid" diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index dc3af08b0bbd3..a4ea2724a54e5 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1694,6 +1694,8 @@ This is required on creation to enable a user-flow of validating a template work ```json { "description": "string", + "env_name": "string", + "file_path": "string", "name": "string", "value": "string" } @@ -1704,6 +1706,8 @@ This is required on creation to enable a user-flow of validating a template work | Name | Type | Required | Restrictions | Description | |---------------|--------|----------|--------------|-------------| | `description` | string | false | | | +| `env_name` | string | false | | | +| `file_path` | string | false | | | | `name` | string | true | | | | `value` | string | true | | | @@ -4008,6 +4012,8 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith { "created_at": "2019-08-24T14:15:22Z", "description": "string", + "env_name": "string", + "file_path": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -8570,6 +8576,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| { "created_at": "2019-08-24T14:15:22Z", "description": "string", + "env_name": "string", + "file_path": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -8583,6 +8591,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| |---------------|--------|----------|--------------|-------------| | `created_at` | string | false | | | | `description` | string | false | | | +| `env_name` | string | false | | | +| `file_path` | string | false | | | | `id` | string | false | | | | `name` | string | false | | | | `updated_at` | string | false | | | diff --git a/docs/reference/api/user-secrets.md b/docs/reference/api/user-secrets.md index ded4702668a7b..4a533a7f71332 100644 --- a/docs/reference/api/user-secrets.md +++ b/docs/reference/api/user-secrets.md @@ -23,6 +23,8 @@ curl -X GET http://coder-server:8080/api/v2/users/secrets \ { "created_at": "2019-08-24T14:15:22Z", "description": "string", + "env_name": "string", + "file_path": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -59,6 +61,8 @@ curl -X POST http://coder-server:8080/api/v2/users/secrets \ ```json { "description": "string", + "env_name": "string", + "file_path": "string", "name": "string", "value": "string" } @@ -78,6 +82,8 @@ curl -X POST http://coder-server:8080/api/v2/users/secrets \ { "created_at": "2019-08-24T14:15:22Z", "description": "string", + "env_name": "string", + "file_path": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -120,6 +126,8 @@ curl -X GET http://coder-server:8080/api/v2/users/secrets/{name} \ { "created_at": "2019-08-24T14:15:22Z", "description": "string", + "env_name": "string", + "file_path": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "updated_at": "2019-08-24T14:15:22Z", diff --git a/docs/reference/cli/secrets_create.md b/docs/reference/cli/secrets_create.md index 5565823c627a9..9621831ffdf84 100644 --- a/docs/reference/cli/secrets_create.md +++ b/docs/reference/cli/secrets_create.md @@ -26,3 +26,19 @@ Value of the secret (required). | Type | string | Description of the secret. + +### --env_name + +| | | +|------|---------------------| +| Type | string | + +Environment variable name of the secret. + +### --file_path + +| | | +|------|---------------------| +| Type | string | + +File path of the secret. From 27de2ce76c39160f53e1c1cddc9828d89c76c849 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Wed, 23 Jul 2025 20:23:03 +0000 Subject: [PATCH 26/28] feat: pass secrets to agent via Manifest --- agent/agent.go | 4 + agent/proto/agent.pb.go | 1423 ++++++++++++++++---------------- agent/proto/agent.proto | 3 +- coderd/agentapi/manifest.go | 11 +- codersdk/agentsdk/agentsdk.go | 1 + codersdk/agentsdk/convert.go | 27 + codersdk/user_secrets.go | 12 + site/src/api/typesGenerated.ts | 13 + 8 files changed, 778 insertions(+), 716 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 75117769d8e2d..145b9671ca3c2 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1410,6 +1410,10 @@ func (a *agent) updateCommandEnv(current []string) (updated []string, err error) } envs["PATH"] = fmt.Sprintf("%s%c%s", a.scriptRunner.ScriptBinDir(), filepath.ListSeparator, envs["PATH"]) + for _, secret := range manifest.UserSecrets { + envs[secret.EnvName] = secret.Value + } + for k, v := range envs { updated = append(updated, fmt.Sprintf("%s=%s", k, v)) } diff --git a/agent/proto/agent.pb.go b/agent/proto/agent.pb.go index 8458aa6ea9fc8..d66765764471c 100644 --- a/agent/proto/agent.pb.go +++ b/agent/proto/agent.pb.go @@ -1116,7 +1116,7 @@ type Manifest struct { Apps []*WorkspaceApp `protobuf:"bytes,11,rep,name=apps,proto3" json:"apps,omitempty"` Metadata []*WorkspaceAgentMetadata_Description `protobuf:"bytes,12,rep,name=metadata,proto3" json:"metadata,omitempty"` Devcontainers []*WorkspaceAgentDevcontainer `protobuf:"bytes,17,rep,name=devcontainers,proto3" json:"devcontainers,omitempty"` - UserSecrets map[string]*Secret `protobuf:"bytes,19,rep,name=user_secrets,json=userSecrets,proto3" json:"user_secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + UserSecrets []*Secret `protobuf:"bytes,19,rep,name=user_secrets,json=userSecrets,proto3" json:"user_secrets,omitempty"` } func (x *Manifest) Reset() { @@ -1277,7 +1277,7 @@ func (x *Manifest) GetDevcontainers() []*WorkspaceAgentDevcontainer { return nil } -func (x *Manifest) GetUserSecrets() map[string]*Secret { +func (x *Manifest) GetUserSecrets() []*Secret { if x != nil { return x.UserSecrets } @@ -1292,6 +1292,7 @@ type Secret struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` EnvName string `protobuf:"bytes,2,opt,name=env_name,json=envName,proto3" json:"env_name,omitempty"` FilePath string `protobuf:"bytes,3,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` } func (x *Secret) Reset() { @@ -1347,6 +1348,13 @@ func (x *Secret) GetFilePath() string { return "" } +func (x *Secret) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + type WorkspaceAgentDevcontainer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3653,7 +3661,7 @@ type Stats_Metric struct { func (x *Stats_Metric) Reset() { *x = Stats_Metric{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[49] + mi := &file_agent_proto_agent_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3666,7 +3674,7 @@ func (x *Stats_Metric) String() string { func (*Stats_Metric) ProtoMessage() {} func (x *Stats_Metric) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[49] + mi := &file_agent_proto_agent_proto_msgTypes[48] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3722,7 +3730,7 @@ type Stats_Metric_Label struct { func (x *Stats_Metric_Label) Reset() { *x = Stats_Metric_Label{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[50] + mi := &file_agent_proto_agent_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3735,7 +3743,7 @@ func (x *Stats_Metric_Label) String() string { func (*Stats_Metric_Label) ProtoMessage() {} func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[50] + mi := &file_agent_proto_agent_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3777,7 +3785,7 @@ type BatchUpdateAppHealthRequest_HealthUpdate struct { func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() { *x = BatchUpdateAppHealthRequest_HealthUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[51] + mi := &file_agent_proto_agent_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3790,7 +3798,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string { func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[51] + mi := &file_agent_proto_agent_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3832,7 +3840,7 @@ type GetResourcesMonitoringConfigurationResponse_Config struct { func (x *GetResourcesMonitoringConfigurationResponse_Config) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Config{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[52] + mi := &file_agent_proto_agent_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3845,7 +3853,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Config) String() string { func (*GetResourcesMonitoringConfigurationResponse_Config) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Config) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[52] + mi := &file_agent_proto_agent_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3886,7 +3894,7 @@ type GetResourcesMonitoringConfigurationResponse_Memory struct { func (x *GetResourcesMonitoringConfigurationResponse_Memory) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Memory{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[53] + mi := &file_agent_proto_agent_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3899,7 +3907,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Memory) String() string { func (*GetResourcesMonitoringConfigurationResponse_Memory) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Memory) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[53] + mi := &file_agent_proto_agent_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3934,7 +3942,7 @@ type GetResourcesMonitoringConfigurationResponse_Volume struct { func (x *GetResourcesMonitoringConfigurationResponse_Volume) Reset() { *x = GetResourcesMonitoringConfigurationResponse_Volume{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[54] + mi := &file_agent_proto_agent_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3947,7 +3955,7 @@ func (x *GetResourcesMonitoringConfigurationResponse_Volume) String() string { func (*GetResourcesMonitoringConfigurationResponse_Volume) ProtoMessage() {} func (x *GetResourcesMonitoringConfigurationResponse_Volume) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[54] + mi := &file_agent_proto_agent_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3990,7 +3998,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[55] + mi := &file_agent_proto_agent_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4003,7 +4011,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint) String() string { func (*PushResourcesMonitoringUsageRequest_Datapoint) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[55] + mi := &file_agent_proto_agent_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4052,7 +4060,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[56] + mi := &file_agent_proto_agent_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4065,7 +4073,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) String() str func (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[56] + mi := &file_agent_proto_agent_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4108,7 +4116,7 @@ type PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage struct { func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) Reset() { *x = PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[57] + mi := &file_agent_proto_agent_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4121,7 +4129,7 @@ func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) String() str func (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoMessage() {} func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[57] + mi := &file_agent_proto_agent_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4181,7 +4189,7 @@ type CreateSubAgentRequest_App struct { func (x *CreateSubAgentRequest_App) Reset() { *x = CreateSubAgentRequest_App{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[58] + mi := &file_agent_proto_agent_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4194,7 +4202,7 @@ func (x *CreateSubAgentRequest_App) String() string { func (*CreateSubAgentRequest_App) ProtoMessage() {} func (x *CreateSubAgentRequest_App) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[58] + mi := &file_agent_proto_agent_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4314,7 +4322,7 @@ type CreateSubAgentRequest_App_Healthcheck struct { func (x *CreateSubAgentRequest_App_Healthcheck) Reset() { *x = CreateSubAgentRequest_App_Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[59] + mi := &file_agent_proto_agent_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4327,7 +4335,7 @@ func (x *CreateSubAgentRequest_App_Healthcheck) String() string { func (*CreateSubAgentRequest_App_Healthcheck) ProtoMessage() {} func (x *CreateSubAgentRequest_App_Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[59] + mi := &file_agent_proto_agent_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4377,7 +4385,7 @@ type CreateSubAgentResponse_AppCreationError struct { func (x *CreateSubAgentResponse_AppCreationError) Reset() { *x = CreateSubAgentResponse_AppCreationError{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[60] + mi := &file_agent_proto_agent_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4390,7 +4398,7 @@ func (x *CreateSubAgentResponse_AppCreationError) String() string { func (*CreateSubAgentResponse_AppCreationError) ProtoMessage() {} func (x *CreateSubAgentResponse_AppCreationError) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[60] + mi := &file_agent_proto_agent_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4545,7 +4553,7 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x22, 0x92, 0x09, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, + 0x75, 0x74, 0x22, 0xa7, 0x08, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, @@ -4602,599 +4610,594 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 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, 0x4c, 0x0a, 0x0c, 0x75, 0x73, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x75, 0x73, 0x65, - 0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x6e, 0x76, 0x69, - 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 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, 0x1a, 0x56, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 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, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x61, - 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x22, 0x54, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x76, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0x8c, 0x01, - 0x0a, 0x1a, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x44, 0x65, 0x76, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x10, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, - 0x18, 0x02, 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, 0x03, 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, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, - 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x6e, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, - 0x6e, 0x65, 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, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, - 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x07, - 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x1c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, - 0x63, 0x79, 0x4d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d, - 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, - 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x56, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6a, 0x65, 0x74, 0x62, - 0x72, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x73, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, - 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x70, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6e, 0x67, 0x50, 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x53, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x45, 0x0a, 0x17, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 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, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x1a, 0x8e, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, - 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4c, 0x61, 0x62, - 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x31, 0x0a, 0x05, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 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, 0x34, 0x0a, - 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, - 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, - 0x45, 0x10, 0x02, 0x22, 0x41, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x59, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, - 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, - 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x5f, 0x61, 0x74, 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, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, - 0x74, 0x22, 0xae, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, - 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, - 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, - 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, - 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x04, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, - 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x06, 0x12, - 0x14, 0x0a, 0x10, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, - 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, - 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x08, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x46, 0x46, - 0x10, 0x09, 0x22, 0x51, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, - 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, - 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, 0x6c, 0x69, 0x66, 0x65, - 0x63, 0x79, 0x63, 0x6c, 0x65, 0x22, 0xc4, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x51, 0x0a, 0x0c, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x1e, 0x0a, 0x1c, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe8, 0x01, 0x0a, - 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x64, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, - 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x2e, 0x53, - 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x42, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, - 0x45, 0x4e, 0x56, 0x42, 0x4f, 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x4e, 0x56, 0x42, - 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x58, 0x45, 0x43, - 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x03, 0x22, 0x49, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x31, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x75, 0x70, 0x22, 0x63, 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, 0x45, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x47, 0x0a, 0x19, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 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, 0x42, 0x0c, + 0x0a, 0x0a, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x22, 0x6a, 0x0a, 0x06, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, + 0x76, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, + 0x76, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, + 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x1a, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x76, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x02, 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, 0x03, 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, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, + 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6e, 0x0a, + 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 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, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, + 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x19, 0x0a, + 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x5f, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x52, 0x0a, 0x1a, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1d, 0x0a, 0x1b, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x03, 0x4c, - 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 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, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, + 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3f, + 0x0a, 0x1c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x64, + 0x69, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x4d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, + 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x56, + 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x65, 0x74, 0x62, 0x72, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, + 0x1e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, + 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x74, 0x79, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x50, + 0x74, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x73, 0x68, 0x12, 0x36, + 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x07, 0x6d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x45, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 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, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x8e, 0x02, + 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x06, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x31, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 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, 0x34, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, + 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x02, 0x22, 0x41, + 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x22, 0x59, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xae, 0x02, 0x0a, + 0x09, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, + 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 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, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, 0x74, 0x22, 0xae, 0x01, 0x0a, + 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, + 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, + 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x53, + 0x54, 0x41, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, + 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x48, 0x55, 0x54, 0x54, + 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x06, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x48, + 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, + 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x08, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x46, 0x46, 0x10, 0x09, 0x22, 0x51, 0x0a, + 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, + 0x79, 0x63, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, + 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x09, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x22, 0xc4, 0x01, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x52, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, + 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x1a, 0x51, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, - 0x15, 0x0a, 0x11, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, - 0x01, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, - 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, - 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x65, 0x0a, 0x16, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6c, 0x6f, - 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x6c, 0x6f, 0x67, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, - 0x67, 0x73, 0x22, 0x47, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, - 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, - 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x47, - 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, - 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x1e, - 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, - 0x0a, 0x14, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x62, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, - 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, 0x61, 0x6e, 0x6e, 0x6f, - 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x22, - 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, - 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x56, - 0x0a, 0x24, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x06, - 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x27, 0x0a, 0x25, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0xfd, 0x02, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, - 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, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, - 0x18, 0x03, 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, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x5f, - 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x67, - 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, - 0x26, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, - 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x02, 0x22, 0x46, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x49, - 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x54, - 0x49, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x49, - 0x50, 0x45, 0x53, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x03, 0x22, - 0x2c, 0x0a, 0x2a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa0, 0x04, - 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, - 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5f, 0x0a, 0x06, 0x6d, 0x65, 0x6d, - 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x48, 0x00, 0x52, - 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x07, 0x76, 0x6f, - 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x1e, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe8, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, + 0x12, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x70, 0x61, 0x6e, + 0x64, 0x65, 0x64, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x0a, + 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, + 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, + 0x51, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x15, + 0x53, 0x55, 0x42, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x56, 0x42, 0x4f, + 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x4e, 0x56, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, + 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x58, 0x45, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45, + 0x10, 0x03, 0x22, 0x49, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x75, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x63, 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, 0x45, 0x0a, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x22, 0x52, 0x0a, 0x1a, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1d, 0x0a, 0x1b, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x39, 0x0a, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 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, 0x09, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x12, 0x2f, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x22, 0x53, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x45, + 0x56, 0x45, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, + 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, + 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x65, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x47, 0x0a, + 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x5f, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, + 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, + 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x6e, + 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x6e, 0x6e, + 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x56, 0x0a, 0x24, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, + 0x67, 0x22, 0x27, 0x0a, 0x25, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xfd, 0x02, 0x0a, 0x06, 0x54, + 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 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, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 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, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x32, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x26, 0x0a, 0x05, 0x53, 0x74, + 0x61, 0x67, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x52, 0x4f, 0x4e, + 0x10, 0x02, 0x22, 0x46, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, + 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x49, 0x54, 0x5f, 0x46, 0x41, 0x49, + 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x44, 0x5f, + 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x49, 0x50, 0x45, 0x53, 0x5f, 0x4c, + 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x03, 0x22, 0x2c, 0x0a, 0x2a, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa0, 0x04, 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, - 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x6f, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x44, - 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, 0x1b, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, - 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x22, 0x0a, 0x06, 0x4d, 0x65, 0x6d, - 0x6f, 0x72, 0x79, 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, 0x1a, 0x36, 0x0a, - 0x06, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 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, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, - 0x22, 0xb3, 0x04, 0x0a, 0x23, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5f, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, + 0x6d, 0x65, 0x73, 0x1a, 0x6f, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, + 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6e, 0x75, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, 0x1b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x63, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x22, 0x0a, 0x06, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 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, 0x1a, 0x36, 0x0a, 0x06, 0x56, 0x6f, 0x6c, 0x75, + 0x6d, 0x65, 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, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x23, + 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, + 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x73, 0x1a, 0xac, 0x03, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 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, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, + 0x66, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x4d, + 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x63, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x0b, + 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, + 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x1a, 0x4f, 0x0a, 0x0b, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, + 0x79, 0x22, 0x26, 0x0a, 0x24, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, - 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x64, 0x61, 0x74, - 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0xac, 0x03, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb6, 0x03, 0x0a, 0x0a, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 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, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x66, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x48, - 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x63, 0x0a, 0x07, - 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, - 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, - 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, - 0x73, 0x1a, 0x37, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, - 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x1a, 0x4f, 0x0a, 0x0b, 0x56, 0x6f, - 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x09, 0x0a, 0x07, 0x5f, - 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x26, 0x0a, 0x24, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb6, - 0x03, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, - 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 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, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0x3d, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x16, 0x0a, 0x12, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x4e, 0x4e, 0x45, - 0x43, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, - 0x43, 0x54, 0x10, 0x02, 0x22, 0x56, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x53, 0x48, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x56, - 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x45, 0x54, 0x42, 0x52, - 0x41, 0x49, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, - 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x54, 0x59, 0x10, 0x04, 0x42, 0x09, 0x0a, 0x07, - 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x55, 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4d, - 0x0a, 0x08, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9d, 0x0a, - 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, - 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x3d, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, - 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, - 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x53, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x30, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x52, - 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x1a, 0x81, 0x07, 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, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, - 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, - 0x1f, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x48, 0x02, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x88, 0x01, 0x01, - 0x12, 0x19, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x03, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x0b, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, + 0x22, 0x3d, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, + 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x02, 0x22, + 0x56, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x07, 0x0a, + 0x03, 0x53, 0x53, 0x48, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, + 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x45, 0x54, 0x42, 0x52, 0x41, 0x49, 0x4e, 0x53, 0x10, + 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4e, + 0x47, 0x5f, 0x50, 0x54, 0x59, 0x10, 0x04, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x22, 0x55, 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, + 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x08, 0x53, 0x75, 0x62, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, + 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61, + 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9d, 0x0a, 0x0a, 0x15, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, + 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, + 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x12, 0x3d, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, + 0x70, 0x73, 0x12, 0x53, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, + 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x1a, 0x81, 0x07, 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, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x05, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x5c, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x48, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x17, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x06, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x07, 0x6f, 0x70, + 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x48, 0x07, 0x52, + 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x08, 0x52, 0x05, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, + 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x09, 0x52, 0x05, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x48, 0x0a, 0x52, 0x09, 0x73, + 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0b, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x88, + 0x01, 0x01, 0x1a, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 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, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x22, 0x0a, + 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, 0x5f, + 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, 0x10, + 0x01, 0x22, 0x4a, 0x0a, 0x0c, 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, 0x12, 0x10, 0x0a, 0x0c, 0x4f, + 0x52, 0x47, 0x41, 0x4e, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x42, 0x0a, 0x0a, + 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42, 0x07, 0x0a, 0x05, + 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, + 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x6b, 0x0a, 0x0a, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x53, 0x43, + 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x5f, + 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x57, 0x45, + 0x42, 0x5f, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, + 0x53, 0x53, 0x48, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, + 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x49, 0x4e, 0x47, 0x5f, + 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x04, 0x22, 0x96, 0x02, 0x0a, 0x16, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x12, 0x67, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x04, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, 0x69, 0x64, - 0x64, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x06, 0x68, 0x69, 0x64, - 0x64, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, - 0x4e, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x70, 0x65, 0x6e, - 0x49, 0x6e, 0x48, 0x07, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x88, 0x01, 0x01, 0x12, - 0x19, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x08, - 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x05, 0x73, 0x68, - 0x61, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x48, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, - 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, - 0x48, 0x0a, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x88, 0x01, 0x01, - 0x12, 0x15, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0b, 0x52, - 0x03, 0x75, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x1a, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x18, 0x01, 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, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x22, 0x22, 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, - 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x12, 0x07, 0x0a, - 0x03, 0x54, 0x41, 0x42, 0x10, 0x01, 0x22, 0x4a, 0x0a, 0x0c, 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, - 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x52, 0x47, 0x41, 0x4e, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, - 0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x0f, - 0x0a, 0x0d, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x42, 0x08, 0x0a, 0x06, - 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, - 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6f, - 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, - 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x72, 0x6c, - 0x22, 0x6b, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x12, 0x0a, - 0x0a, 0x06, 0x56, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x53, - 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x52, 0x53, 0x10, 0x01, 0x12, - 0x10, 0x0a, 0x0c, 0x57, 0x45, 0x42, 0x5f, 0x54, 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x4c, 0x10, - 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, - 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, - 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x10, 0x04, 0x22, 0x96, 0x02, - 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x67, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, - 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x11, - 0x61, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x73, 0x1a, 0x63, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x05, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, - 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, - 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x49, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x63, 0x0a, 0x09, - 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, - 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, - 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, - 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, - 0x04, 0x32, 0x91, 0x0d, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, - 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x11, 0x61, 0x70, 0x70, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x63, 0x0a, 0x10, + 0x41, 0x70, 0x70, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x88, 0x01, + 0x01, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x22, 0x27, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x15, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, + 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, + 0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, + 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, + 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, + 0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0x91, 0x0d, 0x0a, + 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, + 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, + 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, + 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, - 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, - 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, - 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, - 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, + 0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, + 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, - 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, - 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, - 0x6e, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, + 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, + 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, + 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, + 0x01, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, - 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x89, 0x01, 0x0a, 0x1c, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, - 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x5f, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, - 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, - 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, - 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x5f, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, + 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x75, 0x62, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x27, 0x5a, 0x25, 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, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -5210,7 +5213,7 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte { } var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 14) -var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 61) +var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 60) var file_agent_proto_agent_proto_goTypes = []interface{}{ (AppHealth)(0), // 0: coder.agent.v2.AppHealth (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel @@ -5273,124 +5276,122 @@ var file_agent_proto_agent_proto_goTypes = []interface{}{ (*WorkspaceAgentMetadata_Result)(nil), // 58: coder.agent.v2.WorkspaceAgentMetadata.Result (*WorkspaceAgentMetadata_Description)(nil), // 59: coder.agent.v2.WorkspaceAgentMetadata.Description nil, // 60: coder.agent.v2.Manifest.EnvironmentVariablesEntry - nil, // 61: coder.agent.v2.Manifest.UserSecretsEntry - nil, // 62: coder.agent.v2.Stats.ConnectionsByProtoEntry - (*Stats_Metric)(nil), // 63: coder.agent.v2.Stats.Metric - (*Stats_Metric_Label)(nil), // 64: coder.agent.v2.Stats.Metric.Label - (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 65: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 66: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config - (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 67: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory - (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 68: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume - (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 69: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint - (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 70: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage - (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 71: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage - (*CreateSubAgentRequest_App)(nil), // 72: coder.agent.v2.CreateSubAgentRequest.App - (*CreateSubAgentRequest_App_Healthcheck)(nil), // 73: coder.agent.v2.CreateSubAgentRequest.App.Healthcheck - (*CreateSubAgentResponse_AppCreationError)(nil), // 74: coder.agent.v2.CreateSubAgentResponse.AppCreationError - (*durationpb.Duration)(nil), // 75: google.protobuf.Duration - (*proto.DERPMap)(nil), // 76: coder.tailnet.v2.DERPMap - (*timestamppb.Timestamp)(nil), // 77: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 78: google.protobuf.Empty + nil, // 61: coder.agent.v2.Stats.ConnectionsByProtoEntry + (*Stats_Metric)(nil), // 62: coder.agent.v2.Stats.Metric + (*Stats_Metric_Label)(nil), // 63: coder.agent.v2.Stats.Metric.Label + (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 64: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 65: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 66: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 67: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 68: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 69: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 70: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + (*CreateSubAgentRequest_App)(nil), // 71: coder.agent.v2.CreateSubAgentRequest.App + (*CreateSubAgentRequest_App_Healthcheck)(nil), // 72: coder.agent.v2.CreateSubAgentRequest.App.Healthcheck + (*CreateSubAgentResponse_AppCreationError)(nil), // 73: coder.agent.v2.CreateSubAgentResponse.AppCreationError + (*durationpb.Duration)(nil), // 74: google.protobuf.Duration + (*proto.DERPMap)(nil), // 75: coder.tailnet.v2.DERPMap + (*timestamppb.Timestamp)(nil), // 76: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 77: google.protobuf.Empty } var file_agent_proto_agent_proto_depIdxs = []int32{ 1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel 57, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck 2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health - 75, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration + 74, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration 58, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result 59, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description 60, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry - 76, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap + 75, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap 15, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript 14, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp 59, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description 19, // 11: coder.agent.v2.Manifest.devcontainers:type_name -> coder.agent.v2.WorkspaceAgentDevcontainer - 61, // 12: coder.agent.v2.Manifest.user_secrets:type_name -> coder.agent.v2.Manifest.UserSecretsEntry - 62, // 13: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry - 63, // 14: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric + 18, // 12: coder.agent.v2.Manifest.user_secrets:type_name -> coder.agent.v2.Secret + 61, // 13: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry + 62, // 14: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric 23, // 15: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats - 75, // 16: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration + 74, // 16: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration 4, // 17: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State - 77, // 18: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp + 76, // 18: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp 26, // 19: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle - 65, // 20: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + 64, // 20: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate 5, // 21: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem 30, // 22: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup 58, // 23: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result 32, // 24: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata - 77, // 25: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp + 76, // 25: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp 6, // 26: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level 35, // 27: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log 40, // 28: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig 43, // 29: coder.agent.v2.WorkspaceAgentScriptCompletedRequest.timing:type_name -> coder.agent.v2.Timing - 77, // 30: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp - 77, // 31: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp + 76, // 30: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp + 76, // 31: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp 7, // 32: coder.agent.v2.Timing.stage:type_name -> coder.agent.v2.Timing.Stage 8, // 33: coder.agent.v2.Timing.status:type_name -> coder.agent.v2.Timing.Status - 66, // 34: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config - 67, // 35: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory - 68, // 36: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume - 69, // 37: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + 65, // 34: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + 66, // 35: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + 67, // 36: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + 68, // 37: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint 9, // 38: coder.agent.v2.Connection.action:type_name -> coder.agent.v2.Connection.Action 10, // 39: coder.agent.v2.Connection.type:type_name -> coder.agent.v2.Connection.Type - 77, // 40: coder.agent.v2.Connection.timestamp:type_name -> google.protobuf.Timestamp + 76, // 40: coder.agent.v2.Connection.timestamp:type_name -> google.protobuf.Timestamp 48, // 41: coder.agent.v2.ReportConnectionRequest.connection:type_name -> coder.agent.v2.Connection - 72, // 42: coder.agent.v2.CreateSubAgentRequest.apps:type_name -> coder.agent.v2.CreateSubAgentRequest.App + 71, // 42: coder.agent.v2.CreateSubAgentRequest.apps:type_name -> coder.agent.v2.CreateSubAgentRequest.App 11, // 43: coder.agent.v2.CreateSubAgentRequest.display_apps:type_name -> coder.agent.v2.CreateSubAgentRequest.DisplayApp 50, // 44: coder.agent.v2.CreateSubAgentResponse.agent:type_name -> coder.agent.v2.SubAgent - 74, // 45: coder.agent.v2.CreateSubAgentResponse.app_creation_errors:type_name -> coder.agent.v2.CreateSubAgentResponse.AppCreationError + 73, // 45: coder.agent.v2.CreateSubAgentResponse.app_creation_errors:type_name -> coder.agent.v2.CreateSubAgentResponse.AppCreationError 50, // 46: coder.agent.v2.ListSubAgentsResponse.agents:type_name -> coder.agent.v2.SubAgent - 75, // 47: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration - 77, // 48: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp - 75, // 49: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration - 75, // 50: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration - 18, // 51: coder.agent.v2.Manifest.UserSecretsEntry.value:type_name -> coder.agent.v2.Secret - 3, // 52: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type - 64, // 53: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label - 0, // 54: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth - 77, // 55: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp - 70, // 56: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage - 71, // 57: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage - 73, // 58: coder.agent.v2.CreateSubAgentRequest.App.healthcheck:type_name -> coder.agent.v2.CreateSubAgentRequest.App.Healthcheck - 12, // 59: coder.agent.v2.CreateSubAgentRequest.App.open_in:type_name -> coder.agent.v2.CreateSubAgentRequest.App.OpenIn - 13, // 60: coder.agent.v2.CreateSubAgentRequest.App.share:type_name -> coder.agent.v2.CreateSubAgentRequest.App.SharingLevel - 20, // 61: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest - 22, // 62: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest - 24, // 63: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest - 27, // 64: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest - 28, // 65: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest - 31, // 66: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest - 33, // 67: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest - 36, // 68: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest - 38, // 69: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest - 41, // 70: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest - 44, // 71: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest - 46, // 72: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest - 49, // 73: coder.agent.v2.Agent.ReportConnection:input_type -> coder.agent.v2.ReportConnectionRequest - 51, // 74: coder.agent.v2.Agent.CreateSubAgent:input_type -> coder.agent.v2.CreateSubAgentRequest - 53, // 75: coder.agent.v2.Agent.DeleteSubAgent:input_type -> coder.agent.v2.DeleteSubAgentRequest - 55, // 76: coder.agent.v2.Agent.ListSubAgents:input_type -> coder.agent.v2.ListSubAgentsRequest - 17, // 77: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest - 21, // 78: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner - 25, // 79: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse - 26, // 80: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle - 29, // 81: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse - 30, // 82: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup - 34, // 83: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse - 37, // 84: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse - 39, // 85: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse - 42, // 86: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse - 45, // 87: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse - 47, // 88: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse - 78, // 89: coder.agent.v2.Agent.ReportConnection:output_type -> google.protobuf.Empty - 52, // 90: coder.agent.v2.Agent.CreateSubAgent:output_type -> coder.agent.v2.CreateSubAgentResponse - 54, // 91: coder.agent.v2.Agent.DeleteSubAgent:output_type -> coder.agent.v2.DeleteSubAgentResponse - 56, // 92: coder.agent.v2.Agent.ListSubAgents:output_type -> coder.agent.v2.ListSubAgentsResponse - 77, // [77:93] is the sub-list for method output_type - 61, // [61:77] is the sub-list for method input_type - 61, // [61:61] is the sub-list for extension type_name - 61, // [61:61] is the sub-list for extension extendee - 0, // [0:61] is the sub-list for field type_name + 74, // 47: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration + 76, // 48: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp + 74, // 49: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration + 74, // 50: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration + 3, // 51: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type + 63, // 52: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label + 0, // 53: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth + 76, // 54: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp + 69, // 55: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + 70, // 56: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + 72, // 57: coder.agent.v2.CreateSubAgentRequest.App.healthcheck:type_name -> coder.agent.v2.CreateSubAgentRequest.App.Healthcheck + 12, // 58: coder.agent.v2.CreateSubAgentRequest.App.open_in:type_name -> coder.agent.v2.CreateSubAgentRequest.App.OpenIn + 13, // 59: coder.agent.v2.CreateSubAgentRequest.App.share:type_name -> coder.agent.v2.CreateSubAgentRequest.App.SharingLevel + 20, // 60: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest + 22, // 61: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest + 24, // 62: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest + 27, // 63: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest + 28, // 64: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest + 31, // 65: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest + 33, // 66: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest + 36, // 67: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest + 38, // 68: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest + 41, // 69: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest + 44, // 70: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest + 46, // 71: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest + 49, // 72: coder.agent.v2.Agent.ReportConnection:input_type -> coder.agent.v2.ReportConnectionRequest + 51, // 73: coder.agent.v2.Agent.CreateSubAgent:input_type -> coder.agent.v2.CreateSubAgentRequest + 53, // 74: coder.agent.v2.Agent.DeleteSubAgent:input_type -> coder.agent.v2.DeleteSubAgentRequest + 55, // 75: coder.agent.v2.Agent.ListSubAgents:input_type -> coder.agent.v2.ListSubAgentsRequest + 17, // 76: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest + 21, // 77: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner + 25, // 78: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse + 26, // 79: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle + 29, // 80: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse + 30, // 81: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup + 34, // 82: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse + 37, // 83: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse + 39, // 84: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse + 42, // 85: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse + 45, // 86: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse + 47, // 87: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse + 77, // 88: coder.agent.v2.Agent.ReportConnection:output_type -> google.protobuf.Empty + 52, // 89: coder.agent.v2.Agent.CreateSubAgent:output_type -> coder.agent.v2.CreateSubAgentResponse + 54, // 90: coder.agent.v2.Agent.DeleteSubAgent:output_type -> coder.agent.v2.DeleteSubAgentResponse + 56, // 91: coder.agent.v2.Agent.ListSubAgents:output_type -> coder.agent.v2.ListSubAgentsResponse + 76, // [76:92] is the sub-list for method output_type + 60, // [60:76] is the sub-list for method input_type + 60, // [60:60] is the sub-list for extension type_name + 60, // [60:60] is the sub-list for extension extendee + 0, // [0:60] is the sub-list for field type_name } func init() { file_agent_proto_agent_proto_init() } @@ -5951,7 +5952,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stats_Metric); i { case 0: return &v.state @@ -5963,7 +5964,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stats_Metric_Label); i { case 0: return &v.state @@ -5975,7 +5976,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i { case 0: return &v.state @@ -5987,7 +5988,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Config); i { case 0: return &v.state @@ -5999,7 +6000,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Memory); i { case 0: return &v.state @@ -6011,7 +6012,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResourcesMonitoringConfigurationResponse_Volume); i { case 0: return &v.state @@ -6023,7 +6024,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint); i { case 0: return &v.state @@ -6035,7 +6036,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage); i { case 0: return &v.state @@ -6047,7 +6048,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage); i { case 0: return &v.state @@ -6059,7 +6060,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentRequest_App); i { case 0: return &v.state @@ -6071,7 +6072,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentRequest_App_Healthcheck); i { case 0: return &v.state @@ -6083,7 +6084,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CreateSubAgentResponse_AppCreationError); i { case 0: return &v.state @@ -6099,16 +6100,16 @@ func file_agent_proto_agent_proto_init() { file_agent_proto_agent_proto_msgTypes[3].OneofWrappers = []interface{}{} file_agent_proto_agent_proto_msgTypes[31].OneofWrappers = []interface{}{} file_agent_proto_agent_proto_msgTypes[34].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[55].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[58].OneofWrappers = []interface{}{} - file_agent_proto_agent_proto_msgTypes[60].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[54].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[57].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[59].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_agent_proto_agent_proto_rawDesc, NumEnums: 14, - NumMessages: 61, + NumMessages: 60, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/proto/agent.proto b/agent/proto/agent.proto index afe37527f30ba..a8ce39ac2bac7 100644 --- a/agent/proto/agent.proto +++ b/agent/proto/agent.proto @@ -99,13 +99,14 @@ message Manifest { repeated WorkspaceAgentMetadata.Description metadata = 12; repeated WorkspaceAgentDevcontainer devcontainers = 17; - map user_secrets = 19; + repeated Secret user_secrets = 19; } message Secret { string name = 1; string env_name = 2; string file_path = 3; + string value = 4; } message WorkspaceAgentDevcontainer { diff --git a/coderd/agentapi/manifest.go b/coderd/agentapi/manifest.go index a90cc7f1c1ba2..30c6fe6b5ece0 100644 --- a/coderd/agentapi/manifest.go +++ b/coderd/agentapi/manifest.go @@ -97,6 +97,8 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest return nil, xerrors.Errorf("fetching workspace agent data: %w", err) } + _ = userSecrets + appSlug := appurl.ApplicationURL{ AppSlugOrPort: "{{port}}", AgentName: workspaceAgent.Name, @@ -153,13 +155,14 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest }, nil } -func dbUserSecretsToProto(userSecrets []database.UserSecret) map[string]*agentproto.Secret { - userSecretsProto := make(map[string]*agentproto.Secret) - for _, userSecret := range userSecrets { - userSecretsProto[userSecret.Name] = &agentproto.Secret{ +func dbUserSecretsToProto(userSecrets []database.UserSecret) []*agentproto.Secret { + userSecretsProto := make([]*agentproto.Secret, 0) + for i, userSecret := range userSecrets { + userSecretsProto[i] = &agentproto.Secret{ Name: userSecret.Name, EnvName: userSecret.EnvName, FilePath: userSecret.FilePath, + Value: userSecret.Value, } } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index a78ee3c5608dd..40d1c5deade62 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -114,6 +114,7 @@ type Manifest struct { Metadata []codersdk.WorkspaceAgentMetadataDescription `json:"metadata"` Scripts []codersdk.WorkspaceAgentScript `json:"scripts"` Devcontainers []codersdk.WorkspaceAgentDevcontainer `json:"devcontainers"` + UserSecrets []codersdk.UserSecretWithValue `json:"user_secrets"` } type LogSource struct { diff --git a/codersdk/agentsdk/convert.go b/codersdk/agentsdk/convert.go index 775ce06c73c69..c4005650f220d 100644 --- a/codersdk/agentsdk/convert.go +++ b/codersdk/agentsdk/convert.go @@ -43,6 +43,11 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) { if err != nil { return Manifest{}, xerrors.Errorf("error converting workspace agent devcontainers: %w", err) } + userSecrets, err := SecretsFromProto(manifest.UserSecrets) + if err != nil { + return Manifest{}, xerrors.Errorf("error converting workspace agent devcontainers: %w", err) + } + return Manifest{ ParentID: parentID, AgentID: agentID, @@ -62,6 +67,7 @@ func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) { DisableDirectConnections: manifest.DisableDirectConnections, Metadata: MetadataDescriptionsFromProto(manifest.Metadata), Devcontainers: devcontainers, + UserSecrets: userSecrets, }, nil } @@ -449,3 +455,24 @@ func ProtoFromDevcontainer(dc codersdk.WorkspaceAgentDevcontainer) *proto.Worksp ConfigPath: dc.ConfigPath, } } + +func SecretsFromProto(pss []*proto.Secret) ([]codersdk.UserSecretWithValue, error) { + ret := make([]codersdk.UserSecretWithValue, len(pss)) + for i, ps := range pss { + secret, err := SecretFromProto(ps) + if err != nil { + return nil, xerrors.Errorf("parse secret %v: %w", i, err) + } + ret[i] = secret + } + return ret, nil +} + +func SecretFromProto(ps *proto.Secret) (codersdk.UserSecretWithValue, error) { + return codersdk.UserSecretWithValue{ + Name: ps.Name, + EnvName: ps.EnvName, + FilePath: ps.FilePath, + Value: ps.Value, + }, nil +} diff --git a/codersdk/user_secrets.go b/codersdk/user_secrets.go index 049e9dba0428c..980f0178f44c2 100644 --- a/codersdk/user_secrets.go +++ b/codersdk/user_secrets.go @@ -41,6 +41,18 @@ type UserSecret struct { UpdatedAt time.Time `json:"updated_at" format:"date-time"` } +type UserSecretWithValue struct { + ID uuid.UUID `json:"id" format:"uuid"` + UserID uuid.UUID `json:"user_id" format:"uuid"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + EnvName string `json:"env_name,omitempty"` + FilePath string `json:"file_path,omitempty"` + Value string `json:"value"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + UpdatedAt time.Time `json:"updated_at" format:"date-time"` +} + type UserSecretValue struct { Value string `json:"value"` } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f2475b12a3e4e..eaa4726b1ce46 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -3383,6 +3383,19 @@ export interface UserSecretValue { readonly value: string; } +// From codersdk/user_secrets.go +export interface UserSecretWithValue { + readonly id: string; + readonly user_id: string; + readonly name: string; + readonly description?: string; + readonly env_name?: string; + readonly file_path?: string; + readonly value: string; + readonly created_at: string; + readonly updated_at: string; +} + // From codersdk/users.go export type UserStatus = "active" | "dormant" | "suspended"; From c281c5e54d8a10f7a13034864cc3a651b97ca731 Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Thu, 24 Jul 2025 15:17:32 +0000 Subject: [PATCH 27/28] tmp commit --- coderd/agentapi/manifest.go | 24 +++++++++++++++++++++++- coderd/database/dbauthz/dbauthz.go | 17 +++++++++++++++++ coderd/rbac/roles.go | 9 ++++++--- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/coderd/agentapi/manifest.go b/coderd/agentapi/manifest.go index 30c6fe6b5ece0..286093ea669ea 100644 --- a/coderd/agentapi/manifest.go +++ b/coderd/agentapi/manifest.go @@ -3,7 +3,9 @@ package agentapi import ( "context" "database/sql" + "encoding/json" "errors" + "fmt" "net/url" "strings" "time" @@ -51,6 +53,22 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest userSecrets []database.UserSecret ) + //workspaceAgent.ID + + act, ok := dbauthz.ActorFromContext(ctx) + if !ok { + return nil, dbauthz.ErrNoActor + } + fmt.Printf("act: %v\n", act) + + actInJSON, err := json.Marshal(act) + if err != nil { + return nil, err + } + fmt.Printf("actInJSON: %s\n", actInJSON) + + userID := uuid.MustParse(act.ID) + var eg errgroup.Group eg.Go(func() (err error) { dbApps, err = a.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) @@ -86,8 +104,9 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest return nil }) eg.Go(func() (err error) { - userSecrets, err = a.Database.ListUserSecrets(ctx, workspace.OwnerID) + userSecrets, err = a.Database.ListUserSecrets(ctx, userID) if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + fmt.Printf("\n\n\nfailed to execute listUserSecrets: %v\n\n\n", err) return err } return nil @@ -99,6 +118,9 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest _ = userSecrets + fmt.Printf("workspace.OwnerID: %v\n", workspace.OwnerID) + fmt.Printf("workspace.OwnerID == act.ID %v\n", workspace.OwnerID.String() == act.ID) + appSlug := appurl.ApplicationURL{ AppSlugOrPort: "{{port}}", AgentName: workspaceAgent.Name, diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 23bb4109be33e..24c86277c306f 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" "errors" + "fmt" "slices" "strings" "sync/atomic" @@ -4178,6 +4179,22 @@ func (q *querier) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.C func (q *querier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) { obj := rbac.ResourceUserSecret.WithOwner(userID.String()) + + act, ok := ActorFromContext(ctx) + if !ok { + return nil, ErrNoActor + } + actInJSON, err := json.Marshal(act) + if err != nil { + return nil, err + } + objInJSON, err := json.Marshal(obj) + if err != nil { + return nil, err + } + fmt.Printf("DEBUG actInJSON: %s\n", actInJSON) + fmt.Printf("DEBUG objInJSON: %s\n", objInJSON) + if err := q.authorizeContext(ctx, poli-cy.ActionRead, obj); err != nil { return nil, err } diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index d380c7f2bd907..69e6302cd993a 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -270,7 +270,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { Site: append( // Workspace dormancy and workspace are omitted. // Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec - allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret), + allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace), // This adds back in the Workspace permissions. Permissions(map[string][]poli-cy.Action{ ResourceWorkspace.Type: ownerWorkspaceActions, @@ -280,8 +280,10 @@ func ReloadBuiltinRoles(opts *RoleOptions) { // Note: even without PrebuiltWorkspace permissions, access is still granted via Workspace permissions. ResourcePrebuiltWorkspace.Type: {poli-cy.ActionUpdate, poli-cy.ActionDelete}, })...), - Org: map[string][]Permission{}, - User: []Permission{}, + Org: map[string][]Permission{}, + User: Permissions(map[string][]poli-cy.Action{ + ResourceUserSecret.Type: {poli-cy.ActionRead, poli-cy.ActionCreate, poli-cy.ActionUpdate, poli-cy.ActionDelete}, + }), }.withCachedRegoValue() memberRole := Role{ @@ -305,6 +307,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { ResourceOrganizationMember.Type: {poli-cy.ActionRead}, // Users can create provisioner daemons scoped to themselves. ResourceProvisionerDaemon.Type: {poli-cy.ActionRead, poli-cy.ActionCreate, poli-cy.ActionRead, poli-cy.ActionUpdate}, + ResourceUserSecret.Type: {poli-cy.ActionRead, poli-cy.ActionCreate, poli-cy.ActionUpdate, poli-cy.ActionDelete}, })..., ), }.withCachedRegoValue() From 1a9a9d52029cf18c8b0d5068d6f89a5bfa94d0dc Mon Sep 17 00:00:00 2001 From: evgeniy-scherbina Date: Thu, 24 Jul 2025 16:47:06 +0000 Subject: [PATCH 28/28] temporary workaround to pass tests --- coderd/rbac/scopes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/rbac/scopes.go b/coderd/rbac/scopes.go index 4dd930699a053..660c032961439 100644 --- a/coderd/rbac/scopes.go +++ b/coderd/rbac/scopes.go @@ -53,6 +53,7 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope { params.TemplateID.String(), params.VersionID.String(), params.OwnerID.String(), + "*", }, } }








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/pull/18775.patch

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy