Skip to content

Commit b1fd347

Browse files
committed
Update tenants transition command
1 parent f08b0f0 commit b1fd347

File tree

4 files changed

+97
-89
lines changed

4 files changed

+97
-89
lines changed

ProcessMaker/Console/Commands/TenantsCreate.php

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ class TenantsCreate extends Command
1818
*
1919
* @var string
2020
*/
21-
protected $signature = 'tenants:create {--name=} {--url=} {--database=} {--username=} {--password=} {--storage-folder=} {--lang-folder=} {--app-key=}';
21+
protected $signature = 'tenants:create
22+
{--name=}
23+
{--url=}
24+
{--database=}
25+
{--username=}
26+
{--password=}
27+
{--storage-folder=}
28+
{--lang-folder=}
29+
{--app-key=}
30+
{--skip-initialize-storage-folder}
31+
{--skip-setup-notifications}';
2232

2333
/**
2434
* The console command description.
@@ -41,7 +51,7 @@ public function handle()
4151
};
4252

4353
// Check if rsync exists
44-
Process::run('which rsync', $infoCallback)->throw();
54+
Process::run('which rsync')->throw();
4555

4656
$requiredOptions = ['name', 'database', 'url'];
4757

@@ -115,7 +125,6 @@ public function handle()
115125

116126
return 1;
117127
} else {
118-
// rename($langFolderOption, $tenantLangPath);
119128
$cmd = "rsync -avz --exclude='tenant_*' " . $langFolderOption . '/ ' . $tenantLangPath;
120129
Process::run($cmd, $infoCallback)->throw();
121130
}
@@ -131,13 +140,6 @@ public function handle()
131140
}
132141
}
133142

134-
$cmd = sprintf(
135-
"rsync -azv --ignore-existing --exclude='tenant_*' %s %s",
136-
escapeshellarg($sourceLangPath . '/'),
137-
escapeshellarg($tenantLangPath)
138-
);
139-
Process::run($cmd, $infoCallback)->throw();
140-
141143
$subfolders = [
142144
'app',
143145
'app/private',
@@ -159,9 +161,11 @@ public function handle()
159161
'api-docs',
160162
];
161163

162-
foreach ($subfolders as $subfolder) {
163-
if (!File::isDirectory($tenantStoragePath . '/' . $subfolder)) {
164-
mkdir($tenantStoragePath . '/' . $subfolder, 0755, true);
164+
if (!$this->option('skip-initialize-storage-folder')) {
165+
foreach ($subfolders as $subfolder) {
166+
if (!File::isDirectory($tenantStoragePath . '/' . $subfolder)) {
167+
mkdir($tenantStoragePath . '/' . $subfolder, 0755, true);
168+
}
165169
}
166170
}
167171

@@ -179,20 +183,15 @@ public function handle()
179183
// Setup database
180184
DB::connection('landlord')->statement("CREATE DATABASE IF NOT EXISTS `{$this->option('database')}`");
181185

182-
// Hold off on this for now.
183-
// $this->tenantArtisan('tenant:storage-link', $tenant->id);
184-
185-
// Must be run after migrations so skip it. (provider somewhere complains about a table missing)
186-
// $this->tenantArtisan('passport:keys --force', $tenant->id);
187-
188-
$this->info("Empty tenant created.\n");
189-
$this->info("Unless you ran `tenants:enable --migrate`, you will need to run the following using TENANT={$tenant->id} env prefix");
190-
$this->line('- Run migrations');
191-
$this->line('- Seed the database');
192-
$this->line('- Run the install command for each package');
193-
$this->line('- Run artisan upgrade');
194-
$this->line('- Install passport with artisan passport:install');
195-
$this->info("For example, `TENANT={$tenant->id} php artisan migrate:fresh --seed`");
186+
$this->info("Empty tenant created. ID: {$tenant->id}");
187+
if (!$this->option('skip-setup-notifications')) {
188+
$this->info("You will need to do the following using TENANT={$tenant->id} env prefix");
189+
$this->line('- Run migrations and seed the database');
190+
$this->line('- Run the install command for each package');
191+
$this->line('- Run artisan upgrade');
192+
$this->line('- Install passport by calling passport:install');
193+
$this->info("For example, `TENANT={$tenant->id} php artisan migrate:fresh --seed`");
194+
}
196195
}
197196

198197
private function tenantArtisan($command, $tenantId)

ProcessMaker/Console/Commands/TenantsEnable.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ public function handle()
9999
'--lang-folder' => lang_path(),
100100
'--name' => config('app.name'),
101101
'--app-key' => config('app.key'),
102+
'--skip-setup-notifications' => true,
103+
'--skip-initialize-storage-folder' => true,
102104
], $this->output);
103105

104106
if ($exitCode !== 0) {

ProcessMaker/Console/Commands/TenantsTransition.php

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ class TenantsTransition extends Command
2222
*
2323
* @var string
2424
*/
25-
protected $description = 'Transition clients to tenants from storage/transitions folder';
25+
protected $description = 'Transition clients to tenants from .env files in storage/transitions folder';
26+
27+
/**
28+
* The success messages.
29+
*
30+
* @var array
31+
*/
32+
protected $success = [];
2633

2734
/**
2835
* Execute the console command.
@@ -44,59 +51,68 @@ public function handle()
4451
return 1;
4552
}
4653

47-
$clientFolders = File::directories($transitionsPath);
54+
$envFiles = File::files($transitionsPath, true);
55+
$envFiles = array_filter($envFiles, function ($file) {
56+
return str_starts_with(basename($file), '.env');
57+
});
4858

49-
if (empty($clientFolders)) {
50-
$this->error('No client folders found in storage/transitions.');
59+
if (empty($envFiles)) {
60+
$this->error('No .env files found in storage/transitions.');
5161

5262
return 1;
5363
}
5464

55-
foreach ($clientFolders as $clientFolder) {
56-
$this->processClientFolder($clientFolder);
65+
foreach ($envFiles as $envFile) {
66+
$exitCode = $this->processEnvFile($envFile);
67+
if ($exitCode !== 0) {
68+
$this->outputSuccessMessages();
69+
70+
return $exitCode;
71+
}
5772
}
5873

5974
$this->info('All clients have been transitioned to tenants.');
75+
$this->outputSuccessMessages();
6076

6177
return 0;
6278
}
6379

80+
private function outputSuccessMessages()
81+
{
82+
foreach ($this->success as $message) {
83+
$this->info($message);
84+
}
85+
}
86+
6487
/**
65-
* Process a single client folder
88+
* Process a single .env file
6689
*
67-
* @param string $clientFolder
90+
* @param string $envFilePath
6891
* @return void
6992
*/
70-
private function processClientFolder(string $clientFolder)
93+
private function processEnvFile(string $envFilePath)
7194
{
72-
$clientName = basename($clientFolder);
73-
$this->info("Processing client: {$clientName}");
74-
75-
$envFile = $clientFolder . '/.env';
76-
if (!File::exists($envFile)) {
77-
$this->error("No .env file found in {$clientName}");
78-
79-
return;
80-
}
95+
$fileName = basename($envFilePath);
96+
$this->info("Processing .env file: {$fileName}");
8197

8298
// Read the .env file
83-
$envContents = File::get($envFile);
99+
$envContents = File::get($envFilePath);
84100
$envVars = $this->parseEnvFile($envContents);
85101

86102
// Required environment variables
87103
$requiredVars = ['APP_NAME', 'APP_URL', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD'];
88104
foreach ($requiredVars as $var) {
89105
if (!isset($envVars[$var])) {
90-
$this->error("Missing required environment variable: {$var} in {$clientName}");
106+
$this->error("Missing required environment variable: {$var} in {$fileName}");
91107

92-
return;
108+
return 1;
93109
}
94110
}
95111

96112
$appName = $envVars['PROCESS_INTELLIGENCE_COMPANY_NAME'] ?? null;
97113
if (!$appName) {
98-
// Get the app name from the folder name
99-
$appName = basename($clientFolder);
114+
// Get the app name from the .env file suffix (e.g., .env.my-app -> my-app)
115+
$appName = str_replace('.env.', '', $fileName);
100116
}
101117

102118
// Create the tenant
@@ -105,36 +121,39 @@ private function processClientFolder(string $clientFolder)
105121
$this->info("Creating tenant for domain: {$domain}");
106122

107123
// Call tenants:create command
124+
// NOTE: Storage and lang folders will be moved manually after the tenant is created.
108125
$command = [
109126
'--name' => $appName,
110127
'--url' => $envVars['APP_URL'],
111128
'--database' => $envVars['DB_DATABASE'],
112129
'--username' => $envVars['DB_USERNAME'],
113130
'--password' => $envVars['DB_PASSWORD'],
114-
'--storage-folder' => $clientFolder . '/storage',
115-
'--lang-folder' => $clientFolder . '/lang',
116131
'--app-key' => $envVars['APP_KEY'],
132+
'--skip-setup-notifications' => true,
133+
'--skip-initialize-storage-folder' => true,
117134
];
118135

119136
$exitCode = Artisan::call('tenants:create', $command, $this->output);
120137
if ($exitCode !== 0) {
121138
$this->error("Failed to create tenant for domain: {$domain}");
122139

123-
return;
140+
return 1;
124141
}
125142

126143
// Find the newly created tenant
127144
$tenant = Tenant::where('domain', $domain)->first();
128145
if (!$tenant) {
129146
$this->error("Failed to find tenant after creation for domain: {$domain}");
130147

131-
return;
148+
return 1;
132149
}
133150

134-
// Delete the client folder
135-
File::deleteDirectory($clientFolder);
151+
// Delete the .env file
152+
File::delete($envFilePath);
136153

137-
$this->info("Successfully transitioned client {$clientName} to tenant.");
154+
$this->success[] = "Success: {$fileName} -> Tenant ID: {$tenant->id}";
155+
156+
return 0;
138157
}
139158

140159
/**

README.md

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -540,15 +540,16 @@ ProcessMaker can now be set up as a multitenant application.
540540

541541
## Transition your dev instnace to multitenancy
542542

543-
1. Run the following command to enable multitenancy
544-
```
545-
php artisan tenants:enable --migrate
546-
```
547-
This command will
548-
- Set your existing database as the tenant database
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
543+
Run the following command to enable multitenancy
544+
```
545+
php artisan tenants:enable --migrate
546+
```
547+
This command will
548+
- Setup the landlord database. Make sure you create the empty landlord database first.
549+
- Set your existing database as the tenant database
550+
- Copy your existing `storage` folder to `storage/tenant_1`
551+
- Copy your existing `resources/lang` folder to `resources/lang/tenant_1`
552+
- Enable multitenancy in your .env
552553

553554
## Using `valet share` for the script microservice
554555

@@ -571,42 +572,29 @@ php artisan tenants:create --domain="another-tenant.test" --name="Another Tenant
571572
This command will
572573
- Create the required folder structure
573574
- Create the tenant database
574-
- Seed the tenant database
575-
- Generate new passport keys
576-
- Run the upgrade commands
577-
578-
You will need to run the package install commands for all installed packages.
579-
580-
## Migrate an existing instnace to a tenant
581575

582-
### Option 1: Using the TenantsCreate command
576+
You will need to run migrations, seeders, and package installers with the environment variable prefix TENANT={id}
583577

584-
Provide the --storage-folder option with the path to the storage folder of the instance you want to migrate. Use the existing instances database name.
578+
## `tenants:transition` command
585579

586-
Optionally, provide the --username and --password options of the db for the existing instance.
580+
Move multiple instnaces in bulk to a single multitenancy instance.
587581

588-
The storage folder will be **moved** to the new tenant's storage folder.
589-
```
590-
php artisan tenants:create --domain="some-tenant.test" --name="Some Tenant" --database="some_tenant" --storage-folder="/path/to/storage/folder"
591-
```
592-
593-
### Option 2: Using the TenantsTransition command
582+
If this is your local development environment, it's easier to use `tenants:enable --migrate` above.
594583

595584
Create a folder `storage/transitions` if it doesn't exist.
596585

597-
In that folder create a new folder for each instance you want to migrate.
598-
599-
The folder should contain exactly 3 things:
600-
- the .env from the instance you want to migrate
601-
- the storage folder from the instance you want to migrate, named `storage`
602-
- the resources/lang folder, named `lang`
586+
For each instance you want to transition into this multitenancy instance,
587+
copy the `.env` file into the transitions folder and add the instnace name to the file name.
588+
For example `.env.my-instance`
603589

604590
Run the following command to migrate the instance(s) to a tenant:
605591
```
606592
php artisan tenants:transition
607593
```
608594

609-
This command will:
595+
This command will create a new tenant for each .env file in the storage/transitions folder.
596+
597+
You must move the tenants storage folder and the resources/lang folder manually
610598

611599
# License
612600

0 commit comments

Comments
 (0)