Skip to content

Commit 89c096f

Browse files
authored
Merge pull request #8483 from ProcessMaker/bugfix/FOUR-26084
Copy instead of move tenant transition files
2 parents 6ae6c70 + abd6f62 commit 89c096f

File tree

4 files changed

+130
-155
lines changed

4 files changed

+130
-155
lines changed

ProcessMaker/Console/Commands/TenantsCreate.php

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Support\Facades\Crypt;
99
use Illuminate\Support\Facades\DB;
1010
use Illuminate\Support\Facades\File;
11+
use Illuminate\Support\Facades\Process;
1112
use ProcessMaker\Multitenancy\Tenant;
1213

1314
class TenantsCreate extends Command
@@ -17,7 +18,17 @@ class TenantsCreate extends Command
1718
*
1819
* @var string
1920
*/
20-
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-folders}
31+
{--skip-setup-notifications}';
2132

2233
/**
2334
* The console command description.
@@ -31,6 +42,17 @@ class TenantsCreate extends Command
3142
*/
3243
public function handle()
3344
{
45+
$infoCallback = function ($type, $message) {
46+
if ($type === 'out') {
47+
$this->info($message);
48+
} else {
49+
$this->error($message);
50+
}
51+
};
52+
53+
// Check if rsync exists
54+
Process::run('which rsync')->throw();
55+
3456
$requiredOptions = ['name', 'database', 'url'];
3557

3658
foreach ($requiredOptions as $option) {
@@ -82,21 +104,8 @@ public function handle()
82104
if ($storageFolderOption) {
83105
if (File::isDirectory($storageFolderOption)) {
84106
$this->info('Moving storage folder to ' . $tenantStoragePath);
85-
$subfoldersToExclude = '/^(tenant_\d+|logs|transitions)$/i';
86-
foreach (File::directories($storageFolderOption) as $subfolder) {
87-
if (preg_match($subfoldersToExclude, basename($subfolder))) {
88-
$this->info('Skipping ' . $subfolder);
89-
continue;
90-
}
91-
$this->info('Moving ' . $subfolder . ' to ' . $tenantStoragePath);
92-
rename($subfolder, $tenantStoragePath . '/' . basename($subfolder));
93-
}
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-
}
107+
$cmd = "rsync -avz --exclude='tenant_*' --exclude='logs' --exclude='transitions' " . $storageFolderOption . '/ ' . $tenantStoragePath;
108+
Process::run($cmd, $infoCallback)->throw();
100109
} else {
101110
$this->error('Storage folder does not exist: ' . $storageFolderOption);
102111

@@ -110,13 +119,14 @@ public function handle()
110119
$tenantLangPath = resource_path('lang/tenant_' . $tenant->id);
111120
if ($langFolderOption) {
112121
if (File::isDirectory($langFolderOption)) {
113-
$this->info('Moving lang folder to ' . $tenantLangPath);
122+
$this->info('Copying lang folder to ' . $tenantLangPath);
114123
if (File::isDirectory($tenantLangPath)) {
115124
$this->error('Tenant lang path already exists: ' . $tenantLangPath);
116125

117126
return 1;
118127
} else {
119-
rename($langFolderOption, $tenantLangPath);
128+
$cmd = "rsync -avz --exclude='tenant_*' " . $langFolderOption . '/ ' . $tenantLangPath;
129+
Process::run($cmd, $infoCallback)->throw();
120130
}
121131
} else {
122132
$this->error('Lang folder does not exist: ' . $langFolderOption);
@@ -130,16 +140,13 @@ public function handle()
130140
}
131141
}
132142

133-
$cmd = sprintf(
134-
"rsync -azv --ignore-existing --exclude='tenant_*' %s %s",
135-
escapeshellarg($sourceLangPath . '/'),
136-
escapeshellarg($tenantLangPath)
137-
);
138-
exec($cmd, $output, $returnVar);
139-
if ($returnVar !== 0) {
140-
$this->error('Failed to rsync lang folder to tenant: ' . implode(PHP_EOL, $output));
141-
142-
return 1;
143+
if (!$this->option('skip-initialize-folders')) {
144+
$cmd = sprintf(
145+
"rsync -azv --ignore-existing --exclude='tenant_*' %s %s",
146+
escapeshellarg($sourceLangPath . '/'),
147+
escapeshellarg($tenantLangPath)
148+
);
149+
Process::run($cmd, $infoCallback)->throw();
143150
}
144151

145152
$subfolders = [
@@ -163,9 +170,11 @@ public function handle()
163170
'api-docs',
164171
];
165172

166-
foreach ($subfolders as $subfolder) {
167-
if (!File::isDirectory($tenantStoragePath . '/' . $subfolder)) {
168-
mkdir($tenantStoragePath . '/' . $subfolder, 0755, true);
173+
if (!$this->option('skip-initialize-folders')) {
174+
foreach ($subfolders as $subfolder) {
175+
if (!File::isDirectory($tenantStoragePath . '/' . $subfolder)) {
176+
mkdir($tenantStoragePath . '/' . $subfolder, 0755, true);
177+
}
169178
}
170179
}
171180

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

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

202206
private function tenantArtisan($command, $tenantId)

ProcessMaker/Console/Commands/TenantsEnable.php

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,6 @@ public function handle()
4545

4646
return 1;
4747
}
48-
49-
// Check if rsync exists
50-
if (!exec('which rsync')) {
51-
$this->error('rsync is not installed');
52-
53-
return 1;
54-
}
5548
}
5649

5750
// Get the landlord database name
@@ -97,35 +90,17 @@ public function handle()
9790
* Begin migration if the --migrate option is provided.
9891
*/
9992

100-
// First, copy the existing storage folder to a temp location
101-
$tempStorageFolder = base_path('storage-temp');
102-
exec("rsync -avz --exclude='tenant_*' " . base_path('storage') . '/ ' . $tempStorageFolder, $output, $returnVar);
103-
if ($returnVar !== 0) {
104-
$this->error('Failed to copy storage folder to temp location');
105-
$this->error(implode("\n", $output));
106-
107-
return 1;
108-
}
109-
$this->info(implode("\n", $output));
110-
111-
// Next, do the same thing for the lang folder
112-
$tempLangFolder = base_path('lang-temp');
113-
exec('rsync -avz ' . resource_path('lang') . '/ ' . $tempLangFolder, $output, $returnVar);
114-
if ($returnVar !== 0) {
115-
$this->error('Failed to copy lang folder to temp location');
116-
$this->error(implode("\n", $output));
117-
}
118-
$this->info(implode("\n", $output));
119-
120-
// Now, create the tenant. The folder will be moved to the new tenant after the creation
121-
// and the $tempStorageFolder will no longer exist.
93+
// Now, create the tenant.
94+
// The contents of lang-folder and storage-folder will be copied to the new tenant.
12295
$exitCode = Artisan::call('tenants:create', [
12396
'--database' => config('database.connections.processmaker.database'),
12497
'--url' => config('app.url'),
125-
'--storage-folder' => $tempStorageFolder,
126-
'--lang-folder' => $tempLangFolder,
98+
'--storage-folder' => storage_path(),
99+
'--lang-folder' => lang_path(),
127100
'--name' => config('app.name'),
128101
'--app-key' => config('app.key'),
102+
'--skip-setup-notifications' => true,
103+
'--skip-initialize-folders' => true,
129104
], $this->output);
130105

131106
if ($exitCode !== 0) {
@@ -134,22 +109,6 @@ public function handle()
134109
return 1;
135110
}
136111

137-
// Remove temp storage folder
138-
exec('rm -rf ' . $tempStorageFolder, $output, $returnVar);
139-
if ($returnVar !== 0) {
140-
$this->error('Failed to remove temp storage folder');
141-
$this->error(implode("\n", $output));
142-
}
143-
$this->info(implode("\n", $output));
144-
145-
// Remove temp lang folder
146-
exec('rm -rf ' . $tempLangFolder, $output, $returnVar);
147-
if ($returnVar !== 0) {
148-
$this->error('Failed to remove temp lang folder');
149-
$this->error(implode("\n", $output));
150-
}
151-
$this->info(implode("\n", $output));
152-
153112
// Add or update the MULTITENANCY env var
154113
$this->addOrUpdateEnvVar('MULTITENANCY', 'true');
155114

ProcessMaker/Console/Commands/TenantsTransition.php

Lines changed: 55 additions & 31 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

96-
$appName = $envVars['PROCESS_INTELLIGENCE_COMPANY_NAME'];
112+
$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,31 +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-folders' => true,
117134
];
118135

119-
Artisan::call('tenants:create', $command);
136+
$exitCode = Artisan::call('tenants:create', $command, $this->output);
137+
if ($exitCode !== 0) {
138+
$this->error("Failed to create tenant for domain: {$domain}");
139+
140+
return 1;
141+
}
120142

121143
// Find the newly created tenant
122144
$tenant = Tenant::where('domain', $domain)->first();
123145
if (!$tenant) {
124146
$this->error("Failed to find tenant after creation for domain: {$domain}");
125147

126-
return;
148+
return 1;
127149
}
128150

129-
// Delete the client folder
130-
File::deleteDirectory($clientFolder);
151+
// Delete the .env file
152+
File::delete($envFilePath);
131153

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

135159
/**

0 commit comments

Comments
 (0)