mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: add single tailnet support to pgcoord (#9351)
This commit is contained in:
@ -694,6 +694,13 @@ func (q *querier) DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) e
|
||||
return q.db.DeleteAPIKeysByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteAllTailnetClientSubscriptions(ctx context.Context, arg database.DeleteAllTailnetClientSubscriptionsParams) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteAllTailnetClientSubscriptions(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error {
|
||||
// TODO: This is not 100% correct because it omits apikey IDs.
|
||||
err := q.authorizeContext(ctx, rbac.ActionDelete,
|
||||
@ -783,6 +790,13 @@ func (q *querier) DeleteTailnetClient(ctx context.Context, arg database.DeleteTa
|
||||
return q.db.DeleteTailnetClient(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteTailnetClientSubscription(ctx context.Context, arg database.DeleteTailnetClientSubscriptionParams) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteTailnetClientSubscription(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetAPIKeyByID)(ctx, id)
|
||||
}
|
||||
@ -825,9 +839,9 @@ func (q *querier) GetAllTailnetAgents(ctx context.Context) ([]database.TailnetAg
|
||||
return q.db.GetAllTailnetAgents(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetAllTailnetClients(ctx context.Context) ([]database.TailnetClient, error) {
|
||||
func (q *querier) GetAllTailnetClients(ctx context.Context) ([]database.GetAllTailnetClientsRow, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return []database.TailnetClient{}, err
|
||||
return []database.GetAllTailnetClientsRow{}, err
|
||||
}
|
||||
return q.db.GetAllTailnetClients(ctx)
|
||||
}
|
||||
@ -2794,6 +2808,13 @@ func (q *querier) UpsertTailnetClient(ctx context.Context, arg database.UpsertTa
|
||||
return q.db.UpsertTailnetClient(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertTailnetClientSubscription(ctx context.Context, arg database.UpsertTailnetClientSubscriptionParams) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpsertTailnetClientSubscription(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertTailnetCoordinator(ctx context.Context, id uuid.UUID) (database.TailnetCoordinator, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return database.TailnetCoordinator{}, err
|
||||
|
@ -854,6 +854,15 @@ func (q *FakeQuerier) DeleteAPIKeysByUserID(_ context.Context, userID uuid.UUID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*FakeQuerier) DeleteAllTailnetClientSubscriptions(_ context.Context, arg database.DeleteAllTailnetClientSubscriptionsParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteApplicationConnectAPIKeysByUserID(_ context.Context, userID uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@ -987,6 +996,10 @@ func (*FakeQuerier) DeleteTailnetClient(context.Context, database.DeleteTailnetC
|
||||
return database.DeleteTailnetClientRow{}, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (*FakeQuerier) DeleteTailnetClientSubscription(context.Context, database.DeleteTailnetClientSubscriptionParams) error {
|
||||
return ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetAPIKeyByID(_ context.Context, id string) (database.APIKey, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@ -1102,7 +1115,7 @@ func (*FakeQuerier) GetAllTailnetAgents(_ context.Context) ([]database.TailnetAg
|
||||
return nil, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (*FakeQuerier) GetAllTailnetClients(_ context.Context) ([]database.TailnetClient, error) {
|
||||
func (*FakeQuerier) GetAllTailnetClients(_ context.Context) ([]database.GetAllTailnetClientsRow, error) {
|
||||
return nil, ErrUnimplemented
|
||||
}
|
||||
|
||||
@ -6112,6 +6125,10 @@ func (*FakeQuerier) UpsertTailnetClient(context.Context, database.UpsertTailnetC
|
||||
return database.TailnetClient{}, ErrUnimplemented
|
||||
}
|
||||
|
||||
func (*FakeQuerier) UpsertTailnetClientSubscription(context.Context, database.UpsertTailnetClientSubscriptionParams) error {
|
||||
return ErrUnimplemented
|
||||
}
|
||||
|
||||
func (*FakeQuerier) UpsertTailnetCoordinator(context.Context, uuid.UUID) (database.TailnetCoordinator, error) {
|
||||
return database.TailnetCoordinator{}, ErrUnimplemented
|
||||
}
|
||||
|
@ -128,6 +128,13 @@ func (m metricsStore) DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUI
|
||||
return err
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteAllTailnetClientSubscriptions(ctx context.Context, arg database.DeleteAllTailnetClientSubscriptionsParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteAllTailnetClientSubscriptions(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("DeleteAllTailnetClientSubscriptions").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error {
|
||||
start := time.Now()
|
||||
err := m.s.DeleteApplicationConnectAPIKeysByUserID(ctx, userID)
|
||||
@ -209,6 +216,13 @@ func (m metricsStore) DeleteTailnetClient(ctx context.Context, arg database.Dele
|
||||
return m.s.DeleteTailnetClient(ctx, arg)
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteTailnetClientSubscription(ctx context.Context, arg database.DeleteTailnetClientSubscriptionParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteTailnetClientSubscription(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("DeleteTailnetClientSubscription").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) GetAPIKeyByID(ctx context.Context, id string) (database.APIKey, error) {
|
||||
start := time.Now()
|
||||
apiKey, err := m.s.GetAPIKeyByID(ctx, id)
|
||||
@ -265,7 +279,7 @@ func (m metricsStore) GetAllTailnetAgents(ctx context.Context) ([]database.Tailn
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetAllTailnetClients(ctx context.Context) ([]database.TailnetClient, error) {
|
||||
func (m metricsStore) GetAllTailnetClients(ctx context.Context) ([]database.GetAllTailnetClientsRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAllTailnetClients(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetAllTailnetClients").Observe(time.Since(start).Seconds())
|
||||
@ -1752,6 +1766,13 @@ func (m metricsStore) UpsertTailnetClient(ctx context.Context, arg database.Upse
|
||||
return m.s.UpsertTailnetClient(ctx, arg)
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertTailnetClientSubscription(ctx context.Context, arg database.UpsertTailnetClientSubscriptionParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertTailnetClientSubscription(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpsertTailnetClientSubscription").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertTailnetCoordinator(ctx context.Context, id uuid.UUID) (database.TailnetCoordinator, error) {
|
||||
start := time.Now()
|
||||
defer m.queryLatencies.WithLabelValues("UpsertTailnetCoordinator").Observe(time.Since(start).Seconds())
|
||||
|
@ -139,6 +139,20 @@ func (mr *MockStoreMockRecorder) DeleteAPIKeysByUserID(arg0, arg1 interface{}) *
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAPIKeysByUserID", reflect.TypeOf((*MockStore)(nil).DeleteAPIKeysByUserID), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteAllTailnetClientSubscriptions mocks base method.
|
||||
func (m *MockStore) DeleteAllTailnetClientSubscriptions(arg0 context.Context, arg1 database.DeleteAllTailnetClientSubscriptionsParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAllTailnetClientSubscriptions", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAllTailnetClientSubscriptions indicates an expected call of DeleteAllTailnetClientSubscriptions.
|
||||
func (mr *MockStoreMockRecorder) DeleteAllTailnetClientSubscriptions(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllTailnetClientSubscriptions", reflect.TypeOf((*MockStore)(nil).DeleteAllTailnetClientSubscriptions), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteApplicationConnectAPIKeysByUserID mocks base method.
|
||||
func (m *MockStore) DeleteApplicationConnectAPIKeysByUserID(arg0 context.Context, arg1 uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
@ -310,6 +324,20 @@ func (mr *MockStoreMockRecorder) DeleteTailnetClient(arg0, arg1 interface{}) *go
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTailnetClient", reflect.TypeOf((*MockStore)(nil).DeleteTailnetClient), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteTailnetClientSubscription mocks base method.
|
||||
func (m *MockStore) DeleteTailnetClientSubscription(arg0 context.Context, arg1 database.DeleteTailnetClientSubscriptionParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteTailnetClientSubscription", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteTailnetClientSubscription indicates an expected call of DeleteTailnetClientSubscription.
|
||||
func (mr *MockStoreMockRecorder) DeleteTailnetClientSubscription(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTailnetClientSubscription", reflect.TypeOf((*MockStore)(nil).DeleteTailnetClientSubscription), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetAPIKeyByID mocks base method.
|
||||
func (m *MockStore) GetAPIKeyByID(arg0 context.Context, arg1 string) (database.APIKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -431,10 +459,10 @@ func (mr *MockStoreMockRecorder) GetAllTailnetAgents(arg0 interface{}) *gomock.C
|
||||
}
|
||||
|
||||
// GetAllTailnetClients mocks base method.
|
||||
func (m *MockStore) GetAllTailnetClients(arg0 context.Context) ([]database.TailnetClient, error) {
|
||||
func (m *MockStore) GetAllTailnetClients(arg0 context.Context) ([]database.GetAllTailnetClientsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAllTailnetClients", arg0)
|
||||
ret0, _ := ret[0].([]database.TailnetClient)
|
||||
ret0, _ := ret[0].([]database.GetAllTailnetClientsRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@ -3681,6 +3709,20 @@ func (mr *MockStoreMockRecorder) UpsertTailnetClient(arg0, arg1 interface{}) *go
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTailnetClient", reflect.TypeOf((*MockStore)(nil).UpsertTailnetClient), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertTailnetClientSubscription mocks base method.
|
||||
func (m *MockStore) UpsertTailnetClientSubscription(arg0 context.Context, arg1 database.UpsertTailnetClientSubscriptionParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertTailnetClientSubscription", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertTailnetClientSubscription indicates an expected call of UpsertTailnetClientSubscription.
|
||||
func (mr *MockStoreMockRecorder) UpsertTailnetClientSubscription(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTailnetClientSubscription", reflect.TypeOf((*MockStore)(nil).UpsertTailnetClientSubscription), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertTailnetCoordinator mocks base method.
|
||||
func (m *MockStore) UpsertTailnetCoordinator(arg0 context.Context, arg1 uuid.UUID) (database.TailnetCoordinator, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
70
coderd/database/dump.sql
generated
70
coderd/database/dump.sql
generated
@ -219,13 +219,57 @@ $$;
|
||||
CREATE FUNCTION tailnet_notify_client_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
var_client_id uuid;
|
||||
var_coordinator_id uuid;
|
||||
var_agent_ids uuid[];
|
||||
var_agent_id uuid;
|
||||
BEGIN
|
||||
IF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', OLD.id || ',' || OLD.agent_id);
|
||||
RETURN NULL;
|
||||
IF (NEW.id IS NOT NULL) THEN
|
||||
var_client_id = NEW.id;
|
||||
var_coordinator_id = NEW.coordinator_id;
|
||||
ELSIF (OLD.id IS NOT NULL) THEN
|
||||
var_client_id = OLD.id;
|
||||
var_coordinator_id = OLD.coordinator_id;
|
||||
END IF;
|
||||
|
||||
-- Read all agents the client is subscribed to, so we can notify them.
|
||||
SELECT
|
||||
array_agg(agent_id)
|
||||
INTO
|
||||
var_agent_ids
|
||||
FROM
|
||||
tailnet_client_subscriptions subs
|
||||
WHERE
|
||||
subs.client_id = NEW.id AND
|
||||
subs.coordinator_id = NEW.coordinator_id;
|
||||
|
||||
-- No agents to notify
|
||||
if (var_agent_ids IS NULL) THEN
|
||||
return NULL;
|
||||
END IF;
|
||||
|
||||
-- pg_notify is limited to 8k bytes, which is approximately 221 UUIDs.
|
||||
-- Instead of sending all agent ids in a single update, send one for each
|
||||
-- agent id to prevent overflow.
|
||||
FOREACH var_agent_id IN ARRAY var_agent_ids
|
||||
LOOP
|
||||
PERFORM pg_notify('tailnet_client_update', var_client_id || ',' || var_agent_id);
|
||||
END LOOP;
|
||||
|
||||
return NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION tailnet_notify_client_subscription_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', NEW.id || ',' || NEW.agent_id);
|
||||
PERFORM pg_notify('tailnet_client_update', NEW.client_id || ',' || NEW.agent_id);
|
||||
RETURN NULL;
|
||||
ELSIF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', OLD.client_id || ',' || OLD.agent_id);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
@ -495,10 +539,16 @@ CREATE TABLE tailnet_agents (
|
||||
node jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tailnet_client_subscriptions (
|
||||
client_id uuid NOT NULL,
|
||||
coordinator_id uuid NOT NULL,
|
||||
agent_id uuid NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tailnet_clients (
|
||||
id uuid NOT NULL,
|
||||
coordinator_id uuid NOT NULL,
|
||||
agent_id uuid NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
node jsonb NOT NULL
|
||||
);
|
||||
@ -1144,6 +1194,9 @@ ALTER TABLE ONLY site_configs
|
||||
ALTER TABLE ONLY tailnet_agents
|
||||
ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id);
|
||||
|
||||
ALTER TABLE ONLY tailnet_client_subscriptions
|
||||
ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id);
|
||||
|
||||
ALTER TABLE ONLY tailnet_clients
|
||||
ADD CONSTRAINT tailnet_clients_pkey PRIMARY KEY (id, coordinator_id);
|
||||
|
||||
@ -1248,8 +1301,6 @@ CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lo
|
||||
|
||||
CREATE INDEX idx_tailnet_agents_coordinator ON tailnet_agents USING btree (coordinator_id);
|
||||
|
||||
CREATE INDEX idx_tailnet_clients_agent ON tailnet_clients USING btree (agent_id);
|
||||
|
||||
CREATE INDEX idx_tailnet_clients_coordinator ON tailnet_clients USING btree (coordinator_id);
|
||||
|
||||
CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false);
|
||||
@ -1284,6 +1335,8 @@ CREATE TRIGGER tailnet_notify_agent_change AFTER INSERT OR DELETE OR UPDATE ON t
|
||||
|
||||
CREATE TRIGGER tailnet_notify_client_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_clients FOR EACH ROW EXECUTE FUNCTION tailnet_notify_client_change();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_client_subscription_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_client_subscriptions FOR EACH ROW EXECUTE FUNCTION tailnet_notify_client_subscription_change();
|
||||
|
||||
CREATE TRIGGER tailnet_notify_coordinator_heartbeat AFTER INSERT OR UPDATE ON tailnet_coordinators FOR EACH ROW EXECUTE FUNCTION tailnet_notify_coordinator_heartbeat();
|
||||
|
||||
CREATE TRIGGER trigger_insert_apikeys BEFORE INSERT ON api_keys FOR EACH ROW EXECUTE FUNCTION insert_apikey_fail_if_user_deleted();
|
||||
@ -1329,6 +1382,9 @@ ALTER TABLE ONLY provisioner_jobs
|
||||
ALTER TABLE ONLY tailnet_agents
|
||||
ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY tailnet_client_subscriptions
|
||||
ADD CONSTRAINT tailnet_client_subscriptions_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY tailnet_clients
|
||||
ADD CONSTRAINT tailnet_clients_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -19,6 +19,7 @@ const (
|
||||
ForeignKeyProvisionerJobLogsJobID ForeignKeyConstraint = "provisioner_job_logs_job_id_fkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
|
||||
ForeignKeyProvisionerJobsOrganizationID ForeignKeyConstraint = "provisioner_jobs_organization_id_fkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
ForeignKeyTailnetAgentsCoordinatorID ForeignKeyConstraint = "tailnet_agents_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||
ForeignKeyTailnetClientSubscriptionsCoordinatorID ForeignKeyConstraint = "tailnet_client_subscriptions_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||
ForeignKeyTailnetClientsCoordinatorID ForeignKeyConstraint = "tailnet_clients_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
|
||||
ForeignKeyTemplateVersionParametersTemplateVersionID ForeignKeyConstraint = "template_version_parameters_template_version_id_fkey" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
|
||||
ForeignKeyTemplateVersionVariablesTemplateVersionID ForeignKeyConstraint = "template_version_variables_template_version_id_fkey" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
|
||||
|
@ -0,0 +1,39 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE
|
||||
tailnet_clients
|
||||
ADD COLUMN
|
||||
agent_id uuid;
|
||||
|
||||
UPDATE
|
||||
tailnet_clients
|
||||
SET
|
||||
-- there's no reason for us to try and preserve data since coordinators will
|
||||
-- have to restart anyways, which will create all of the client mappings.
|
||||
agent_id = '00000000-0000-0000-0000-000000000000'::uuid;
|
||||
|
||||
ALTER TABLE
|
||||
tailnet_clients
|
||||
ALTER COLUMN
|
||||
agent_id SET NOT NULL;
|
||||
|
||||
DROP TABLE tailnet_client_subscriptions;
|
||||
DROP FUNCTION tailnet_notify_client_subscription_change;
|
||||
|
||||
-- update the tailnet_clients trigger to the old version.
|
||||
CREATE OR REPLACE FUNCTION tailnet_notify_client_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', OLD.id || ',' || OLD.agent_id);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', NEW.id || ',' || NEW.agent_id);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMIT;
|
@ -0,0 +1,88 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE tailnet_client_subscriptions (
|
||||
client_id uuid NOT NULL,
|
||||
coordinator_id uuid NOT NULL,
|
||||
-- this isn't a foreign key since it's more of a list of agents the client
|
||||
-- *wants* to connect to, and they don't necessarily have to currently
|
||||
-- exist in the db.
|
||||
agent_id uuid NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY (client_id, coordinator_id, agent_id),
|
||||
FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators (id) ON DELETE CASCADE
|
||||
-- we don't keep a foreign key to the tailnet_clients table since there's
|
||||
-- not a great way to guarantee that a subscription is always added after
|
||||
-- the client is inserted. clients are only created after the client sends
|
||||
-- its first node update, which can take an undetermined amount of time.
|
||||
);
|
||||
|
||||
CREATE FUNCTION tailnet_notify_client_subscription_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (NEW IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', NEW.client_id || ',' || NEW.agent_id);
|
||||
RETURN NULL;
|
||||
ELSIF (OLD IS NOT NULL) THEN
|
||||
PERFORM pg_notify('tailnet_client_update', OLD.client_id || ',' || OLD.agent_id);
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER tailnet_notify_client_subscription_change
|
||||
AFTER INSERT OR UPDATE OR DELETE ON tailnet_client_subscriptions
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE tailnet_notify_client_subscription_change();
|
||||
|
||||
CREATE OR REPLACE FUNCTION tailnet_notify_client_change() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
var_client_id uuid;
|
||||
var_coordinator_id uuid;
|
||||
var_agent_ids uuid[];
|
||||
var_agent_id uuid;
|
||||
BEGIN
|
||||
IF (NEW.id IS NOT NULL) THEN
|
||||
var_client_id = NEW.id;
|
||||
var_coordinator_id = NEW.coordinator_id;
|
||||
ELSIF (OLD.id IS NOT NULL) THEN
|
||||
var_client_id = OLD.id;
|
||||
var_coordinator_id = OLD.coordinator_id;
|
||||
END IF;
|
||||
|
||||
-- Read all agents the client is subscribed to, so we can notify them.
|
||||
SELECT
|
||||
array_agg(agent_id)
|
||||
INTO
|
||||
var_agent_ids
|
||||
FROM
|
||||
tailnet_client_subscriptions subs
|
||||
WHERE
|
||||
subs.client_id = NEW.id AND
|
||||
subs.coordinator_id = NEW.coordinator_id;
|
||||
|
||||
-- No agents to notify
|
||||
if (var_agent_ids IS NULL) THEN
|
||||
return NULL;
|
||||
END IF;
|
||||
|
||||
-- pg_notify is limited to 8k bytes, which is approximately 221 UUIDs.
|
||||
-- Instead of sending all agent ids in a single update, send one for each
|
||||
-- agent id to prevent overflow.
|
||||
FOREACH var_agent_id IN ARRAY var_agent_ids
|
||||
LOOP
|
||||
PERFORM pg_notify('tailnet_client_update', var_client_id || ',' || var_agent_id);
|
||||
END LOOP;
|
||||
|
||||
return NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
ALTER TABLE
|
||||
tailnet_clients
|
||||
DROP COLUMN
|
||||
agent_id;
|
||||
|
||||
COMMIT;
|
@ -18,7 +18,7 @@ VALUES
|
||||
);
|
||||
|
||||
INSERT INTO tailnet_agents
|
||||
(id, coordinator_id, updated_at, node)
|
||||
(id, coordinator_id, updated_at, node)
|
||||
VALUES
|
||||
(
|
||||
'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
|
||||
|
9
coderd/database/migrations/testdata/fixtures/000156_pg_coordinator_single_tailnet.up.sql
vendored
Normal file
9
coderd/database/migrations/testdata/fixtures/000156_pg_coordinator_single_tailnet.up.sql
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
INSERT INTO tailnet_client_subscriptions
|
||||
(client_id, agent_id, coordinator_id, updated_at)
|
||||
VALUES
|
||||
(
|
||||
'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
|
||||
'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
|
||||
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
|
||||
'2023-06-15 10:23:54+00'
|
||||
);
|
@ -1783,11 +1783,17 @@ type TailnetAgent struct {
|
||||
type TailnetClient struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CoordinatorID uuid.UUID `db:"coordinator_id" json:"coordinator_id"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Node json.RawMessage `db:"node" json:"node"`
|
||||
}
|
||||
|
||||
type TailnetClientSubscription struct {
|
||||
ClientID uuid.UUID `db:"client_id" json:"client_id"`
|
||||
CoordinatorID uuid.UUID `db:"coordinator_id" json:"coordinator_id"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// We keep this separate from replicas in case we need to break the coordinator out into its own service
|
||||
type TailnetCoordinator struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
@ -36,6 +36,7 @@ type sqlcQuerier interface {
|
||||
CleanTailnetCoordinators(ctx context.Context) error
|
||||
DeleteAPIKeyByID(ctx context.Context, id string) error
|
||||
DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteAllTailnetClientSubscriptions(ctx context.Context, arg DeleteAllTailnetClientSubscriptionsParams) error
|
||||
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteCoordinator(ctx context.Context, id uuid.UUID) error
|
||||
DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error
|
||||
@ -50,6 +51,7 @@ type sqlcQuerier interface {
|
||||
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
|
||||
DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error)
|
||||
DeleteTailnetClient(ctx context.Context, arg DeleteTailnetClientParams) (DeleteTailnetClientRow, error)
|
||||
DeleteTailnetClientSubscription(ctx context.Context, arg DeleteTailnetClientSubscriptionParams) error
|
||||
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
|
||||
// there is no unique constraint on empty token names
|
||||
GetAPIKeyByName(ctx context.Context, arg GetAPIKeyByNameParams) (APIKey, error)
|
||||
@ -59,7 +61,7 @@ type sqlcQuerier interface {
|
||||
GetActiveUserCount(ctx context.Context) (int64, error)
|
||||
GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error)
|
||||
GetAllTailnetAgents(ctx context.Context) ([]TailnetAgent, error)
|
||||
GetAllTailnetClients(ctx context.Context) ([]TailnetClient, error)
|
||||
GetAllTailnetClients(ctx context.Context) ([]GetAllTailnetClientsRow, error)
|
||||
GetAppSecurityKey(ctx context.Context) (string, error)
|
||||
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
// ID.
|
||||
@ -324,6 +326,7 @@ type sqlcQuerier interface {
|
||||
UpsertServiceBanner(ctx context.Context, value string) error
|
||||
UpsertTailnetAgent(ctx context.Context, arg UpsertTailnetAgentParams) (TailnetAgent, error)
|
||||
UpsertTailnetClient(ctx context.Context, arg UpsertTailnetClientParams) (TailnetClient, error)
|
||||
UpsertTailnetClientSubscription(ctx context.Context, arg UpsertTailnetClientSubscriptionParams) error
|
||||
UpsertTailnetCoordinator(ctx context.Context, id uuid.UUID) (TailnetCoordinator, error)
|
||||
}
|
||||
|
||||
|
@ -4131,6 +4131,22 @@ func (q *sqlQuerier) CleanTailnetCoordinators(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteAllTailnetClientSubscriptions = `-- name: DeleteAllTailnetClientSubscriptions :exec
|
||||
DELETE
|
||||
FROM tailnet_client_subscriptions
|
||||
WHERE client_id = $1 and coordinator_id = $2
|
||||
`
|
||||
|
||||
type DeleteAllTailnetClientSubscriptionsParams struct {
|
||||
ClientID uuid.UUID `db:"client_id" json:"client_id"`
|
||||
CoordinatorID uuid.UUID `db:"coordinator_id" json:"coordinator_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteAllTailnetClientSubscriptions(ctx context.Context, arg DeleteAllTailnetClientSubscriptionsParams) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteAllTailnetClientSubscriptions, arg.ClientID, arg.CoordinatorID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteCoordinator = `-- name: DeleteCoordinator :exec
|
||||
DELETE
|
||||
FROM tailnet_coordinators
|
||||
@ -4190,6 +4206,23 @@ func (q *sqlQuerier) DeleteTailnetClient(ctx context.Context, arg DeleteTailnetC
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteTailnetClientSubscription = `-- name: DeleteTailnetClientSubscription :exec
|
||||
DELETE
|
||||
FROM tailnet_client_subscriptions
|
||||
WHERE client_id = $1 and agent_id = $2 and coordinator_id = $3
|
||||
`
|
||||
|
||||
type DeleteTailnetClientSubscriptionParams struct {
|
||||
ClientID uuid.UUID `db:"client_id" json:"client_id"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
CoordinatorID uuid.UUID `db:"coordinator_id" json:"coordinator_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteTailnetClientSubscription(ctx context.Context, arg DeleteTailnetClientSubscriptionParams) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteTailnetClientSubscription, arg.ClientID, arg.AgentID, arg.CoordinatorID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getAllTailnetAgents = `-- name: GetAllTailnetAgents :many
|
||||
SELECT id, coordinator_id, updated_at, node
|
||||
FROM tailnet_agents
|
||||
@ -4224,26 +4257,32 @@ func (q *sqlQuerier) GetAllTailnetAgents(ctx context.Context) ([]TailnetAgent, e
|
||||
}
|
||||
|
||||
const getAllTailnetClients = `-- name: GetAllTailnetClients :many
|
||||
SELECT id, coordinator_id, agent_id, updated_at, node
|
||||
SELECT tailnet_clients.id, tailnet_clients.coordinator_id, tailnet_clients.updated_at, tailnet_clients.node, array_agg(tailnet_client_subscriptions.agent_id)::uuid[] as agent_ids
|
||||
FROM tailnet_clients
|
||||
ORDER BY agent_id
|
||||
LEFT JOIN tailnet_client_subscriptions
|
||||
ON tailnet_clients.id = tailnet_client_subscriptions.client_id
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetAllTailnetClients(ctx context.Context) ([]TailnetClient, error) {
|
||||
type GetAllTailnetClientsRow struct {
|
||||
TailnetClient TailnetClient `db:"tailnet_client" json:"tailnet_client"`
|
||||
AgentIds []uuid.UUID `db:"agent_ids" json:"agent_ids"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetAllTailnetClients(ctx context.Context) ([]GetAllTailnetClientsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAllTailnetClients)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []TailnetClient
|
||||
var items []GetAllTailnetClientsRow
|
||||
for rows.Next() {
|
||||
var i TailnetClient
|
||||
var i GetAllTailnetClientsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CoordinatorID,
|
||||
&i.AgentID,
|
||||
&i.UpdatedAt,
|
||||
&i.Node,
|
||||
&i.TailnetClient.ID,
|
||||
&i.TailnetClient.CoordinatorID,
|
||||
&i.TailnetClient.UpdatedAt,
|
||||
&i.TailnetClient.Node,
|
||||
pq.Array(&i.AgentIds),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -4293,9 +4332,13 @@ func (q *sqlQuerier) GetTailnetAgents(ctx context.Context, id uuid.UUID) ([]Tail
|
||||
}
|
||||
|
||||
const getTailnetClientsForAgent = `-- name: GetTailnetClientsForAgent :many
|
||||
SELECT id, coordinator_id, agent_id, updated_at, node
|
||||
SELECT id, coordinator_id, updated_at, node
|
||||
FROM tailnet_clients
|
||||
WHERE agent_id = $1
|
||||
WHERE id IN (
|
||||
SELECT tailnet_client_subscriptions.client_id
|
||||
FROM tailnet_client_subscriptions
|
||||
WHERE tailnet_client_subscriptions.agent_id = $1
|
||||
)
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetTailnetClientsForAgent(ctx context.Context, agentID uuid.UUID) ([]TailnetClient, error) {
|
||||
@ -4310,7 +4353,6 @@ func (q *sqlQuerier) GetTailnetClientsForAgent(ctx context.Context, agentID uuid
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CoordinatorID,
|
||||
&i.AgentID,
|
||||
&i.UpdatedAt,
|
||||
&i.Node,
|
||||
); err != nil {
|
||||
@ -4369,47 +4411,67 @@ INSERT INTO
|
||||
tailnet_clients (
|
||||
id,
|
||||
coordinator_id,
|
||||
agent_id,
|
||||
node,
|
||||
updated_at
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, now() at time zone 'utc')
|
||||
($1, $2, $3, now() at time zone 'utc')
|
||||
ON CONFLICT (id, coordinator_id)
|
||||
DO UPDATE SET
|
||||
id = $1,
|
||||
coordinator_id = $2,
|
||||
agent_id = $3,
|
||||
node = $4,
|
||||
node = $3,
|
||||
updated_at = now() at time zone 'utc'
|
||||
RETURNING id, coordinator_id, agent_id, updated_at, node
|
||||
RETURNING id, coordinator_id, updated_at, node
|
||||
`
|
||||
|
||||
type UpsertTailnetClientParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CoordinatorID uuid.UUID `db:"coordinator_id" json:"coordinator_id"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Node json.RawMessage `db:"node" json:"node"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpsertTailnetClient(ctx context.Context, arg UpsertTailnetClientParams) (TailnetClient, error) {
|
||||
row := q.db.QueryRowContext(ctx, upsertTailnetClient,
|
||||
arg.ID,
|
||||
arg.CoordinatorID,
|
||||
arg.AgentID,
|
||||
arg.Node,
|
||||
)
|
||||
row := q.db.QueryRowContext(ctx, upsertTailnetClient, arg.ID, arg.CoordinatorID, arg.Node)
|
||||
var i TailnetClient
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CoordinatorID,
|
||||
&i.AgentID,
|
||||
&i.UpdatedAt,
|
||||
&i.Node,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const upsertTailnetClientSubscription = `-- name: UpsertTailnetClientSubscription :exec
|
||||
INSERT INTO
|
||||
tailnet_client_subscriptions (
|
||||
client_id,
|
||||
coordinator_id,
|
||||
agent_id,
|
||||
updated_at
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, now() at time zone 'utc')
|
||||
ON CONFLICT (client_id, coordinator_id, agent_id)
|
||||
DO UPDATE SET
|
||||
client_id = $1,
|
||||
coordinator_id = $2,
|
||||
agent_id = $3,
|
||||
updated_at = now() at time zone 'utc'
|
||||
`
|
||||
|
||||
type UpsertTailnetClientSubscriptionParams struct {
|
||||
ClientID uuid.UUID `db:"client_id" json:"client_id"`
|
||||
CoordinatorID uuid.UUID `db:"coordinator_id" json:"coordinator_id"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpsertTailnetClientSubscription(ctx context.Context, arg UpsertTailnetClientSubscriptionParams) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertTailnetClientSubscription, arg.ClientID, arg.CoordinatorID, arg.AgentID)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertTailnetCoordinator = `-- name: UpsertTailnetCoordinator :one
|
||||
INSERT INTO
|
||||
tailnet_coordinators (
|
||||
|
@ -3,21 +3,36 @@ INSERT INTO
|
||||
tailnet_clients (
|
||||
id,
|
||||
coordinator_id,
|
||||
agent_id,
|
||||
node,
|
||||
updated_at
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, now() at time zone 'utc')
|
||||
($1, $2, $3, now() at time zone 'utc')
|
||||
ON CONFLICT (id, coordinator_id)
|
||||
DO UPDATE SET
|
||||
id = $1,
|
||||
coordinator_id = $2,
|
||||
agent_id = $3,
|
||||
node = $4,
|
||||
node = $3,
|
||||
updated_at = now() at time zone 'utc'
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpsertTailnetClientSubscription :exec
|
||||
INSERT INTO
|
||||
tailnet_client_subscriptions (
|
||||
client_id,
|
||||
coordinator_id,
|
||||
agent_id,
|
||||
updated_at
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, now() at time zone 'utc')
|
||||
ON CONFLICT (client_id, coordinator_id, agent_id)
|
||||
DO UPDATE SET
|
||||
client_id = $1,
|
||||
coordinator_id = $2,
|
||||
agent_id = $3,
|
||||
updated_at = now() at time zone 'utc';
|
||||
|
||||
-- name: UpsertTailnetAgent :one
|
||||
INSERT INTO
|
||||
tailnet_agents (
|
||||
@ -43,6 +58,16 @@ FROM tailnet_clients
|
||||
WHERE id = $1 and coordinator_id = $2
|
||||
RETURNING id, coordinator_id;
|
||||
|
||||
-- name: DeleteTailnetClientSubscription :exec
|
||||
DELETE
|
||||
FROM tailnet_client_subscriptions
|
||||
WHERE client_id = $1 and agent_id = $2 and coordinator_id = $3;
|
||||
|
||||
-- name: DeleteAllTailnetClientSubscriptions :exec
|
||||
DELETE
|
||||
FROM tailnet_client_subscriptions
|
||||
WHERE client_id = $1 and coordinator_id = $2;
|
||||
|
||||
-- name: DeleteTailnetAgent :one
|
||||
DELETE
|
||||
FROM tailnet_agents
|
||||
@ -66,12 +91,17 @@ FROM tailnet_agents;
|
||||
-- name: GetTailnetClientsForAgent :many
|
||||
SELECT *
|
||||
FROM tailnet_clients
|
||||
WHERE agent_id = $1;
|
||||
WHERE id IN (
|
||||
SELECT tailnet_client_subscriptions.client_id
|
||||
FROM tailnet_client_subscriptions
|
||||
WHERE tailnet_client_subscriptions.agent_id = $1
|
||||
);
|
||||
|
||||
-- name: GetAllTailnetClients :many
|
||||
SELECT *
|
||||
SELECT sqlc.embed(tailnet_clients), array_agg(tailnet_client_subscriptions.agent_id)::uuid[] as agent_ids
|
||||
FROM tailnet_clients
|
||||
ORDER BY agent_id;
|
||||
LEFT JOIN tailnet_client_subscriptions
|
||||
ON tailnet_clients.id = tailnet_client_subscriptions.client_id;
|
||||
|
||||
-- name: UpsertTailnetCoordinator :one
|
||||
INSERT INTO
|
||||
|
Reference in New Issue
Block a user