1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-04 17:04:03 +00:00
This commit is contained in:
Samuel Štancl 2019-08-10 11:01:41 +02:00
commit c38dd1bb9a
8 changed files with 196 additions and 53 deletions

View file

@ -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
View 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);
}
}
}

View file

@ -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
View file

@ -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/

View file

@ -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');
}
}

View 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,
];
}

View 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 = [];
}

View file

@ -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);