mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-04 17:04:03 +00:00
wip
This commit is contained in:
commit
c38dd1bb9a
8 changed files with 196 additions and 53 deletions
19
README.md
19
README.md
|
|
@ -53,6 +53,7 @@ You won't have to change a thing in your application's code.\*
|
|||
* [Artisan commands](#artisan-commands)
|
||||
- [`tenants:list`](#-tenants-list-)
|
||||
- [`tenants:migrate`, `tenants:rollback`, `tenants:seed`](#-tenants-migrate----tenants-rollback----tenants-seed-)
|
||||
- [Running your commands for tenants](#running-your-commands-for-tenants)
|
||||
+ [Tenant migrations](#tenant-migrations)
|
||||
* [Testing](#testing)
|
||||
- [Tips](#tips)
|
||||
|
|
@ -487,6 +488,7 @@ Available commands for the "tenants" namespace:
|
|||
tenants:list List tenants.
|
||||
tenants:migrate Run migrations for tenant(s)
|
||||
tenants:rollback Rollback migrations for tenant(s).
|
||||
tenants:run Run a command for tenant(s).
|
||||
tenants:seed Seed tenant database(s).
|
||||
```
|
||||
|
||||
|
|
@ -509,6 +511,18 @@ Tenant: 8075a580-1cb8-11e9-8822-49c5d8f8ff23 (laravel.localhost)
|
|||
Database seeding completed successfully.
|
||||
```
|
||||
|
||||
### Running your commands for tenants
|
||||
|
||||
You can use the `tenants:run` command to run your own commands for tenants.
|
||||
|
||||
If your command's signature were `email:send {user} {--queue} {--subject} {body}`, you would run this command like this:
|
||||
|
||||
```
|
||||
$ artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..."
|
||||
```
|
||||
|
||||
The `=` separates the argument/option name from its value, but you can still use `=` in the argument's value.
|
||||
|
||||
### Tenant migrations
|
||||
|
||||
Tenant migrations are located in `database/migrations/tenant`, so you should move your tenant migrations there.
|
||||
|
|
@ -530,6 +544,9 @@ To do this automatically, you can make this part of your `TestCase::setUp()` met
|
|||
|
||||
## HTTPS certificates
|
||||
|
||||
<details>
|
||||
<summary><strong>Click to expand/collapse</strong></summary>
|
||||
|
||||
HTTPS certificates are very easy to deal with if you use the `yourclient1.yourapp.com`, `yourclient2.yourapp.com` model. You can use a wildcard HTTPS certificate.
|
||||
|
||||
If you use the model where second level domains are used, there are multiple ways you can solve this.
|
||||
|
|
@ -562,6 +579,8 @@ Creating this config dynamically from PHP is not easy, but is probably feasible.
|
|||
|
||||
However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP.
|
||||
|
||||
</details>
|
||||
|
||||
# Development
|
||||
|
||||
## Running tests
|
||||
|
|
|
|||
66
src/Commands/Run.php
Normal file
66
src/Commands/Run.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Run extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run a command for tenant(s)';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = "tenants:run {commandname : The command's name.}
|
||||
{--tenants= : The tenant(s) to run the command for. Default: all}
|
||||
{--argument=* : The arguments to pass to the command. Default: none}
|
||||
{--option=* : The options to pass to the command. Default: none}";
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($tenancy_was_initialized = tenancy()->initialized) {
|
||||
$previous_tenants_domain = tenant('domain');
|
||||
}
|
||||
|
||||
tenant()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['uuid']} ({$tenant['domain']})");
|
||||
tenancy()->init($tenant['domain']);
|
||||
|
||||
$callback = function ($prefix = '') {
|
||||
return function ($arguments, $argument) use ($prefix) {
|
||||
[$key, $value] = explode('=', $argument, 2);
|
||||
$arguments[$prefix . $key] = $value;
|
||||
|
||||
return $arguments;
|
||||
};
|
||||
};
|
||||
|
||||
// Turns ['foo=bar', 'abc=xyz=zzz'] into ['foo' => 'bar', 'abc' => 'xyz=zzz']
|
||||
$arguments = array_reduce($this->option('argument'), $callback(), []);
|
||||
|
||||
// Turns ['foo=bar', 'abc=xyz=zzz'] into ['--foo' => 'bar', '--abc' => 'xyz=zzz']
|
||||
$options = array_reduce($this->option('option'), $callback('--'), []);
|
||||
|
||||
// Run command
|
||||
$this->call($this->argument('commandname'), array_merge($arguments, $options));
|
||||
|
||||
tenancy()->end();
|
||||
});
|
||||
|
||||
if ($tenancy_was_initialized) {
|
||||
tenancy()->init($previous_tenants_domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Stancl\Tenancy;
|
||||
|
||||
use Stancl\Tenancy\Commands\Run;
|
||||
use Stancl\Tenancy\Commands\Seed;
|
||||
use Illuminate\Cache\CacheManager;
|
||||
use Stancl\Tenancy\Commands\Migrate;
|
||||
|
|
@ -24,9 +25,10 @@ class TenancyServiceProvider extends ServiceProvider
|
|||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
Run::class,
|
||||
Seed::class,
|
||||
Migrate::class,
|
||||
Rollback::class,
|
||||
Seed::class,
|
||||
TenantList::class,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
35
test
35
test
|
|
@ -1,26 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
from os import system
|
||||
import argparse
|
||||
#!/bin/bash
|
||||
|
||||
system('docker-compose up -d')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--variants", default='1,2,3',
|
||||
help="Comma-separated values. Which test variants should be run.")
|
||||
args, other = parser.parse_known_args()
|
||||
|
||||
variants = args.variants.split(',')
|
||||
|
||||
for variant in variants:
|
||||
filename_base = "phpunit_var_" + variant
|
||||
with open('phpunit.xml', 'r') as inp, open(filename_base + '.xml', 'w') as out:
|
||||
out.write(inp.read().replace('"STANCL_TENANCY_TEST_VARIANT" value="1"',
|
||||
'"STANCL_TENANCY_TEST_VARIANT" value="%s"' % variant))
|
||||
|
||||
print("Test variant: %s\n" % variant)
|
||||
|
||||
system('docker-compose exec test vendor/bin/phpunit --configuration "%s" --coverage-php %s %s'
|
||||
% (filename_base + '.xml', 'coverage/' + filename_base + '.cov', ' '.join(other)))
|
||||
|
||||
# todo delete folder contents first?
|
||||
system("docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/")
|
||||
# for development
|
||||
docker-compose up -d
|
||||
printf "Variant 1\n\n"
|
||||
TENANCY_TEST_REDIS_TENANCY=1 TENANCY_TEST_REDIS_CLIENT=phpredis docker-compose exec test vendor/bin/phpunit --coverage-php coverage/1.cov "$@"
|
||||
printf "Variant 2\n\n"
|
||||
TENANCY_TEST_REDIS_TENANCY=0 TENANCY_TEST_REDIS_CLIENT=predis docker-compose exec test vendor/bin/phpunit --coverage-php coverage/2.cov "$@"
|
||||
printf "Variant 3\n\n"
|
||||
TENANCY_TEST_REDIS_TENANCY=1 TENANCY_TEST_REDIS_CLIENT=phpredis TENANCY_TEST_STORAGE_DRIVER=db docker-compose exec test vendor/bin/phpunit --coverage-php coverage/3.cov "$@"
|
||||
docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class CommandsTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function database_connection_is_switched_to_default_after_migrating_or_seeding_or_rolling_back()
|
||||
public function database_connection_is_switched_to_default()
|
||||
{
|
||||
$originalDBName = DB::connection()->getDatabaseName();
|
||||
|
||||
|
|
@ -88,13 +88,29 @@ class CommandsTest extends TestCase
|
|||
|
||||
Artisan::call('tenants:rollback');
|
||||
$this->assertSame($originalDBName, DB::connection()->getDatabaseName());
|
||||
|
||||
$this->run_commands_works();
|
||||
$this->assertSame($originalDBName, DB::connection()->getDatabaseName());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function database_connection_is_switched_to_default_after_migrating_or_seeding_or_rolling_back_when_tenancy_has_been_initialized()
|
||||
public function database_connection_is_switched_to_default_when_tenancy_has_been_initialized()
|
||||
{
|
||||
tenancy()->init('localhost');
|
||||
|
||||
$this->database_connection_is_switched_to_default_after_migrating_or_seeding_or_rolling_back();
|
||||
$this->database_connection_is_switched_to_default();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function run_commands_works()
|
||||
{
|
||||
$uuid = tenant()->create('run.localhost')['uuid'];
|
||||
|
||||
Artisan::call('tenants:migrate', ['--tenants' => $uuid]);
|
||||
|
||||
$this->artisan("tenants:run foo --tenants=$uuid --argument='a=foo' --option='b=bar' --option='c=xyz'")
|
||||
->expectsOutput("User's name is Test command")
|
||||
->expectsOutput('foo')
|
||||
->expectsOutput('xyz');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
tests/Etc/ConsoleKernel.php
Normal file
17
tests/Etc/ConsoleKernel.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc;
|
||||
|
||||
use Orchestra\Testbench\Console\Kernel;
|
||||
|
||||
class ConsoleKernel extends Kernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
ExampleCommand::class,
|
||||
];
|
||||
}
|
||||
39
tests/Etc/ExampleCommand.php
Normal file
39
tests/Etc/ExampleCommand.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ExampleCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'foo {a} {--b=} {--c=}';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
User::create([
|
||||
'id' => 999,
|
||||
'name' => 'Test command',
|
||||
'email' => 'test@command.com',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
|
||||
$this->line("User's name is " . User::find(999)->name);
|
||||
$this->line($this->argument('a'));
|
||||
$this->line($this->option('c'));
|
||||
}
|
||||
}
|
||||
|
||||
class User extends \Illuminate\Database\Eloquent\Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
Artisan::call('migrate:fresh', [
|
||||
'--path' => __DIR__ . '/../src/assets/migrations'
|
||||
]);
|
||||
dd(Artisan::output());
|
||||
// dd(Artisan::output());
|
||||
// $this->loadLaravelMigrations();
|
||||
// $this->loadMigrationsFrom(__DIR__ . '/../src/assets/migrations');
|
||||
|
||||
|
|
@ -62,7 +62,6 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
}
|
||||
|
||||
$app['config']->set([
|
||||
'database.redis.client' => 'phpredis',
|
||||
'database.redis.cache.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
||||
'database.redis.default.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
||||
'database.redis.options.prefix' => 'foo',
|
||||
|
|
@ -88,33 +87,22 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'public',
|
||||
's3',
|
||||
],
|
||||
'tenancy.redis.tenancy' => true,
|
||||
'tenancy.redis.tenancy' => env('TENANCY_TEST_REDIS_TENANCY', true),
|
||||
'database.redis.client' => env('TENANCY_TEST_REDIS_CLIENT', 'phpredis'),
|
||||
'tenancy.redis.prefixed_connections' => ['default'],
|
||||
'tenancy.migrations_directory' => database_path('../migrations'),
|
||||
]);
|
||||
|
||||
switch ((string) env('STANCL_TENANCY_TEST_VARIANT', '1')) {
|
||||
case '3':
|
||||
$app['config']->set([
|
||||
'tenancy.redis.tenancy' => true,
|
||||
'database.redis.client' => 'phpredis',
|
||||
'tenancy.storage_driver' => DatabaseStorageDriver::class,
|
||||
]);
|
||||
tenancy()->setStorageDriver(DatabaseStorageDriver::class);
|
||||
|
||||
break;
|
||||
case '2':
|
||||
$app['config']->set([
|
||||
'tenancy.redis.tenancy' => false,
|
||||
'database.redis.client' => 'predis',
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
$app['config']->set([
|
||||
'tenancy.redis.tenancy' => true,
|
||||
'database.redis.client' => 'phpredis',
|
||||
]);
|
||||
if (env('TENANCY_TEST_STORAGE_DRIVER', 'redis') === 'db') {
|
||||
$app['config']->set([
|
||||
'tenancy.redis.tenancy' => true,
|
||||
'database.redis.client' => 'phpredis',
|
||||
'tenancy.storage_driver' => DatabaseStorageDriver::class,
|
||||
]);
|
||||
|
||||
tenancy()->setStorageDriver(DatabaseStorageDriver::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function getPackageProviders($app)
|
||||
|
|
@ -141,6 +129,17 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
$app->singleton('Illuminate\Contracts\Http\Kernel', Etc\HttpKernel::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve application Console Kernel implementation.
|
||||
*
|
||||
* @param \Illuminate\Foundation\Application $app
|
||||
* @return void
|
||||
*/
|
||||
protected function resolveApplicationConsoleKernel($app)
|
||||
{
|
||||
$app->singleton('Illuminate\Contracts\Console\Kernel', Etc\ConsoleKernel::class);
|
||||
}
|
||||
|
||||
public function randomString(int $length = 10)
|
||||
{
|
||||
return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue