From 1cd78e5f0c7a9ac1ac46e0b99246e127d0e786b3 Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Oct 2026 15:29:48 +1100 Subject: [PATCH 1/2] feat: PPT-526 seed the management partner and own the placeholder estate start creates the management partner and its staff organisation and attaches the first domain to it, so the seeded admin has cluster reach once tenancy enforcement is on. Placeholder rows are created owned by that organisation. Builds and clones migrations from the models tenancy branch until models #332 merges. --- Dockerfile | 3 ++- shard.lock | 6 ++--- shard.override.yml | 5 +++++ src/sam.cr | 4 +++- src/tasks/entities.cr | 44 +++++++++++++++++++++++++++++++++++-- src/tasks/initialization.cr | 3 ++- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0708a3a..ffeb5c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,7 +81,8 @@ RUN for binary in /app/bin/* /usr/bin/pg_dump /usr/bin/pg_restore /usr/bin/psql; xargs -I % sh -c 'mkdir -p $(dirname deps%); cp % deps%;'; \ done -RUN git clone https://github.com/PlaceOS/models +# PPT-526: the tenancy migrations live on this branch until models #332 merges +RUN git clone --branch PPT-526-partner-client https://github.com/PlaceOS/models # Create tmp directory with proper permissions RUN rm -rf /tmp && mkdir -p /tmp && chmod 1777 /tmp diff --git a/shard.lock b/shard.lock index b9a0d43..e2a23ee 100644 --- a/shard.lock +++ b/shard.lock @@ -147,15 +147,15 @@ shards: place_calendar: git: https://github.com/placeos/calendar.git - version: 4.30.0 + version: 4.30.1 placeos-log-backend: git: https://github.com/place-labs/log-backend.git version: 0.13.0 - placeos-models: + placeos-models: # Overridden git: https://github.com/placeos/models.git - version: 9.118.1 + version: 9.118.1+git.commit.c7fac7913312531a3df1ed5ada1b7031e3661135 sam: git: https://github.com/imdrasil/sam.cr.git diff --git a/shard.override.yml b/shard.override.yml index 9c17593..a8bb7aa 100644 --- a/shard.override.yml +++ b/shard.override.yml @@ -1,4 +1,9 @@ dependencies: + # PPT-526: build against the multi-tenancy data layer until models #332 merges + placeos-models: + github: placeos/models + branch: PPT-526-partner-client + retriable: github: Sija/retriable.cr db: diff --git a/src/sam.cr b/src/sam.cr index 250055e..44edd68 100644 --- a/src/sam.cr +++ b/src/sam.cr @@ -261,7 +261,9 @@ namespace "create" do desc "Creates a representative set of documents in PostgreSQL DB" task "placeholders" do - PlaceOS::Tasks.create_placeholders + # placeholders belong to the staff organisation when one exists + staff = PlaceOS::Model::Organisation.where(partner_staff: true).first? + PlaceOS::Tasks.create_placeholders(staff.try(&.id)) end desc "Creates an authority" diff --git a/src/tasks/entities.cr b/src/tasks/entities.cr index 6f46dc1..b222b5f 100644 --- a/src/tasks/entities.cr +++ b/src/tasks/entities.cr @@ -35,6 +35,41 @@ module PlaceOS::Tasks::Entities raise e end + # The platform operator's partner and staff organisation. The first domain + # belongs to that organisation, so its admins have cluster reach once + # tenancy enforcement is on. + def create_management_organisation( + authority : Model::Authority, + partner_name : String = "PlaceOS", + ) : Model::Organisation + partner = upsert_document(Model::Partner.where(management: true)) do + Log.info { {message: "creating management Partner", name: partner_name} } + Model::Partner.new(name: partner_name, description: "Platform operator (management partner)").tap do |new_partner| + new_partner.management = true + end + end + + organisation = upsert_document(Model::Organisation.where(partner_id: partner.id, partner_staff: true)) do + Log.info { {message: "creating staff Organisation", name: authority.name} } + Model::Organisation.new(name: "#{partner_name} staff", description: "Staff of the management partner").tap do |new_org| + new_org.partner_id = partner.id + new_org.partner_staff = true + new_org.payer = Model::Organisation::PAYER_ORGANISATION + end + end + + if authority.organisation_id.nil? + authority.organisation_id = organisation.id + authority.save! + Log.info { {message: "attached Authority to staff Organisation", authority: authority.id, organisation: organisation.id.to_s} } + end + + organisation + rescue e + log_fail("Organisation", e) + raise e + end + def create_interface( name : String, folder_name : String, @@ -141,7 +176,7 @@ module PlaceOS::Tasks::Entities raise e end - def create_placeholders + def create_placeholders(organisation_id : UUID? = nil) version = UUID.random.to_s.split('-').first private_repository_uri = "https://github.com/placeos/private-drivers" @@ -201,6 +236,7 @@ module PlaceOS::Tasks::Entities Model::Zone.new.tap do |zone| zone.name = "Zone-#{tag}-#{version}" zone.tags = Set{tag} + zone.organisation_id = organisation_id end end end @@ -213,7 +249,9 @@ module PlaceOS::Tasks::Entities zones[2].save! control_system = upsert_document(Model::ControlSystem.all) do - Model::ControlSystem.new(name: "System-#{version}") + Model::ControlSystem.new(name: "System-#{version}").tap do |sys| + sys.organisation_id = organisation_id + end end upsert_document(Model::Settings.for_parent(control_system.id.as(String))) do @@ -227,6 +265,7 @@ module PlaceOS::Tasks::Entities mod = upsert_document(Model::Module.where(driver_id: driver.id.as(String), control_system_id: control_system.id.as(String))) do Model::Generator.module(driver: driver, control_system: control_system).tap do |new_module| new_module.custom_name = "Module-#{version}" + new_module.organisation_id = organisation_id end end @@ -241,6 +280,7 @@ module PlaceOS::Tasks::Entities trigger_description = "An automatically generated Trigger." new_trigger = Model::Trigger.new(name: trigger_name, description: trigger_description) new_trigger.control_system = control_system + new_trigger.organisation_id = organisation_id new_trigger end diff --git a/src/tasks/initialization.cr b/src/tasks/initialization.cr index ad2199e..0bbf5e8 100644 --- a/src/tasks/initialization.cr +++ b/src/tasks/initialization.cr @@ -33,6 +33,7 @@ module PlaceOS::Tasks::Initialization start_lock.synchronize do authority = Entities.create_authority(name: application_base, domain: application_base, config: metrics_config) + organisation = Entities.create_management_organisation(authority) Entities.create_user(authority: authority, name: username, email: email, password: password, sys_admin: true) Entities.create_application(authority: authority, name: application_name, base: application_base) @@ -53,7 +54,7 @@ module PlaceOS::Tasks::Initialization unless PlaceOS::Tasks.production? || PlaceOS::SKIP_PLACEHOLDERS Log.info { "creating placeholder documents" } - Entities.create_placeholders + Entities.create_placeholders(organisation.id) end end end From d3ddb20fb7a7069a01ba5fd65d76769e26b2eb3e Mon Sep 17 00:00:00 2001 From: Cameron Reeves Date: Wed, 7 Oct 2026 15:47:27 +1100 Subject: [PATCH 2/2] chore: PPT-526 track the models tenancy branch at e21d790 --- shard.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.lock b/shard.lock index e2a23ee..33346a7 100644 --- a/shard.lock +++ b/shard.lock @@ -155,7 +155,7 @@ shards: placeos-models: # Overridden git: https://github.com/placeos/models.git - version: 9.118.1+git.commit.c7fac7913312531a3df1ed5ada1b7031e3661135 + version: 9.118.1+git.commit.e21d7904b1d9fe12b7852e9e835a50489647e5f6 sam: git: https://github.com/imdrasil/sam.cr.git