From 15f09e59dfb86c0a018c11e5278ee6f7f5750db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 9 Aug 2019 19:06:00 +0200 Subject: [PATCH 1/3] [1.7.0] Add tenants:run (#81) * wip * Apply fixes from StyleCI * first implementation * Apply fixes from StyleCI * Add support for arguments and options * Apply fixes from StyleCI * Write docs, add support for = in arg/opt value * Apply fixes from StyleCI * add $ [ci skip] --- README.md | 14 ++++++++ src/Commands/Run.php | 66 ++++++++++++++++++++++++++++++++++ src/TenancyServiceProvider.php | 4 ++- tests/CommandsTest.php | 22 ++++++++++-- tests/Etc/ConsoleKernel.php | 17 +++++++++ tests/Etc/ExampleCommand.php | 39 ++++++++++++++++++++ tests/TestCase.php | 11 ++++++ 7 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/Commands/Run.php create mode 100644 tests/Etc/ConsoleKernel.php create mode 100644 tests/Etc/ExampleCommand.php diff --git a/README.md b/README.md index 31f03153..18b0ec15 100644 --- a/README.md +++ b/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. diff --git a/src/Commands/Run.php b/src/Commands/Run.php new file mode 100644 index 00000000..d3e00861 --- /dev/null +++ b/src/Commands/Run.php @@ -0,0 +1,66 @@ +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); + } + } +} diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 12005e9f..233b87bb 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -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; @@ -23,9 +24,10 @@ class TenancyServiceProvider extends ServiceProvider { if ($this->app->runningInConsole()) { $this->commands([ + Run::class, + Seed::class, Migrate::class, Rollback::class, - Seed::class, TenantList::class, ]); } diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 3998719d..d26b71c1 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -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'); } } diff --git a/tests/Etc/ConsoleKernel.php b/tests/Etc/ConsoleKernel.php new file mode 100644 index 00000000..a0d82a84 --- /dev/null +++ b/tests/Etc/ConsoleKernel.php @@ -0,0 +1,17 @@ + 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 = []; +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 7cc55500..3bc0889f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -130,6 +130,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); From aec12ae1283dc095202415328f93fb08e6e1f17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 9 Aug 2019 19:40:45 +0200 Subject: [PATCH 2/3] Collapse HTTPS certs section --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 31f03153..a97f16c8 100644 --- a/README.md +++ b/README.md @@ -530,6 +530,9 @@ To do this automatically, you can make this part of your `TestCase::setUp()` met ## HTTPS certificates +
+Click to expand/collapse + 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 +565,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. +
+ # Development ## Running tests From d77d05e620546c24de027ad88fb17999c2596e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sat, 10 Aug 2019 10:59:23 +0200 Subject: [PATCH 3/3] Use env vars instead of a python script for ./test --- test | 33 ++++++++------------------------- tests/TestCase.php | 18 ++---------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/test b/test index 0d11eef3..5516ec57 100755 --- a/test +++ b/test @@ -1,26 +1,9 @@ -#!/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', - 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 "$@" +docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/ \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 3bc0889f..b5d28acd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -60,7 +60,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', @@ -86,24 +85,11 @@ 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 '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', - ]); - } } protected function getPackageProviders($app)