From ce691a1b15805e40a8dc6683971d8440d1d7fba1 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 4 Jul 2025 14:33:46 -0300 Subject: [PATCH 1/4] Swaps the edge image for the MSSQL Server Express edition The azure-edge is gonna get retired soon, and they recommend [1](1) going with the express for use cases like ours. [1](https://azure.microsoft.com/en-us/updates?id=azure-sql-edge-retirement ) --- app/Services/MsSql.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Services/MsSql.php b/app/Services/MsSql.php index 1e12389d..d5c87ad0 100644 --- a/app/Services/MsSql.php +++ b/app/Services/MsSql.php @@ -8,8 +8,10 @@ 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 +29,8 @@ 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'; } From 45b21ae3a6d44395f29474b7b6cf4ed26146d264 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 4 Jul 2025 15:47:39 -0300 Subject: [PATCH 2/4] Extracts the platform checking from Docker Tags into a Platform class --- app/Providers/AppServiceProvider.php | 3 ++- app/Shell/DockerTags.php | 15 +++-------- app/Shell/Platform.php | 32 ++++++++++++++++++++++++ tests/Feature/DockerTagsTest.php | 24 ++++++++++-------- tests/Support/FakePlatformDockerTags.php | 26 ------------------- 5 files changed, 52 insertions(+), 48 deletions(-) create mode 100644 app/Shell/Platform.php delete mode 100644 tests/Support/FakePlatformDockerTags.php 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/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..dddde4e1 --- /dev/null +++ b/app/Shell/Platform.php @@ -0,0 +1,32 @@ +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..7656d5ff 100644 --- a/tests/Feature/DockerTagsTest.php +++ b/tests/Feature/DockerTagsTest.php @@ -5,12 +5,12 @@ 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 @@ -18,13 +18,13 @@ class DockerTagsTest extends TestCase public static function armPlatforms(): array { return [ - [FakePlatformDockerTags::M1_ARM_PLATFORM], - [FakePlatformDockerTags::LINUX_ARM_PLATFORM], + ['arm64'], // Apple silicon + ['aarch64'], // Linux ARM ]; } /** @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 +33,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 +42,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]); @@ -57,23 +57,27 @@ function it_sorts_the_versions_naturally() * * @dataProvider armPlatforms */ - 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; - } -} From e8202a4171f1d56dbb1c960a9eae5d147157c46e Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 4 Jul 2025 17:04:30 -0300 Subject: [PATCH 3/4] Tweaks test to use testWith instead of data provider --- app/Shell/Platform.php | 3 ++- tests/Feature/DockerTagsTest.php | 11 ++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/Shell/Platform.php b/app/Shell/Platform.php index dddde4e1..8b9ef5db 100644 --- a/app/Shell/Platform.php +++ b/app/Shell/Platform.php @@ -8,7 +8,8 @@ class Platform public function __construct( private ?string $platform = null, - ) {} + ) { + } public static function make(): static { diff --git a/tests/Feature/DockerTagsTest.php b/tests/Feature/DockerTagsTest.php index 7656d5ff..90d53f60 100644 --- a/tests/Feature/DockerTagsTest.php +++ b/tests/Feature/DockerTagsTest.php @@ -15,14 +15,6 @@ class DockerTagsTest extends TestCase { - public static function armPlatforms(): array - { - return [ - ['arm64'], // Apple silicon - ['aarch64'], // Linux ARM - ]; - } - /** @test */ public function it_gets_the_latest_tag_not_named_latest() { @@ -55,7 +47,8 @@ public function it_sorts_the_versions_naturally() /** * @test * - * @dataProvider armPlatforms + * @testWith ["arm64"] + * ["aarch64"] */ public function it_detects_arm_based_images_when_running_on_arm64_based_host($platform) { From b3335504a3a27f4f8adf52cc2aaa04703b0d0916 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 4 Jul 2025 17:18:42 -0300 Subject: [PATCH 4/4] Specify a platform so Rosetta can act on Macs --- app/Services/BaseService.php | 62 ++++++++++++++++++------------------ app/Services/MsSql.php | 13 ++++++++ 2 files changed, 44 insertions(+), 31 deletions(-) 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 d5c87ad0..cd05e264 100644 --- a/app/Services/MsSql.php +++ b/app/Services/MsSql.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Shell\MicrosoftDockerTags; +use App\Shell\Platform; class MsSql extends BaseService { @@ -33,4 +34,16 @@ class MsSql extends BaseService -e SA_PASSWORD="${:sa_password}" \ -v "${:volume}":/var/opt/mssql \ "${:organization}"/"${:image_name}":"${:tag}"'; + + 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, + }; + } }