Skip to content

Commit 9760a2f

Browse files
authored
Merge pull request #8450 from ProcessMaker/multitenancy-updates
Multitenancy updates
2 parents c06851d + d521413 commit 9760a2f

File tree

4 files changed

+72
-26
lines changed

4 files changed

+72
-26
lines changed

ProcessMaker/Console/Commands/TenantsCreate.php

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Encryption\Encrypter;
7-
use Illuminate\Support\Env;
87
use Illuminate\Support\Facades\Artisan;
98
use Illuminate\Support\Facades\Crypt;
109
use Illuminate\Support\Facades\DB;
@@ -32,13 +31,6 @@ class TenantsCreate extends Command
3231
*/
3332
public function handle()
3433
{
35-
// Datbase name should be .env DB_DATABASE + -tenant-<id>
36-
// For example pm4_ci-003c0e7501-tenant-1
37-
38-
// For CI Instnaces, Domain is https://t1.ci-003c0e7501.engk8s.processmaker.net
39-
40-
// In prod, domain won't change
41-
4234
$requiredOptions = ['name', 'database', 'url'];
4335

4436
foreach ($requiredOptions as $option) {
@@ -99,6 +91,12 @@ public function handle()
9991
$this->info('Moving ' . $subfolder . ' to ' . $tenantStoragePath);
10092
rename($subfolder, $tenantStoragePath . '/' . basename($subfolder));
10193
}
94+
95+
// Move all files from the root storage folder to the tenant storage folder
96+
foreach (File::files($storageFolderOption) as $file) {
97+
$this->info('Moving ' . $file . ' to ' . $tenantStoragePath);
98+
rename($file, $tenantStoragePath . '/' . basename($file));
99+
}
102100
} else {
103101
$this->error('Storage folder does not exist: ' . $storageFolderOption);
104102

@@ -184,7 +182,7 @@ public function handle()
184182
$this->line('- Seed the database');
185183
$this->line('- Run the install command for each package');
186184
$this->line('- Run artisan upgrade');
187-
$this->line('- Generate passport keys with artisan passport:install');
185+
$this->line('- Generate passport keys with artisan passport:keys');
188186
$this->info("For example, `TENANT={$tenant->id} php artisan migrate:fresh --seed`");
189187
}
190188

ProcessMaker/Multitenancy/SwitchTenant.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function makeCurrent(IsTenant $tenant): void
4949
// the worker queue jobs since it reuses the same process.
5050
self::$originalConfig = self::$originalConfig ?? [];
5151
self::$originalConfig[$tenant->id] = self::$originalConfig[$tenant->id] ?? [
52-
'host' => parse_url(config('app.url'), PHP_URL_HOST),
52+
'app.url' => config('app.url'),
5353
'cache.stores.cache_settings.prefix' => config('cache.stores.cache_settings.prefix'),
5454
'app.instance' => config('app.instance') ?? config('database.connections.landlord.database'),
5555
'script-runner-microservice.callback' => config('script-runner-microservice.callback'),
@@ -73,10 +73,27 @@ public function makeCurrent(IsTenant $tenant): void
7373
'root' => storage_path('lang'),
7474
],
7575
'l5-swagger.defaults.paths.docs' => storage_path('api-docs'),
76-
'cache.stores.cache_settings.prefix' => 'tenant_id_' . $tenant->id . ':' . self::$originalConfig[$tenant->id]['cache.stores.cache_settings.prefix'],
7776
'app.instance' => self::$originalConfig[$tenant->id]['app.instance'] . '_' . $tenant->id,
78-
'script-runner-microservice.callback' => str_replace(self::$originalConfig[$tenant->id]['host'], $tenant->domain, self::$originalConfig[$tenant->id]['script-runner-microservice.callback']),
7977
];
78+
79+
if (!isset($tenant->config['cache.stores.cache_settings.prefix'])) {
80+
$newConfig['cache.stores.cache_settings.prefix'] =
81+
'tenant_id_' . $tenant->id . ':' . self::$originalConfig[$tenant->id]['cache.stores.cache_settings.prefix'];
82+
}
83+
84+
if (!isset($tenant->config['script-runner-microservice.callback'])) {
85+
$newConfig['script-runner-microservice.callback'] = str_replace(
86+
self::$originalConfig[$tenant->id]['app.url'],
87+
$tenant->config['app.url'],
88+
self::$originalConfig[$tenant->id]['script-runner-microservice.callback']
89+
);
90+
}
91+
92+
if (!isset($tenant->config['app.docker_host_url'])) {
93+
// There is no specific override in the tenant's config so set it to the app url
94+
$newConfig['app.docker_host_url'] = $tenant->config['app.url'];
95+
}
96+
8097
config($newConfig);
8198

8299
// Set config from the entry in the tenants table

ProcessMaker/Providers/ProcessMakerServiceProvider.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
1010
use Illuminate\Notifications\Events\BroadcastNotificationCreated;
1111
use Illuminate\Notifications\Events\NotificationSent;
12+
use Illuminate\Support\Arr;
1213
use Illuminate\Support\Env;
1314
use Illuminate\Support\Facades;
1415
use Illuminate\Support\Facades\DB;
@@ -43,6 +44,7 @@
4344
use RuntimeException;
4445
use Spatie\Multitenancy\Events\MadeTenantCurrentEvent;
4546
use Spatie\Multitenancy\Events\TenantNotFoundForRequestEvent;
47+
use Spatie\Multitenancy\TenantCollection;
4648

4749
/**
4850
* Provide our ProcessMaker specific services.
@@ -93,26 +95,51 @@ public function boot(): void
9395
// This sets up individual supervisors for each tenant so that one tenant does not block
9496
// the queue for another tenant. This must be done here instead of SwitchTenant.php because
9597
// there is a single horizon instance for all tenants.
96-
if ($this->app->runningInConsole() && config('app.multitenancy')) {
97-
$tenantIds = Tenant::all()->pluck('id')->toArray();
98+
if ($this->app->runningInConsole() && config('app.multitenancy') && $this->horizonTenantsNotSet()) {
99+
$tenants = Tenant::all();
98100
$config = config('horizon.environments');
99-
$config = $this->addTenantSupervisors($config, $tenantIds);
101+
$config = $this->addTenantSupervisors($config, $tenants);
100102
config(['horizon.environments' => $config]);
101103
}
102104
}
103105

104-
private function addTenantSupervisors(array $config, array $tenantIds): array
106+
private function horizonTenantsNotSet(): bool
105107
{
108+
$firstKey = array_keys(config('horizon.environments.production'))[0];
109+
if (str_starts_with($firstKey, 'tenant')) {
110+
// Already cached
111+
return false;
112+
}
113+
114+
return true;
115+
}
116+
117+
private function addTenantSupervisors(array $config, TenantCollection $tenants): array
118+
{
119+
$tenantsConfigById = $tenants->mapWithKeys(function ($tenant) {
120+
return [$tenant->id => $tenant->config];
121+
});
122+
106123
foreach ($config as $env => &$supervisors) {
107124
$newSupervisors = [];
108125

109126
foreach ($supervisors as $supervisorName => $settings) {
110-
foreach ($tenantIds as $tenantId) {
127+
foreach ($tenants as $tenant) {
128+
$tenantId = $tenant->id;
111129
$tenantSupervisorName = "tenant-{$tenantId}-{$supervisorName}";
112130

113131
// Copy original settings
114132
$tenantSettings = $settings;
115133

134+
// Set tenant-specific settings
135+
$tenantConfig = $tenantsConfigById[$tenantId];
136+
foreach (['balance', 'tries', 'timeout', 'minProcesses', 'maxProcesses'] as $key) {
137+
$value = Arr::get($tenantConfig, "horizon.{$supervisorName}.{$key}", null);
138+
if ($value !== null) {
139+
$tenantSettings[$key] = $value;
140+
}
141+
}
142+
116143
// Prepend tenant ID to each queue
117144
if (isset($tenantSettings['queue']) && is_array($tenantSettings['queue'])) {
118145
$tenantSettings['queue'] = array_map(function ($queue) use ($tenantId) {

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -530,15 +530,13 @@ You can provide an optional description, for example `Metrics::gauge('active_tas
530530

531531
Go to Grafana and import the dashboards from the `resources/grafana` folder. Each JSON file represents a configured dashboard that can be imported into Grafana to visualize metrics and data.
532532

533-
# Multitenancy (Alpha)
533+
# Multitenancy
534534

535-
**Note that this feature is disabled for clients. It's not fully implemented yet.**
536-
537-
ProcessMaker is now set up as a multitenant application. By default, there is one tenant.
535+
ProcessMaker can now be set up as a multitenant application.
538536

539537
## Requirements
540538

541-
- Your MySql user `DB_USERNAME` will need to be able to create databases.
539+
- Create an empty datbase named `landlord`. Your `DB_USERNAME` should have permission to write to this table.
542540

543541
## Transition your dev instnace to multitenancy
544542

@@ -547,10 +545,15 @@ ProcessMaker is now set up as a multitenant application. By default, there is on
547545
php artisan tenants:enable --migrate
548546
```
549547
This command will
550-
- Create the landlord database
551548
- Set your existing database as the tenant database
552-
- Move your existing storage folder to `storage/tenant_1`
553-
- Update your .env DB_DATABASE to the new landlord database name
549+
- Move your existing `storage` folder to `storage/tenant_1`
550+
- Move your existing `resources/lang` folder to `resources/lang/tenant_1`
551+
- Enable multitenancy in your .env
552+
553+
## Using `valet share` for the script microservice
554+
555+
In the landlord tenant's table you will need to set the domain to the ngroc domain (without https://) and, in the config, column
556+
you will need to set the `app.url` to the ngroc domain (including the https://)
554557

555558
## Add another tenant
556559

@@ -593,9 +596,10 @@ Create a folder `storage/transitions` if it doesn't exist.
593596

594597
In that folder create a new folder for each instance you want to migrate.
595598

596-
The folder should contain exactly 2 things:
599+
The folder should contain exactly 3 things:
597600
- the .env from the instance you want to migrate
598601
- the storage folder from the instance you want to migrate, named `storage`
602+
- the resources/lang folder, named `lang`
599603

600604
Run the following command to migrate the instance(s) to a tenant:
601605
```

0 commit comments

Comments
 (0)