diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 35471f6f..9e461a62 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Shell\Platform; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -23,6 +24,6 @@ public function boot() */ public function register() { - // + $this->app->scoped(Platform::class); } } diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php index 0165942e..94a73792 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -68,7 +68,12 @@ public static function name(): string return static::$displayName ?? Str::afterLast(static::class, '\\'); } - public function enable(bool $useDefaults = false, array $passthroughOptions = [], string $runOptions = null): void + public static function category(): string + { + return static::$category ?? 'Other'; + } + + public function enable(bool $useDefaults = false, array $passthroughOptions = [], ?string $runOptions = null): void { $this->useDefaults = $useDefaults; @@ -80,9 +85,9 @@ public function enable(bool $useDefaults = false, array $passthroughOptions = [] try { $this->docker->bootContainer( - join(' ', array_filter([ + implode(' ', array_filter([ $runOptions, - $this->sanitizeDockerRunTemplate($this->dockerRunTemplate), + $this->sanitizeDockerRunTemplate($this->dockerRunTemplate()), $this->buildPassthroughOptionsString($passthroughOptions), ])), $this->buildParameters(), @@ -111,11 +116,6 @@ public function forwardShell(): void $this->docker->forwardShell($service['container_id'], $this->shellCommand()); } - protected function shellCommand(): string - { - return 'bash'; - } - public function organization(): string { return $this->organization; @@ -126,11 +126,6 @@ public function imageName(): string return $this->imageName; } - public static function category(): string - { - return static::$category ?? 'Other'; - } - public function shortName(): string { return strtolower(class_basename(static::class)); @@ -146,6 +141,29 @@ public function defaultPort(): int return $this->defaultPort; } + public function sanitizeDockerRunTemplate($dockerRunTemplate): string + { + if ($this->environment->isWindowsOs()) { + return stripslashes($dockerRunTemplate); + } + + return $dockerRunTemplate; + } + + public function buildPassthroughOptionsString(array $passthroughOptions): string + { + if (empty($passthroughOptions)) { + return ''; + } + + return implode(' ', $passthroughOptions); + } + + protected function shellCommand(): string + { + return 'bash'; + } + protected function ensureImageIsDownloaded(): void { if ($this->docker->imageIsDownloaded($this->organization, $this->imageName, $this->tag)) { @@ -244,22 +262,4 @@ protected function containerName(): string return 'TO--' . $this->shortName() . '--' . $this->tag . $portTag; } - - public function sanitizeDockerRunTemplate($dockerRunTemplate): string - { - if ($this->environment->isWindowsOs()) { - return stripslashes($dockerRunTemplate); - } - - return $dockerRunTemplate; - } - - public function buildPassthroughOptionsString(array $passthroughOptions): string - { - if (empty($passthroughOptions)) { - return ''; - } - - return join(' ', $passthroughOptions); - } } diff --git a/app/Services/MsSql.php b/app/Services/MsSql.php index 1e12389d..cd05e264 100644 --- a/app/Services/MsSql.php +++ b/app/Services/MsSql.php @@ -3,13 +3,16 @@ namespace App\Services; use App\Shell\MicrosoftDockerTags; +use App\Shell\Platform; class MsSql extends BaseService { protected static $category = Category::DATABASE; + protected static $displayName = 'MS SQL Server'; + protected $organization = 'mcr.microsoft.com'; - protected $imageName = 'azure-sql-edge'; + protected $imageName = 'mssql/server'; protected $dockerTagsClass = MicrosoftDockerTags::class; protected $defaultPort = 1433; protected $prompts = [ @@ -27,9 +30,20 @@ class MsSql extends BaseService protected $dockerRunTemplate = '-p "${:port}":1433 \ -e ACCEPT_EULA=Y \ + -e MSSQL_PID=Express \ -e SA_PASSWORD="${:sa_password}" \ -v "${:volume}":/var/opt/mssql \ "${:organization}"/"${:image_name}":"${:tag}"'; - protected static $displayName = 'MS SQL Server'; + public function dockerRunTemplate(): string + { + // The Microsoft image doesn't provide a proper ARM64 build, + // so we need to rely on Rosetta for Mac users. We can do + // that by specifying a platform such as `linux/amd64`. + + return match (Platform::isArm()) { + true => '--platform linux/amd64 \\' . PHP_EOL . $this->dockerRunTemplate, + default => $this->dockerRunTemplate, + }; + } } diff --git a/app/Shell/DockerTags.php b/app/Shell/DockerTags.php index c4dfa45d..feaa54d9 100644 --- a/app/Shell/DockerTags.php +++ b/app/Shell/DockerTags.php @@ -45,11 +45,9 @@ public function getTags(): Collection { $response = json_decode($this->getTagsResponse()->getContents(), true); - $platform = $this->platform(); - return collect($response['results']) - ->when(in_array($platform, $this->armArchitectures, true), $this->onlyArmImagesFilter()) - ->when(! in_array($platform, $this->armArchitectures, true), $this->onlyNonArmImagesFilter()) + ->when(Platform::isArm(), $this->onlyArmImagesFilter()) + ->when(! Platform::isArm(), $this->onlyNonArmImagesFilter()) ->pluck('name') ->sort(new VersionComparator) ->values(); @@ -61,7 +59,7 @@ protected function onlyArmImagesFilter() return $tags->filter(function ($tag) { $supportedArchs = collect($tag['images'])->pluck('architecture'); - foreach ($this->armArchitectures as $arch) { + foreach (Platform::$armArchitectures as $arch) { if ($supportedArchs->contains($arch)) { return true; } @@ -85,16 +83,11 @@ protected function onlyNonArmImagesFilter() // still be other options in the supported architectures // so we can consider that the tag is not arm-only. - return $supportedArchitectures->diff($this->armArchitectures)->count() > 0; + return $supportedArchitectures->diff(Platform::$armArchitectures)->count() > 0; }); }; } - protected function platform(): string - { - return php_uname('m'); - } - protected function getTagsResponse(): StreamInterface { return $this->guzzle diff --git a/app/Shell/Platform.php b/app/Shell/Platform.php new file mode 100644 index 00000000..8b9ef5db --- /dev/null +++ b/app/Shell/Platform.php @@ -0,0 +1,33 @@ +instance(Platform::class, new Platform($platform)); + } + + public static function isArm(): bool + { + return in_array(static::make()->platform(), static::$armArchitectures, true); + } + + protected function platform(): string + { + return $this->platform ??= php_uname('m'); + } +} diff --git a/tests/Feature/DockerTagsTest.php b/tests/Feature/DockerTagsTest.php index db6b2f40..90d53f60 100644 --- a/tests/Feature/DockerTagsTest.php +++ b/tests/Feature/DockerTagsTest.php @@ -5,26 +5,18 @@ use App\Services\MySql; use App\Services\PostgreSql; use App\Shell\DockerTags; +use App\Shell\Platform; use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; use Mockery as M; -use Tests\Support\FakePlatformDockerTags; use Tests\TestCase; class DockerTagsTest extends TestCase { - public static function armPlatforms(): array - { - return [ - [FakePlatformDockerTags::M1_ARM_PLATFORM], - [FakePlatformDockerTags::LINUX_ARM_PLATFORM], - ]; - } - /** @test */ - function it_gets_the_latest_tag_not_named_latest() + public function it_gets_the_latest_tag_not_named_latest() { $dockerTags = M::mock(DockerTags::class, [app(Client::class), app(MySql::class)])->makePartial(); $dockerTags->shouldReceive('getTags')->andReturn(collect(['latest', 'some named tag', '1.0.0'])); @@ -33,7 +25,7 @@ function it_gets_the_latest_tag_not_named_latest() } /** @test */ - function if_latest_is_the_only_tag_it_returns_latest() + public function if_latest_is_the_only_tag_it_returns_latest() { $dockerTags = M::mock(DockerTags::class, [app(Client::class), app(MySql::class)])->makePartial(); $dockerTags->shouldReceive('getTags')->andReturn(collect(['latest'])); @@ -42,7 +34,7 @@ function if_latest_is_the_only_tag_it_returns_latest() } /** @test */ - function it_sorts_the_versions_naturally() + public function it_sorts_the_versions_naturally() { $postgres = app(PostgreSql::class); $dockerTags = app(DockerTags::class, ['service' => $postgres]); @@ -55,25 +47,30 @@ function it_sorts_the_versions_naturally() /** * @test * - * @dataProvider armPlatforms + * @testWith ["arm64"] + * ["aarch64"] */ - function it_detects_arm_based_images_when_running_on_arm64_based_host($platform) + public function it_detects_arm_based_images_when_running_on_arm64_based_host($platform) { $handlerStack = HandlerStack::create($this->mockImagesResponseHandler()); $client = new Client(['handler' => $handlerStack]); - $dockerTags = (new FakePlatformDockerTags($client, app(MySql::class)))->withFakePlatform($platform); + Platform::fake($platform); + + $dockerTags = new DockerTags($client, app(MySql::class)); $this->assertEquals('1.0.0-arm64', $dockerTags->getLatestTag()); } /** @test */ - function it_gets_latest_tag_on_intel_platform() + public function it_gets_latest_tag_on_intel_platform() { $handlerStack = HandlerStack::create($this->mockImagesResponseHandler()); $client = new Client(['handler' => $handlerStack]); - $dockerTags = (new FakePlatformDockerTags($client, app(MySql::class)))->withFakePlatform(FakePlatformDockerTags::INTEL_ARM_PLATFORM); + Platform::fake('x86_64'); + + $dockerTags = new DockerTags($client, app(MySql::class)); $this->assertEquals('1.0.0', $dockerTags->getLatestTag()); } diff --git a/tests/Support/FakePlatformDockerTags.php b/tests/Support/FakePlatformDockerTags.php deleted file mode 100644 index dc4b7dc4..00000000 --- a/tests/Support/FakePlatformDockerTags.php +++ /dev/null @@ -1,26 +0,0 @@ -fakePlatform = $platform; - - return $this; - } - - protected function platform(): string - { - return $this->fakePlatform; - } -}