mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: include pgcoordinator schema changes in 2.33 (#24931)
Includes https://github.com/coder/coder/pull/24613 since it landed prior to the pgcoordinator migration --------- Co-authored-by: Marcin Tojek <mtojek@users.noreply.github.com>
This commit is contained in:
Generated
+8
@@ -13798,6 +13798,9 @@ const docTemplate = `{
|
||||
"enum": [
|
||||
"all",
|
||||
"application_connect",
|
||||
"ai_seat:*",
|
||||
"ai_seat:create",
|
||||
"ai_seat:read",
|
||||
"aibridge_interception:*",
|
||||
"aibridge_interception:create",
|
||||
"aibridge_interception:read",
|
||||
@@ -14007,6 +14010,9 @@ const docTemplate = `{
|
||||
"x-enum-varnames": [
|
||||
"APIKeyScopeAll",
|
||||
"APIKeyScopeApplicationConnect",
|
||||
"APIKeyScopeAiSeatAll",
|
||||
"APIKeyScopeAiSeatCreate",
|
||||
"APIKeyScopeAiSeatRead",
|
||||
"APIKeyScopeAibridgeInterceptionAll",
|
||||
"APIKeyScopeAibridgeInterceptionCreate",
|
||||
"APIKeyScopeAibridgeInterceptionRead",
|
||||
@@ -19466,6 +19472,7 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"*",
|
||||
"ai_seat",
|
||||
"aibridge_interception",
|
||||
"api_key",
|
||||
"assign_org_role",
|
||||
@@ -19512,6 +19519,7 @@ const docTemplate = `{
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ResourceWildcard",
|
||||
"ResourceAiSeat",
|
||||
"ResourceAibridgeInterception",
|
||||
"ResourceApiKey",
|
||||
"ResourceAssignOrgRole",
|
||||
|
||||
Generated
+8
@@ -12338,6 +12338,9 @@
|
||||
"enum": [
|
||||
"all",
|
||||
"application_connect",
|
||||
"ai_seat:*",
|
||||
"ai_seat:create",
|
||||
"ai_seat:read",
|
||||
"aibridge_interception:*",
|
||||
"aibridge_interception:create",
|
||||
"aibridge_interception:read",
|
||||
@@ -12547,6 +12550,9 @@
|
||||
"x-enum-varnames": [
|
||||
"APIKeyScopeAll",
|
||||
"APIKeyScopeApplicationConnect",
|
||||
"APIKeyScopeAiSeatAll",
|
||||
"APIKeyScopeAiSeatCreate",
|
||||
"APIKeyScopeAiSeatRead",
|
||||
"APIKeyScopeAibridgeInterceptionAll",
|
||||
"APIKeyScopeAibridgeInterceptionCreate",
|
||||
"APIKeyScopeAibridgeInterceptionRead",
|
||||
@@ -17808,6 +17814,7 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"*",
|
||||
"ai_seat",
|
||||
"aibridge_interception",
|
||||
"api_key",
|
||||
"assign_org_role",
|
||||
@@ -17854,6 +17861,7 @@
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ResourceWildcard",
|
||||
"ResourceAiSeat",
|
||||
"ResourceAibridgeInterception",
|
||||
"ResourceApiKey",
|
||||
"ResourceAssignOrgRole",
|
||||
|
||||
@@ -226,6 +226,7 @@ var (
|
||||
rbac.ResourceProvisionerJobs.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionCreate},
|
||||
rbac.ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
rbac.ResourceSystem.Type: {policy.WildcardSymbol},
|
||||
rbac.ResourceAiSeat.Type: {policy.ActionCreate}, // Required for UpsertAISeatState via SeatTracker.
|
||||
rbac.ResourceTemplate.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
// Unsure why provisionerd needs update and read personal
|
||||
rbac.ResourceUser.Type: {policy.ActionRead, policy.ActionReadPersonal, policy.ActionUpdatePersonal},
|
||||
@@ -596,6 +597,7 @@ var (
|
||||
DisplayName: "Usage Publisher",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceLicense.Type: {policy.ActionRead},
|
||||
rbac.ResourceAiSeat.Type: {policy.ActionRead}, // Required for GetActiveAISeatCount.
|
||||
// The usage publisher doesn't create events, just
|
||||
// reads/processes them.
|
||||
rbac.ResourceUsageEvent.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
@@ -623,7 +625,7 @@ var (
|
||||
},
|
||||
rbac.ResourceApiKey.Type: {policy.ActionRead}, // Validate API keys.
|
||||
rbac.ResourceAibridgeInterception.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceSystem.Type: {policy.ActionCreate}, // Required for UpsertAISeatState.
|
||||
rbac.ResourceAiSeat.Type: {policy.ActionCreate}, // Required for UpsertAISeatState.
|
||||
}),
|
||||
User: []rbac.Permission{},
|
||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||
@@ -1850,9 +1852,9 @@ func (q *querier) DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.U
|
||||
return q.db.DeleteAllChatQueuedMessages(ctx, chatID)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) error {
|
||||
func (q *querier) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) ([]database.DeleteAllTailnetTunnelsRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return q.db.DeleteAllTailnetTunnels(ctx, arg)
|
||||
}
|
||||
@@ -2469,7 +2471,7 @@ func (q *querier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Tim
|
||||
}
|
||||
|
||||
func (q *querier) GetActiveAISeatCount(ctx context.Context) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceLicense); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAiSeat); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.GetActiveAISeatCount(ctx)
|
||||
@@ -4227,7 +4229,7 @@ func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License,
|
||||
}
|
||||
|
||||
func (q *querier) GetUserAISeatStates(ctx context.Context, userIDs []uuid.UUID) ([]uuid.UUID, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceUser); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAiSeat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetUserAISeatStates(ctx, userIDs)
|
||||
@@ -6662,9 +6664,9 @@ func (q *querier) UpdateReplica(ctx context.Context, arg database.UpdateReplicaP
|
||||
return q.db.UpdateReplica(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg database.UpdateTailnetPeerStatusByCoordinatorParams) error {
|
||||
func (q *querier) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg database.UpdateTailnetPeerStatusByCoordinatorParams) ([]uuid.UUID, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return q.db.UpdateTailnetPeerStatusByCoordinator(ctx, arg)
|
||||
}
|
||||
@@ -7375,7 +7377,7 @@ func (q *querier) UpdateWorkspacesTTLByTemplateID(ctx context.Context, arg datab
|
||||
}
|
||||
|
||||
func (q *querier) UpsertAISeatState(ctx context.Context, arg database.UpsertAISeatStateParams) (bool, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceAiSeat); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return q.db.UpsertAISeatState(ctx, arg)
|
||||
|
||||
@@ -1848,15 +1848,18 @@ func (s *MethodTestSuite) TestProvisionerJob() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestLicense() {
|
||||
func (s *MethodTestSuite) TestAISeat() {
|
||||
s.Run("GetActiveAISeatCount", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().GetActiveAISeatCount(gomock.Any()).Return(int64(100), nil).AnyTimes()
|
||||
check.Args().Asserts(rbac.ResourceLicense, policy.ActionRead).Returns(int64(100))
|
||||
check.Args().Asserts(rbac.ResourceAiSeat, policy.ActionRead).Returns(int64(100))
|
||||
}))
|
||||
s.Run("UpsertAISeatState", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().UpsertAISeatState(gomock.Any(), gomock.Any()).Return(true, nil).AnyTimes()
|
||||
check.Args(database.UpsertAISeatStateParams{}).Asserts(rbac.ResourceSystem, policy.ActionCreate)
|
||||
check.Args(database.UpsertAISeatStateParams{}).Asserts(rbac.ResourceAiSeat, policy.ActionCreate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestLicense() {
|
||||
s.Run("GetLicenses", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
a := database.License{ID: 1}
|
||||
b := database.License{ID: 2}
|
||||
@@ -2544,7 +2547,7 @@ func (s *MethodTestSuite) TestUser() {
|
||||
ids := []uuid.UUID{a.ID, b.ID}
|
||||
seatStates := []uuid.UUID{a.ID}
|
||||
dbm.EXPECT().GetUserAISeatStates(gomock.Any(), ids).Return(seatStates, nil).AnyTimes()
|
||||
check.Args(ids).Asserts(rbac.ResourceUser, policy.ActionRead).Returns(seatStates)
|
||||
check.Args(ids).Asserts(rbac.ResourceAiSeat, policy.ActionRead).Returns(seatStates)
|
||||
}))
|
||||
s.Run("GetUserByEmailOrUsername", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
u := testutil.Fake(s.T(), faker, database.User{})
|
||||
|
||||
@@ -400,12 +400,12 @@ func (m queryMetricsStore) DeleteAllChatQueuedMessages(ctx context.Context, chat
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) error {
|
||||
func (m queryMetricsStore) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) ([]database.DeleteAllTailnetTunnelsRow, error) {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteAllTailnetTunnels(ctx, arg)
|
||||
r0, r1 := m.s.DeleteAllTailnetTunnels(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("DeleteAllTailnetTunnels").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteAllTailnetTunnels").Inc()
|
||||
return r0
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteAllWebpushSubscriptions(ctx context.Context) error {
|
||||
@@ -4776,12 +4776,12 @@ func (m queryMetricsStore) UpdateReplica(ctx context.Context, arg database.Updat
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg database.UpdateTailnetPeerStatusByCoordinatorParams) error {
|
||||
func (m queryMetricsStore) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg database.UpdateTailnetPeerStatusByCoordinatorParams) ([]uuid.UUID, error) {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateTailnetPeerStatusByCoordinator(ctx, arg)
|
||||
r0, r1 := m.s.UpdateTailnetPeerStatusByCoordinator(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateTailnetPeerStatusByCoordinator").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateTailnetPeerStatusByCoordinator").Inc()
|
||||
return r0
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateTaskPrompt(ctx context.Context, arg database.UpdateTaskPromptParams) (database.TaskTable, error) {
|
||||
|
||||
@@ -645,11 +645,12 @@ func (mr *MockStoreMockRecorder) DeleteAllChatQueuedMessages(ctx, chatID any) *g
|
||||
}
|
||||
|
||||
// DeleteAllTailnetTunnels mocks base method.
|
||||
func (m *MockStore) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) error {
|
||||
func (m *MockStore) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) ([]database.DeleteAllTailnetTunnelsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAllTailnetTunnels", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].([]database.DeleteAllTailnetTunnelsRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DeleteAllTailnetTunnels indicates an expected call of DeleteAllTailnetTunnels.
|
||||
@@ -9018,11 +9019,12 @@ func (mr *MockStoreMockRecorder) UpdateReplica(ctx, arg any) *gomock.Call {
|
||||
}
|
||||
|
||||
// UpdateTailnetPeerStatusByCoordinator mocks base method.
|
||||
func (m *MockStore) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg database.UpdateTailnetPeerStatusByCoordinatorParams) error {
|
||||
func (m *MockStore) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg database.UpdateTailnetPeerStatusByCoordinatorParams) ([]uuid.UUID, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateTailnetPeerStatusByCoordinator", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].([]uuid.UUID)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateTailnetPeerStatusByCoordinator indicates an expected call of UpdateTailnetPeerStatusByCoordinator.
|
||||
|
||||
Generated
+4
-45
@@ -220,7 +220,10 @@ CREATE TYPE api_key_scope AS ENUM (
|
||||
'chat:read',
|
||||
'chat:update',
|
||||
'chat:delete',
|
||||
'chat:*'
|
||||
'chat:*',
|
||||
'ai_seat:*',
|
||||
'ai_seat:create',
|
||||
'ai_seat:read'
|
||||
);
|
||||
|
||||
CREATE TYPE app_sharing_level AS ENUM (
|
||||
@@ -1058,44 +1061,6 @@ BEGIN
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION tailnet_notify_coordinator_heartbeat() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('tailnet_coordinator_heartbeat', NEW.id::text);
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION tailnet_notify_peer_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_peer_update', OLD.id::text);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_peer_update', NEW.id::text);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION tailnet_notify_tunnel_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_tunnel_update', NEW.src_id || ',' || NEW.dst_id);
|
||||
RETURN NULL;
|
||||
ELSIF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_tunnel_update', OLD.src_id || ',' || OLD.dst_id);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TABLE ai_seat_state (
|
||||
user_id uuid NOT NULL,
|
||||
first_used_at timestamp with time zone NOT NULL,
|
||||
@@ -4098,12 +4063,6 @@ CREATE TRIGGER remove_organization_member_custom_role BEFORE DELETE ON custom_ro
|
||||
|
||||
COMMENT ON TRIGGER remove_organization_member_custom_role ON custom_roles IS 'When a custom_role is deleted, this trigger removes the role from all organization members.';
|
||||
|
||||
CREATE TRIGGER tailnet_notify_coordinator_heartbeat AFTER INSERT OR UPDATE ON tailnet_coordinators FOR EACH ROW EXECUTE FUNCTION tailnet_notify_coordinator_heartbeat();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_peer_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_peers FOR EACH ROW EXECUTE FUNCTION tailnet_notify_peer_change();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_tunnel_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_tunnels FOR EACH ROW EXECUTE FUNCTION tailnet_notify_tunnel_change();
|
||||
|
||||
CREATE TRIGGER trigger_aggregate_usage_event AFTER INSERT ON usage_events FOR EACH ROW EXECUTE FUNCTION aggregate_usage_event();
|
||||
|
||||
CREATE TRIGGER trigger_delete_group_members_on_org_member_delete BEFORE DELETE ON organization_members FOR EACH ROW EXECUTE FUNCTION delete_group_members_on_org_member_delete();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- These enum values cannot be removed from PostgreSQL.
|
||||
-- This migration is a no-op placeholder for rollback safety.
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_seat:*';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_seat:create';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'ai_seat:read';
|
||||
@@ -0,0 +1,43 @@
|
||||
CREATE FUNCTION tailnet_notify_coordinator_heartbeat() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('tailnet_coordinator_heartbeat', NEW.id::text);
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION tailnet_notify_peer_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_peer_update', OLD.id::text);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_peer_update', NEW.id::text);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION tailnet_notify_tunnel_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_tunnel_update', NEW.src_id || ',' || NEW.dst_id);
|
||||
RETURN NULL;
|
||||
ELSIF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_tunnel_update', OLD.src_id || ',' || OLD.dst_id);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER tailnet_notify_coordinator_heartbeat AFTER INSERT OR UPDATE ON tailnet_coordinators FOR EACH ROW EXECUTE FUNCTION tailnet_notify_coordinator_heartbeat();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_peer_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_peers FOR EACH ROW EXECUTE FUNCTION tailnet_notify_peer_change();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_tunnel_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_tunnels FOR EACH ROW EXECUTE FUNCTION tailnet_notify_tunnel_change();
|
||||
@@ -0,0 +1,6 @@
|
||||
DROP TRIGGER IF EXISTS tailnet_notify_peer_change ON tailnet_peers;
|
||||
DROP TRIGGER IF EXISTS tailnet_notify_tunnel_change ON tailnet_tunnels;
|
||||
DROP TRIGGER IF EXISTS tailnet_notify_coordinator_heartbeat ON tailnet_coordinators;
|
||||
DROP FUNCTION IF EXISTS tailnet_notify_peer_change();
|
||||
DROP FUNCTION IF EXISTS tailnet_notify_tunnel_change();
|
||||
DROP FUNCTION IF EXISTS tailnet_notify_coordinator_heartbeat();
|
||||
@@ -224,6 +224,9 @@ const (
|
||||
ApiKeyScopeChatUpdate APIKeyScope = "chat:update"
|
||||
ApiKeyScopeChatDelete APIKeyScope = "chat:delete"
|
||||
ApiKeyScopeChat APIKeyScope = "chat:*"
|
||||
ApiKeyScopeAiSeat APIKeyScope = "ai_seat:*"
|
||||
ApiKeyScopeAiSeatCreate APIKeyScope = "ai_seat:create"
|
||||
ApiKeyScopeAiSeatRead APIKeyScope = "ai_seat:read"
|
||||
)
|
||||
|
||||
func (e *APIKeyScope) Scan(src interface{}) error {
|
||||
@@ -467,7 +470,10 @@ func (e APIKeyScope) Valid() bool {
|
||||
ApiKeyScopeChatRead,
|
||||
ApiKeyScopeChatUpdate,
|
||||
ApiKeyScopeChatDelete,
|
||||
ApiKeyScopeChat:
|
||||
ApiKeyScopeChat,
|
||||
ApiKeyScopeAiSeat,
|
||||
ApiKeyScopeAiSeatCreate,
|
||||
ApiKeyScopeAiSeatRead:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -680,6 +686,9 @@ func AllAPIKeyScopeValues() []APIKeyScope {
|
||||
ApiKeyScopeChatUpdate,
|
||||
ApiKeyScopeChatDelete,
|
||||
ApiKeyScopeChat,
|
||||
ApiKeyScopeAiSeat,
|
||||
ApiKeyScopeAiSeatCreate,
|
||||
ApiKeyScopeAiSeatRead,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ type sqlcQuerier interface {
|
||||
DeleteAPIKeyByID(ctx context.Context, id string) error
|
||||
DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.UUID) error
|
||||
DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) error
|
||||
DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) ([]DeleteAllTailnetTunnelsRow, error)
|
||||
// Deletes all existing webpush subscriptions.
|
||||
// This should be called when the VAPID keypair is regenerated, as the old
|
||||
// keypair will no longer be valid and all existing subscriptions will need to
|
||||
@@ -1112,7 +1112,7 @@ type sqlcQuerier interface {
|
||||
UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error
|
||||
UpdateProvisionerJobWithCompleteWithStartedAtByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteWithStartedAtByIDParams) error
|
||||
UpdateReplica(ctx context.Context, arg UpdateReplicaParams) (Replica, error)
|
||||
UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg UpdateTailnetPeerStatusByCoordinatorParams) error
|
||||
UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg UpdateTailnetPeerStatusByCoordinatorParams) ([]uuid.UUID, error)
|
||||
UpdateTaskPrompt(ctx context.Context, arg UpdateTaskPromptParams) (TaskTable, error)
|
||||
UpdateTaskWorkspaceID(ctx context.Context, arg UpdateTaskWorkspaceIDParams) (TaskTable, error)
|
||||
UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) error
|
||||
|
||||
@@ -21234,10 +21234,11 @@ func (q *sqlQuerier) CleanTailnetTunnels(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteAllTailnetTunnels = `-- name: DeleteAllTailnetTunnels :exec
|
||||
const deleteAllTailnetTunnels = `-- name: DeleteAllTailnetTunnels :many
|
||||
DELETE
|
||||
FROM tailnet_tunnels
|
||||
WHERE coordinator_id = $1 and src_id = $2
|
||||
RETURNING src_id, dst_id
|
||||
`
|
||||
|
||||
type DeleteAllTailnetTunnelsParams struct {
|
||||
@@ -21245,9 +21246,32 @@ type DeleteAllTailnetTunnelsParams struct {
|
||||
SrcID uuid.UUID `db:"src_id" json:"src_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteAllTailnetTunnels, arg.CoordinatorID, arg.SrcID)
|
||||
return err
|
||||
type DeleteAllTailnetTunnelsRow struct {
|
||||
SrcID uuid.UUID `db:"src_id" json:"src_id"`
|
||||
DstID uuid.UUID `db:"dst_id" json:"dst_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) ([]DeleteAllTailnetTunnelsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, deleteAllTailnetTunnels, arg.CoordinatorID, arg.SrcID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []DeleteAllTailnetTunnelsRow
|
||||
for rows.Next() {
|
||||
var i DeleteAllTailnetTunnelsRow
|
||||
if err := rows.Scan(&i.SrcID, &i.DstID); 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 deleteTailnetPeer = `-- name: DeleteTailnetPeer :one
|
||||
@@ -21522,13 +21546,14 @@ func (q *sqlQuerier) GetTailnetTunnelPeerIDsBatch(ctx context.Context, ids []uui
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateTailnetPeerStatusByCoordinator = `-- name: UpdateTailnetPeerStatusByCoordinator :exec
|
||||
const updateTailnetPeerStatusByCoordinator = `-- name: UpdateTailnetPeerStatusByCoordinator :many
|
||||
UPDATE
|
||||
tailnet_peers
|
||||
SET
|
||||
status = $2
|
||||
WHERE
|
||||
coordinator_id = $1
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
type UpdateTailnetPeerStatusByCoordinatorParams struct {
|
||||
@@ -21536,9 +21561,27 @@ type UpdateTailnetPeerStatusByCoordinatorParams struct {
|
||||
Status TailnetStatus `db:"status" json:"status"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg UpdateTailnetPeerStatusByCoordinatorParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateTailnetPeerStatusByCoordinator, arg.CoordinatorID, arg.Status)
|
||||
return err
|
||||
func (q *sqlQuerier) UpdateTailnetPeerStatusByCoordinator(ctx context.Context, arg UpdateTailnetPeerStatusByCoordinatorParams) ([]uuid.UUID, error) {
|
||||
rows, err := q.db.QueryContext(ctx, updateTailnetPeerStatusByCoordinator, arg.CoordinatorID, arg.Status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []uuid.UUID
|
||||
for rows.Next() {
|
||||
var id uuid.UUID
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, id)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const upsertTailnetCoordinator = `-- name: UpsertTailnetCoordinator :one
|
||||
|
||||
@@ -50,13 +50,14 @@ DO UPDATE SET
|
||||
updated_at = now() at time zone 'utc'
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateTailnetPeerStatusByCoordinator :exec
|
||||
-- name: UpdateTailnetPeerStatusByCoordinator :many
|
||||
UPDATE
|
||||
tailnet_peers
|
||||
SET
|
||||
status = $2
|
||||
WHERE
|
||||
coordinator_id = $1;
|
||||
coordinator_id = $1
|
||||
RETURNING id;
|
||||
|
||||
-- name: DeleteTailnetPeer :one
|
||||
DELETE
|
||||
@@ -91,10 +92,11 @@ FROM tailnet_tunnels
|
||||
WHERE coordinator_id = $1 and src_id = $2 and dst_id = $3
|
||||
RETURNING coordinator_id, src_id, dst_id;
|
||||
|
||||
-- name: DeleteAllTailnetTunnels :exec
|
||||
-- name: DeleteAllTailnetTunnels :many
|
||||
DELETE
|
||||
FROM tailnet_tunnels
|
||||
WHERE coordinator_id = $1 and src_id = $2;
|
||||
WHERE coordinator_id = $1 and src_id = $2
|
||||
RETURNING src_id, dst_id;
|
||||
|
||||
-- For PG Coordinator HTMLDebug
|
||||
|
||||
|
||||
@@ -15,6 +15,14 @@ var (
|
||||
Type: "*",
|
||||
}
|
||||
|
||||
// ResourceAiSeat
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: record AI seat usage
|
||||
// - "ActionRead" :: read AI seat state
|
||||
ResourceAiSeat = Object{
|
||||
Type: "ai_seat",
|
||||
}
|
||||
|
||||
// ResourceAibridgeInterception
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create aibridge interceptions & related records
|
||||
@@ -433,6 +441,7 @@ var (
|
||||
func AllResources() []Objecter {
|
||||
return []Objecter{
|
||||
ResourceWildcard,
|
||||
ResourceAiSeat,
|
||||
ResourceAibridgeInterception,
|
||||
ResourceApiKey,
|
||||
ResourceAssignOrgRole,
|
||||
|
||||
@@ -392,6 +392,12 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionCreate: "create aibridge interceptions & related records",
|
||||
},
|
||||
},
|
||||
"ai_seat": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionCreate: "record AI seat usage",
|
||||
ActionRead: "read AI seat state",
|
||||
},
|
||||
},
|
||||
"boundary_usage": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionRead: "read boundary usage statistics",
|
||||
|
||||
@@ -294,7 +294,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Workspace dormancy and workspace are omitted.
|
||||
// Workspace is specifically handled based on the opts.NoOwnerWorkspaceExec.
|
||||
// Owners cannot access other users' secrets.
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUsageEvent, ResourceBoundaryUsage),
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUserSecret, ResourceUsageEvent, ResourceBoundaryUsage, ResourceAiSeat),
|
||||
// This adds back in the Workspace permissions.
|
||||
Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspace.Type: ownerWorkspaceActions,
|
||||
@@ -322,7 +322,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
denyPermissions...,
|
||||
),
|
||||
User: append(
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceBoundaryUsage, ResourceAibridgeInterception, ResourceChat),
|
||||
allPermsExcept(ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceWorkspace, ResourceUser, ResourceOrganizationMember, ResourceBoundaryUsage, ResourceAibridgeInterception, ResourceChat, ResourceAiSeat),
|
||||
Permissions(map[string][]policy.Action{
|
||||
// Users cannot do create/update/delete on themselves, but they
|
||||
// can read their own details.
|
||||
@@ -454,7 +454,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
// Org admins should not have workspace exec perms.
|
||||
organizationID.String(): {
|
||||
Org: append(
|
||||
allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage),
|
||||
allPermsExcept(ResourceWorkspace, ResourceWorkspaceDormant, ResourcePrebuiltWorkspace, ResourceAssignRole, ResourceUserSecret, ResourceBoundaryUsage, ResourceAiSeat),
|
||||
Permissions(map[string][]policy.Action{
|
||||
ResourceWorkspace.Type: slice.Omit(ResourceWorkspace.AvailableActions(), policy.ActionApplicationConnect, policy.ActionSSH),
|
||||
ResourceWorkspaceDormant.Type: {policy.ActionRead, policy.ActionDelete, policy.ActionCreate, policy.ActionUpdate, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
|
||||
|
||||
@@ -1104,6 +1104,14 @@ func TestRolePermissions(t *testing.T) {
|
||||
false: {owner, setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "AiSeat",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead},
|
||||
Resource: rbac.ResourceAiSeat,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
false: {owner, setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ChatUsageCRU",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
|
||||
@@ -7,6 +7,8 @@ package rbac
|
||||
// declared in code, not here, to avoid duplication.
|
||||
|
||||
const (
|
||||
ScopeAiSeatCreate ScopeName = "ai_seat:create"
|
||||
ScopeAiSeatRead ScopeName = "ai_seat:read"
|
||||
ScopeAibridgeInterceptionCreate ScopeName = "aibridge_interception:create"
|
||||
ScopeAibridgeInterceptionRead ScopeName = "aibridge_interception:read"
|
||||
ScopeAibridgeInterceptionUpdate ScopeName = "aibridge_interception:update"
|
||||
@@ -171,6 +173,8 @@ func (e ScopeName) Valid() bool {
|
||||
case ScopeName("coder:all"),
|
||||
ScopeName("coder:application_connect"),
|
||||
ScopeName("no_user_data"),
|
||||
ScopeAiSeatCreate,
|
||||
ScopeAiSeatRead,
|
||||
ScopeAibridgeInterceptionCreate,
|
||||
ScopeAibridgeInterceptionRead,
|
||||
ScopeAibridgeInterceptionUpdate,
|
||||
@@ -336,6 +340,8 @@ func AllScopeNameValues() []ScopeName {
|
||||
ScopeName("coder:all"),
|
||||
ScopeName("coder:application_connect"),
|
||||
ScopeName("no_user_data"),
|
||||
ScopeAiSeatCreate,
|
||||
ScopeAiSeatRead,
|
||||
ScopeAibridgeInterceptionCreate,
|
||||
ScopeAibridgeInterceptionRead,
|
||||
ScopeAibridgeInterceptionUpdate,
|
||||
|
||||
@@ -6,6 +6,9 @@ const (
|
||||
APIKeyScopeAll APIKeyScope = "all"
|
||||
// Deprecated: use codersdk.APIKeyScopeCoderApplicationConnect instead.
|
||||
APIKeyScopeApplicationConnect APIKeyScope = "application_connect"
|
||||
APIKeyScopeAiSeatAll APIKeyScope = "ai_seat:*"
|
||||
APIKeyScopeAiSeatCreate APIKeyScope = "ai_seat:create"
|
||||
APIKeyScopeAiSeatRead APIKeyScope = "ai_seat:read"
|
||||
APIKeyScopeAibridgeInterceptionAll APIKeyScope = "aibridge_interception:*"
|
||||
APIKeyScopeAibridgeInterceptionCreate APIKeyScope = "aibridge_interception:create"
|
||||
APIKeyScopeAibridgeInterceptionRead APIKeyScope = "aibridge_interception:read"
|
||||
|
||||
@@ -5,6 +5,7 @@ type RBACResource string
|
||||
|
||||
const (
|
||||
ResourceWildcard RBACResource = "*"
|
||||
ResourceAiSeat RBACResource = "ai_seat"
|
||||
ResourceAibridgeInterception RBACResource = "aibridge_interception"
|
||||
ResourceApiKey RBACResource = "api_key"
|
||||
ResourceAssignOrgRole RBACResource = "assign_org_role"
|
||||
@@ -77,6 +78,7 @@ const (
|
||||
// said resource type.
|
||||
var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceWildcard: {},
|
||||
ResourceAiSeat: {ActionCreate, ActionRead},
|
||||
ResourceAibridgeInterception: {ActionCreate, ActionRead, ActionUpdate},
|
||||
ResourceApiKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate},
|
||||
|
||||
Generated
+20
-20
@@ -193,10 +193,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -326,10 +326,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -459,10 +459,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -554,10 +554,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -960,9 +960,9 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
Generated
+6
-6
@@ -1387,9 +1387,9 @@
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `chat:*`, `chat:create`, `chat:delete`, `chat:read`, `chat:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace:update_agent`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_dormant:update_agent`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` |
|
||||
| Value(s) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ai_seat:*`, `ai_seat:create`, `ai_seat:read`, `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `chat:*`, `chat:create`, `chat:delete`, `chat:read`, `chat:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace:update_agent`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_dormant:update_agent`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` |
|
||||
|
||||
## codersdk.AddLicenseRequest
|
||||
|
||||
@@ -8272,9 +8272,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Value(s) |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
## codersdk.RateLimitConfig
|
||||
|
||||
|
||||
Generated
+5
-5
@@ -853,11 +853,11 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| `login_type` | `github`, `oidc`, `password`, `token` |
|
||||
| `scope` | `all`, `application_connect` |
|
||||
| Property | Value(s) |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `type` | `*`, `ai_seat`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| `login_type` | `github`, `oidc`, `password`, `token` |
|
||||
| `scope` | `all`, `application_connect` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
|
||||
@@ -23,21 +23,31 @@ import (
|
||||
"github.com/coder/quartz"
|
||||
)
|
||||
|
||||
// authzSetup returns a raw DB for seeding and an RBAC-wrapped DB
|
||||
// that enforces real authorization checks.
|
||||
func authzSetup(t *testing.T) (rawDB database.Store, authzDB database.Store) {
|
||||
t.Helper()
|
||||
rawDB, _ = dbtestutil.NewDB(t)
|
||||
authz := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
||||
authzDB = dbauthz.New(rawDB, authz, slogtest.Make(t, nil), coderdtest.AccessControlStorePointer())
|
||||
return rawDB, authzDB
|
||||
}
|
||||
|
||||
func TestSeatTrackerDB(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("ActiveUserRecorded", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
rawDB, authzDB := authzSetup(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
clock := quartz.NewMock(t)
|
||||
tracker := enterpriseaiseats.New(db, testutil.Logger(t), clock, nil)
|
||||
tracker := enterpriseaiseats.New(authzDB, testutil.Logger(t), clock, nil)
|
||||
|
||||
user := dbgen.User(t, db, database.User{Status: database.UserStatusActive})
|
||||
tracker.RecordUsage(ctx, user.ID, agplaiseats.ReasonAIBridge("active user event"))
|
||||
user := dbgen.User(t, rawDB, database.User{Status: database.UserStatusActive})
|
||||
tracker.RecordUsage(dbauthz.AsAIBridged(ctx), user.ID, agplaiseats.ReasonAIBridge("active user event"))
|
||||
|
||||
count, err := db.GetActiveAISeatCount(ctx)
|
||||
count, err := rawDB.GetActiveAISeatCount(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, count)
|
||||
})
|
||||
@@ -77,17 +87,17 @@ func TestSeatTrackerDB(t *testing.T) {
|
||||
t.Run("InactiveUsersExcluded", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
rawDB, authzDB := authzSetup(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
tracker := enterpriseaiseats.New(db, testutil.Logger(t), quartz.NewMock(t), nil)
|
||||
tracker := enterpriseaiseats.New(authzDB, testutil.Logger(t), quartz.NewMock(t), nil)
|
||||
|
||||
dormantUser := dbgen.User(t, db, database.User{Status: database.UserStatusDormant})
|
||||
tracker.RecordUsage(ctx, dormantUser.ID, agplaiseats.ReasonTask("dormant user event"))
|
||||
dormantUser := dbgen.User(t, rawDB, database.User{Status: database.UserStatusDormant})
|
||||
tracker.RecordUsage(dbauthz.AsAIBridged(ctx), dormantUser.ID, agplaiseats.ReasonTask("dormant user event"))
|
||||
|
||||
suspendedUser := dbgen.User(t, db, database.User{Status: database.UserStatusSuspended})
|
||||
tracker.RecordUsage(ctx, suspendedUser.ID, agplaiseats.ReasonTask("suspended user event"))
|
||||
suspendedUser := dbgen.User(t, rawDB, database.User{Status: database.UserStatusSuspended})
|
||||
tracker.RecordUsage(dbauthz.AsAIBridged(ctx), suspendedUser.ID, agplaiseats.ReasonTask("suspended user event"))
|
||||
|
||||
count, err := db.GetActiveAISeatCount(ctx)
|
||||
count, err := rawDB.GetActiveAISeatCount(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, count)
|
||||
})
|
||||
@@ -95,23 +105,23 @@ func TestSeatTrackerDB(t *testing.T) {
|
||||
t.Run("StatusTransitions", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
rawDB, authzDB := authzSetup(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
a := audit.NewMock()
|
||||
var aI audit.Auditor = a
|
||||
var al atomic.Pointer[audit.Auditor]
|
||||
al.Store(&aI)
|
||||
|
||||
tracker := enterpriseaiseats.New(db, testutil.Logger(t), quartz.NewMock(t), &al)
|
||||
tracker := enterpriseaiseats.New(authzDB, testutil.Logger(t), quartz.NewMock(t), &al)
|
||||
|
||||
user := dbgen.User(t, db, database.User{Status: database.UserStatusActive})
|
||||
tracker.RecordUsage(ctx, user.ID, agplaiseats.ReasonAIBridge("status transition"))
|
||||
user := dbgen.User(t, rawDB, database.User{Status: database.UserStatusActive})
|
||||
tracker.RecordUsage(dbauthz.AsAIBridged(ctx), user.ID, agplaiseats.ReasonAIBridge("status transition"))
|
||||
|
||||
count, err := db.GetActiveAISeatCount(ctx)
|
||||
count, err := rawDB.GetActiveAISeatCount(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, count)
|
||||
|
||||
_, err = db.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
|
||||
_, err = rawDB.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
|
||||
ID: user.ID,
|
||||
Status: database.UserStatusDormant,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
@@ -119,11 +129,11 @@ func TestSeatTrackerDB(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err = db.GetActiveAISeatCount(ctx)
|
||||
count, err = rawDB.GetActiveAISeatCount(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, count)
|
||||
|
||||
_, err = db.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
|
||||
_, err = rawDB.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
|
||||
ID: user.ID,
|
||||
Status: database.UserStatusActive,
|
||||
UpdatedAt: dbtime.Now().Add(time.Second),
|
||||
@@ -131,11 +141,44 @@ func TestSeatTrackerDB(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err = db.GetActiveAISeatCount(ctx)
|
||||
count, err = rawDB.GetActiveAISeatCount(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, count)
|
||||
|
||||
require.Len(t, a.AuditLogs(), 1)
|
||||
require.Equal(t, database.ResourceTypeAiSeat, a.AuditLogs()[0].ResourceType)
|
||||
})
|
||||
|
||||
// Provisionerd also calls RecordUsage via SeatTracker for
|
||||
// task workspace builds.
|
||||
t.Run("AsProvisionerd", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawDB, authzDB := authzSetup(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
tracker := enterpriseaiseats.New(authzDB, testutil.Logger(t), quartz.NewMock(t), nil)
|
||||
|
||||
user := dbgen.User(t, rawDB, database.User{Status: database.UserStatusActive})
|
||||
tracker.RecordUsage(dbauthz.AsProvisionerd(ctx), user.ID, agplaiseats.ReasonTask("task build"))
|
||||
|
||||
count, err := rawDB.GetActiveAISeatCount(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, count)
|
||||
})
|
||||
|
||||
// AsUsagePublisher reads AI seat count in heartbeats.
|
||||
t.Run("AsUsagePublisher", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rawDB, authzDB := authzSetup(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
tracker := enterpriseaiseats.New(authzDB, testutil.Logger(t), quartz.NewMock(t), nil)
|
||||
|
||||
user := dbgen.User(t, rawDB, database.User{Status: database.UserStatusActive})
|
||||
tracker.RecordUsage(dbauthz.AsAIBridged(ctx), user.ID, agplaiseats.ReasonAIBridge("heartbeat test"))
|
||||
|
||||
count, err := authzDB.GetActiveAISeatCount(dbauthz.AsUsagePublisher(ctx))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, count)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,13 +5,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"cdr.dev/slog/v3/sloggers/slogtest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/usage/usagetypes"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/usage"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
@@ -77,21 +81,27 @@ func TestCron(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestAISeatsHeartbeat checks that AISeatsHeartbeat returns the
|
||||
// correct event type and count.
|
||||
// correct event type and count. It wraps a mock database with dbauthz
|
||||
// to verify that the AsUsagePublisher subject has the required
|
||||
// ResourceAiSeat.ActionRead permission.
|
||||
func TestAISeatsHeartbeat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
ctrl := gomock.NewController(t)
|
||||
db := dbmock.NewMockStore(ctrl)
|
||||
|
||||
db.EXPECT().Wrappers().Return([]string{}).AnyTimes()
|
||||
db.EXPECT().GetActiveAISeatCount(gomock.Any()).Return(int64(42), nil)
|
||||
|
||||
fn := usage.AISeatsHeartbeat(db)
|
||||
event, err := fn(ctx)
|
||||
authz := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
||||
authzDB := dbauthz.New(db, authz, slogtest.Make(t, nil), coderdtest.AccessControlStorePointer())
|
||||
|
||||
// AISeatsHeartbeat internally uses AsUsagePublisher, which must
|
||||
// have ResourceAiSeat.ActionRead to pass the dbauthz check.
|
||||
fn := usage.AISeatsHeartbeat(authzDB)
|
||||
event, err := fn(testutil.Context(t, testutil.WaitLong))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the event type and count.
|
||||
hb, ok := event.(usagetypes.HBAISeats)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, int64(42), hb.Count)
|
||||
|
||||
@@ -42,6 +42,27 @@ const (
|
||||
CloseErrUnhealthy = "coordinator unhealthy"
|
||||
)
|
||||
|
||||
func publishPeerUpdate(ctx context.Context, ps pubsub.Pubsub, logger slog.Logger, peerID uuid.UUID) {
|
||||
if err := ps.Publish(eventPeerUpdate, []byte(peerID.String())); err != nil {
|
||||
logger.Warn(ctx, "failed to publish peer update", slog.F("peer_id", peerID), slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func publishTunnelUpdate(ctx context.Context, ps pubsub.Pubsub, logger slog.Logger, srcID, dstID uuid.UUID) {
|
||||
if err := ps.Publish(eventTunnelUpdate, []byte(srcID.String()+","+dstID.String())); err != nil {
|
||||
logger.Warn(ctx, "failed to publish tunnel update",
|
||||
slog.F("src_id", srcID), slog.F("dst_id", dstID), slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func publishCoordinatorHeartbeat(ctx context.Context, ps pubsub.Pubsub, logger slog.Logger, id uuid.UUID) {
|
||||
if err := ps.Publish(EventHeartbeats, []byte(id.String())); err != nil {
|
||||
logger.Warn(ctx, "failed to publish coordinator heartbeat", slog.F("coordinator_id", id), slog.Error(err))
|
||||
} else {
|
||||
logger.Debug(ctx, "sent heartbeat", slog.F("coordinator_id", id))
|
||||
}
|
||||
}
|
||||
|
||||
// pgCoord is a postgres-backed coordinator
|
||||
//
|
||||
// ┌────────────┐
|
||||
@@ -152,11 +173,11 @@ func newPGCoordInternal(
|
||||
logger: logger,
|
||||
pubsub: ps,
|
||||
store: store,
|
||||
binder: newBinder(ctx, logger, id, store, bCh, fHB),
|
||||
binder: newBinder(ctx, logger, id, store, ps, bCh, fHB),
|
||||
bindings: bCh,
|
||||
newConnections: cCh,
|
||||
closeConnections: ccCh,
|
||||
tunneler: newTunneler(ctx, logger, id, store, sCh, fHB),
|
||||
tunneler: newTunneler(ctx, logger, id, store, ps, sCh, fHB),
|
||||
tunnelerCh: sCh,
|
||||
handshaker: newHandshaker(ctx, logger, id, ps, rfhCh, fHB),
|
||||
handshakerCh: rfhCh,
|
||||
@@ -273,6 +294,7 @@ type tunneler struct {
|
||||
logger slog.Logger
|
||||
coordinatorID uuid.UUID
|
||||
store database.Store
|
||||
pubsub pubsub.Pubsub
|
||||
updates <-chan tunnel
|
||||
|
||||
mu sync.Mutex
|
||||
@@ -286,6 +308,7 @@ func newTunneler(ctx context.Context,
|
||||
logger slog.Logger,
|
||||
id uuid.UUID,
|
||||
store database.Store,
|
||||
ps pubsub.Pubsub,
|
||||
updates <-chan tunnel,
|
||||
startWorkers <-chan struct{},
|
||||
) *tunneler {
|
||||
@@ -294,6 +317,7 @@ func newTunneler(ctx context.Context,
|
||||
logger: logger,
|
||||
coordinatorID: id,
|
||||
store: store,
|
||||
pubsub: ps,
|
||||
updates: updates,
|
||||
latest: make(map[uuid.UUID]map[uuid.UUID]tunnel),
|
||||
workQ: newWorkQ[tKey](ctx),
|
||||
@@ -396,7 +420,8 @@ func (t *tunneler) writeOne(tun tunnel) error {
|
||||
var err error
|
||||
switch {
|
||||
case tun.dst == uuid.Nil:
|
||||
err = t.store.DeleteAllTailnetTunnels(t.ctx, database.DeleteAllTailnetTunnelsParams{
|
||||
var deleted []database.DeleteAllTailnetTunnelsRow
|
||||
deleted, err = t.store.DeleteAllTailnetTunnels(t.ctx, database.DeleteAllTailnetTunnelsParams{
|
||||
SrcID: tun.src,
|
||||
CoordinatorID: t.coordinatorID,
|
||||
})
|
||||
@@ -404,6 +429,11 @@ func (t *tunneler) writeOne(tun tunnel) error {
|
||||
slog.F("src_id", tun.src),
|
||||
slog.Error(err),
|
||||
)
|
||||
if err == nil {
|
||||
for _, row := range deleted {
|
||||
publishTunnelUpdate(t.ctx, t.pubsub, t.logger, row.SrcID, row.DstID)
|
||||
}
|
||||
}
|
||||
case tun.active:
|
||||
_, err = t.store.UpsertTailnetTunnel(t.ctx, database.UpsertTailnetTunnelParams{
|
||||
CoordinatorID: t.coordinatorID,
|
||||
@@ -415,6 +445,9 @@ func (t *tunneler) writeOne(tun tunnel) error {
|
||||
slog.F("dst_id", tun.dst),
|
||||
slog.Error(err),
|
||||
)
|
||||
if err == nil {
|
||||
publishTunnelUpdate(t.ctx, t.pubsub, t.logger, tun.src, tun.dst)
|
||||
}
|
||||
case !tun.active:
|
||||
_, err = t.store.DeleteTailnetTunnel(t.ctx, database.DeleteTailnetTunnelParams{
|
||||
CoordinatorID: t.coordinatorID,
|
||||
@@ -428,7 +461,10 @@ func (t *tunneler) writeOne(tun tunnel) error {
|
||||
)
|
||||
// writeOne should be idempotent
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
return nil // No row deleted, skip publish.
|
||||
}
|
||||
if err == nil {
|
||||
publishTunnelUpdate(t.ctx, t.pubsub, t.logger, tun.src, tun.dst)
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
@@ -459,6 +495,7 @@ type binder struct {
|
||||
logger slog.Logger
|
||||
coordinatorID uuid.UUID
|
||||
store database.Store
|
||||
pubsub pubsub.Pubsub
|
||||
bindings <-chan binding
|
||||
|
||||
mu sync.Mutex
|
||||
@@ -473,6 +510,7 @@ func newBinder(ctx context.Context,
|
||||
logger slog.Logger,
|
||||
id uuid.UUID,
|
||||
store database.Store,
|
||||
ps pubsub.Pubsub,
|
||||
bindings <-chan binding,
|
||||
startWorkers <-chan struct{},
|
||||
) *binder {
|
||||
@@ -481,6 +519,7 @@ func newBinder(ctx context.Context,
|
||||
logger: logger,
|
||||
coordinatorID: id,
|
||||
store: store,
|
||||
pubsub: ps,
|
||||
bindings: bindings,
|
||||
latest: make(map[bKey]binding),
|
||||
workQ: newWorkQ[bKey](ctx),
|
||||
@@ -508,13 +547,16 @@ func newBinder(ctx context.Context,
|
||||
|
||||
ctx, cancel := context.WithTimeout(dbauthz.As(context.Background(), pgCoordSubject), time.Second*15)
|
||||
defer cancel()
|
||||
err := b.store.UpdateTailnetPeerStatusByCoordinator(ctx, database.UpdateTailnetPeerStatusByCoordinatorParams{
|
||||
peerIDs, err := b.store.UpdateTailnetPeerStatusByCoordinator(ctx, database.UpdateTailnetPeerStatusByCoordinatorParams{
|
||||
CoordinatorID: b.coordinatorID,
|
||||
Status: database.TailnetStatusLost,
|
||||
})
|
||||
if err != nil {
|
||||
b.logger.Error(b.ctx, "update peer status to lost", slog.Error(err))
|
||||
}
|
||||
for _, peerID := range peerIDs {
|
||||
publishPeerUpdate(ctx, b.pubsub, b.logger, peerID)
|
||||
}
|
||||
}()
|
||||
return b
|
||||
}
|
||||
@@ -593,6 +635,9 @@ func (b *binder) writeOne(bnd binding) error {
|
||||
slog.F("node", bnd.node),
|
||||
slog.Error(err))
|
||||
}
|
||||
if err == nil {
|
||||
publishPeerUpdate(b.ctx, b.pubsub, b.logger, uuid.UUID(bnd.bKey))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1299,9 +1344,11 @@ func (q *querier) listenReadyForHandshake(_ context.Context, msg []byte, err err
|
||||
func (q *querier) resyncPeerMappings() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
keys := make([]mKey, 0, len(q.mappers))
|
||||
for mk := range q.mappers {
|
||||
q.mappingQ.enqueue(mk)
|
||||
keys = append(keys, mk)
|
||||
}
|
||||
q.mappingQ.enqueue(keys...)
|
||||
}
|
||||
|
||||
func (q *querier) handleUpdates() {
|
||||
@@ -1710,11 +1757,17 @@ func (h *heartbeats) checkExpiry() {
|
||||
expired := false
|
||||
for id, t := range h.coordinators {
|
||||
lastHB := now.Sub(t)
|
||||
h.logger.Debug(h.ctx, "last heartbeat from coordinator", slog.F("other_coordinator_id", id), slog.F("last_heartbeat", lastHB))
|
||||
h.logger.Debug(h.ctx, "last heartbeat from coordinator",
|
||||
slog.F("other_coordinator_id", id),
|
||||
slog.F("last_heartbeat", lastHB),
|
||||
)
|
||||
if lastHB >= MissedHeartbeats*HeartbeatPeriod {
|
||||
expired = true
|
||||
delete(h.coordinators, id)
|
||||
h.logger.Info(h.ctx, "coordinator failed heartbeat check", slog.F("other_coordinator_id", id), slog.F("last_heartbeat", lastHB))
|
||||
h.logger.Info(h.ctx, "coordinator failed heartbeat check",
|
||||
slog.F("other_coordinator_id", id),
|
||||
slog.F("last_heartbeat", lastHB),
|
||||
)
|
||||
}
|
||||
}
|
||||
if expired {
|
||||
@@ -1754,7 +1807,7 @@ func (h *heartbeats) sendBeat() {
|
||||
}
|
||||
return
|
||||
}
|
||||
h.logger.Debug(h.ctx, "sent heartbeat")
|
||||
publishCoordinatorHeartbeat(h.ctx, h.pubsub, h.logger, h.self)
|
||||
if h.failedHeartbeats >= 3 {
|
||||
h.logger.Info(h.ctx, "coordinator sent heartbeat and is healthy")
|
||||
_ = agpl.SendCtx(h.ctx, h.update, hbUpdate{health: healthUpdateHealthy})
|
||||
|
||||
@@ -76,6 +76,8 @@ func TestHeartbeats_recvBeat_resetSkew(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
logger := testutil.Logger(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
mStore := dbmock.NewMockStore(ctrl)
|
||||
mClock := quartz.NewMock(t)
|
||||
trap := mClock.Trap().Until("heartbeats", "resetExpiryTimerWithLock")
|
||||
defer trap.Close()
|
||||
@@ -83,12 +85,12 @@ func TestHeartbeats_recvBeat_resetSkew(t *testing.T) {
|
||||
uut := heartbeats{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
store: mStore,
|
||||
clock: mClock,
|
||||
self: uuid.UUID{1},
|
||||
update: make(chan hbUpdate, 4),
|
||||
coordinators: make(map[uuid.UUID]time.Time),
|
||||
}
|
||||
|
||||
coord2 := uuid.UUID{2}
|
||||
coord3 := uuid.UUID{3}
|
||||
|
||||
@@ -397,7 +399,7 @@ func TestPGCoordinatorUnhealthy(t *testing.T) {
|
||||
mStore.EXPECT().CleanTailnetCoordinators(gomock.Any()).AnyTimes().Return(nil)
|
||||
mStore.EXPECT().CleanTailnetLostPeers(gomock.Any()).AnyTimes().Return(nil)
|
||||
mStore.EXPECT().CleanTailnetTunnels(gomock.Any()).AnyTimes().Return(nil)
|
||||
mStore.EXPECT().UpdateTailnetPeerStatusByCoordinator(gomock.Any(), gomock.Any())
|
||||
mStore.EXPECT().UpdateTailnetPeerStatusByCoordinator(gomock.Any(), gomock.Any()).Return(nil, nil)
|
||||
|
||||
coordinator, err := newPGCoordInternal(ctx, logger, ps, mStore, mClock)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -268,6 +268,7 @@ func TestPGCoordinatorSingle_MissedHeartbeats(t *testing.T) {
|
||||
ctx: ctx,
|
||||
t: t,
|
||||
store: store,
|
||||
ps: ps,
|
||||
id: uuid.New(),
|
||||
}
|
||||
|
||||
@@ -281,6 +282,7 @@ func TestPGCoordinatorSingle_MissedHeartbeats(t *testing.T) {
|
||||
ctx: ctx,
|
||||
t: t,
|
||||
store: store,
|
||||
ps: ps,
|
||||
id: uuid.New(),
|
||||
}
|
||||
fCoord3.heartbeat()
|
||||
@@ -304,7 +306,6 @@ func TestPGCoordinatorSingle_MissedHeartbeats(t *testing.T) {
|
||||
// one more heartbeat period will result in fCoord2 being expired, which should cause us to
|
||||
// revert to the original agent mapping
|
||||
mClock.Advance(tailnet.HeartbeatPeriod).MustWait(ctx)
|
||||
// note that the timeout doesn't get reset because both fCoord2 and fCoord3 are expired
|
||||
client.AssertEventuallyHasDERP(agent.ID, 10)
|
||||
|
||||
// send fCoord3 heartbeat, which should trigger us to consider that mapping valid again.
|
||||
@@ -343,6 +344,7 @@ func TestPGCoordinatorSingle_MissedHeartbeats_NoDrop(t *testing.T) {
|
||||
ctx: ctx,
|
||||
t: t,
|
||||
store: store,
|
||||
ps: ps,
|
||||
id: uuid.New(),
|
||||
}
|
||||
// simulate a single heartbeat, the coordinator is healthy
|
||||
@@ -594,7 +596,7 @@ func TestPGCoordinator_Unhealthy(t *testing.T) {
|
||||
mStore.EXPECT().GetTailnetTunnelPeerBindingsBatch(gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)
|
||||
mStore.EXPECT().DeleteTailnetPeer(gomock.Any(), gomock.Any()).
|
||||
AnyTimes().Return(database.DeleteTailnetPeerRow{}, nil)
|
||||
mStore.EXPECT().DeleteAllTailnetTunnels(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
|
||||
mStore.EXPECT().DeleteAllTailnetTunnels(gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)
|
||||
mStore.EXPECT().UpdateTailnetPeerStatusByCoordinator(gomock.Any(), gomock.Any())
|
||||
|
||||
uut, err := tailnet.NewPGCoord(ctx, logger, ps, mStore)
|
||||
@@ -948,6 +950,7 @@ type fakeCoordinator struct {
|
||||
ctx context.Context
|
||||
t *testing.T
|
||||
store database.Store
|
||||
ps pubsub.Pubsub
|
||||
id uuid.UUID
|
||||
}
|
||||
|
||||
@@ -955,6 +958,8 @@ func (c *fakeCoordinator) heartbeat() {
|
||||
c.t.Helper()
|
||||
_, err := c.store.UpsertTailnetCoordinator(c.ctx, c.id)
|
||||
require.NoError(c.t, err)
|
||||
err = c.ps.Publish(tailnet.EventHeartbeats, []byte(c.id.String()))
|
||||
require.NoError(c.t, err)
|
||||
}
|
||||
|
||||
func (c *fakeCoordinator) agentNode(agentID uuid.UUID, node *agpl.Node) {
|
||||
@@ -970,4 +975,6 @@ func (c *fakeCoordinator) agentNode(agentID uuid.UUID, node *agpl.Node) {
|
||||
Status: database.TailnetStatusOk,
|
||||
})
|
||||
require.NoError(c.t, err)
|
||||
err = c.ps.Publish("tailnet_peer_update", []byte(agentID.String()))
|
||||
require.NoError(c.t, err)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import type { RBACAction, RBACResource } from "./typesGenerated";
|
||||
export const RBACResourceActions: Partial<
|
||||
Record<RBACResource, Partial<Record<RBACAction, string>>>
|
||||
> = {
|
||||
ai_seat: {
|
||||
create: "record AI seat usage",
|
||||
read: "read AI seat state",
|
||||
},
|
||||
aibridge_interception: {
|
||||
create: "create aibridge interceptions & related records",
|
||||
read: "read aibridge interceptions & related records",
|
||||
|
||||
Generated
+8
@@ -339,6 +339,9 @@ export interface APIKey {
|
||||
|
||||
// From codersdk/apikey.go
|
||||
export type APIKeyScope =
|
||||
| "ai_seat:*"
|
||||
| "ai_seat:create"
|
||||
| "ai_seat:read"
|
||||
| "aibridge_interception:*"
|
||||
| "aibridge_interception:create"
|
||||
| "aibridge_interception:read"
|
||||
@@ -548,6 +551,9 @@ export type APIKeyScope =
|
||||
| "workspace:update_agent";
|
||||
|
||||
export const APIKeyScopes: APIKeyScope[] = [
|
||||
"ai_seat:*",
|
||||
"ai_seat:create",
|
||||
"ai_seat:read",
|
||||
"aibridge_interception:*",
|
||||
"aibridge_interception:create",
|
||||
"aibridge_interception:read",
|
||||
@@ -6131,6 +6137,7 @@ export const RBACActions: RBACAction[] = [
|
||||
|
||||
// From codersdk/rbacresources_gen.go
|
||||
export type RBACResource =
|
||||
| "ai_seat"
|
||||
| "aibridge_interception"
|
||||
| "api_key"
|
||||
| "assign_org_role"
|
||||
@@ -6177,6 +6184,7 @@ export type RBACResource =
|
||||
| "workspace_proxy";
|
||||
|
||||
export const RBACResources: RBACResource[] = [
|
||||
"ai_seat",
|
||||
"aibridge_interception",
|
||||
"api_key",
|
||||
"assign_org_role",
|
||||
|
||||
Reference in New Issue
Block a user