mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
feat(rollback): list Docker registry images of recent deployments
Rollback now also lists the commit images of the last 10 successful deployments when the application pushes to a Docker registry. The deployment pulls such an image when the server no longer has it. The page and the API share GetRollbackImages, and the API returns a source field (local or registry).
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
/**
|
||||
* Lists the images an application can roll back to: the images on its server, and the images of
|
||||
* recent successful deployments that were pushed to the Docker registry. The deployment pulls a
|
||||
* registry image again when the server no longer has it.
|
||||
*/
|
||||
class GetRollbackImages
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public const REGISTRY_DEPLOYMENTS_LIMIT = 10;
|
||||
|
||||
/**
|
||||
* @return array{current: ?string, images: list<array{tag: string, created_at: ?string, is_current: bool, source: 'local'|'registry'}>}
|
||||
*/
|
||||
public function handle(Application $application): array
|
||||
{
|
||||
$current = null;
|
||||
$images = collect();
|
||||
|
||||
$server = $application->destination?->server;
|
||||
if ($server?->isFunctional()) {
|
||||
$output = instant_remote_process([
|
||||
"docker inspect --format='{{.Config.Image}}' {$application->uuid}",
|
||||
], $server, throwError: false);
|
||||
$current = self::imageTag(str($output)->trim()->toString());
|
||||
|
||||
$repository = $application->docker_registry_image_name ?: $application->uuid;
|
||||
$output = instant_remote_process([
|
||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
||||
], $server);
|
||||
$images = str($output)->trim()->explode("\n")
|
||||
->map(fn (string $line) => explode('#', $line))
|
||||
// Exact repository match only, so other images with a similar name are not listed.
|
||||
->filter(fn (array $parts) => $parts[0] === $repository && filled($parts[1] ?? null))
|
||||
->map(fn (array $parts) => [
|
||||
'tag' => $parts[1],
|
||||
'created_at' => $parts[2] ?? null,
|
||||
'is_current' => $parts[1] === $current,
|
||||
'source' => 'local',
|
||||
])
|
||||
->keyBy('tag');
|
||||
}
|
||||
|
||||
foreach ($this->registryImages($application, $current) as $image) {
|
||||
if (! $images->has($image['tag'])) {
|
||||
$images->put($image['tag'], $image);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'current' => $current,
|
||||
'images' => $images->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Each successful deployment pushes `<image name>:<commit>` to the registry, so the commits of
|
||||
* recent deployments are rollback targets. Docker image applications do not push, and
|
||||
* Dockerfile-only applications always push the `latest` tag.
|
||||
*
|
||||
* @return list<array{tag: string, created_at: ?string, is_current: bool, source: 'registry'}>
|
||||
*/
|
||||
private function registryImages(Application $application, ?string $current): array
|
||||
{
|
||||
if (blank($application->docker_registry_image_name)
|
||||
|| in_array($application->build_pack, ['dockerimage', 'dockercompose'], true)
|
||||
|| filled($application->dockerfile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $application->deployment_queue()
|
||||
->where('status', ApplicationDeploymentStatus::FINISHED->value)
|
||||
->where('pull_request_id', 0)
|
||||
->whereNotNull('commit')
|
||||
->where('commit', '!=', 'HEAD')
|
||||
->select('commit')
|
||||
->selectRaw('max(updated_at) as deployed_at')
|
||||
->groupBy('commit')
|
||||
->orderByDesc('deployed_at')
|
||||
->limit(self::REGISTRY_DEPLOYMENTS_LIMIT)
|
||||
->get()
|
||||
->map(function ($deployment) use ($current) {
|
||||
// The same tag as the deployment job builds in generate_image_names().
|
||||
$tag = substr($deployment->commit, 0, 128);
|
||||
|
||||
return [
|
||||
'tag' => $tag,
|
||||
'created_at' => $deployment->deployed_at,
|
||||
'is_current' => $tag === $current,
|
||||
'source' => 'registry',
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag of an image reference such as `registry.example.com:5000/team/app:tag`, or null for a digest.
|
||||
*/
|
||||
public static function imageTag(string $imageReference): ?string
|
||||
{
|
||||
if (str_contains($imageReference, '@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lastColon = strrpos($imageReference, ':');
|
||||
$lastSlash = strrpos($imageReference, '/');
|
||||
|
||||
if ($lastColon === false || ($lastSlash !== false && $lastColon < $lastSlash)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return substr($imageReference, $lastColon + 1) ?: null;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Application\CleanupPreviewDeployment;
|
||||
use App\Actions\Application\GetRollbackImages;
|
||||
use App\Actions\Application\LoadComposeFile;
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Enums\BuildPackTypes;
|
||||
@@ -6133,7 +6134,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List Rollback Images',
|
||||
description: 'List available Docker images for rolling back an application. Returns an empty list when the server is unavailable or remote inspection is not possible.',
|
||||
description: 'List available Docker images for rolling back an application: images on the server (`local`) and, when a Docker registry image name is set, images of recent successful deployments in the registry (`registry`). Returns an empty list when the server is unavailable or remote inspection is not possible.',
|
||||
path: '/applications/{uuid}/rollback-images',
|
||||
operationId: 'list-application-rollback-images',
|
||||
security: [['bearerAuth' => []]],
|
||||
@@ -6157,6 +6158,7 @@ class ApplicationsController extends Controller
|
||||
new OA\Property(property: 'tag', type: 'string'),
|
||||
new OA\Property(property: 'created_at', type: 'string'),
|
||||
new OA\Property(property: 'is_current', type: 'boolean'),
|
||||
new OA\Property(property: 'source', type: 'string', enum: ['local', 'registry']),
|
||||
]
|
||||
)
|
||||
),
|
||||
@@ -6181,61 +6183,11 @@ class ApplicationsController extends Controller
|
||||
|
||||
$this->authorize('view', $application);
|
||||
|
||||
$current = null;
|
||||
$images = [];
|
||||
|
||||
try {
|
||||
$server = $application->destination?->server;
|
||||
if ($server && $server->isFunctional()) {
|
||||
$image = $application->docker_registry_image_name ?? $application->uuid;
|
||||
$output = instant_remote_process([
|
||||
"docker inspect --format='{{.Config.Image}}' {$application->uuid}",
|
||||
], $server, throwError: false);
|
||||
$current = self::currentRollbackImageTag(str($output)->trim()->toString());
|
||||
|
||||
$output = instant_remote_process([
|
||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
||||
], $server);
|
||||
$images = str($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
||||
$repository = str($item)->before('#')->toString();
|
||||
|
||||
// Exact repository match only — avoid substring collisions across images.
|
||||
return $repository === $image;
|
||||
})->map(function ($item) use ($current) {
|
||||
$parts = str($item)->explode('#');
|
||||
|
||||
return [
|
||||
'tag' => $parts[1] ?? null,
|
||||
'created_at' => $parts[2] ?? null,
|
||||
'is_current' => ($parts[1] ?? null) === $current,
|
||||
];
|
||||
})->values()->all();
|
||||
}
|
||||
return response()->json(GetRollbackImages::run($application));
|
||||
} catch (\Throwable) {
|
||||
$current = null;
|
||||
$images = [];
|
||||
return response()->json(['current' => null, 'images' => []]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'current' => $current,
|
||||
'images' => $images,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function currentRollbackImageTag(string $imageReference): ?string
|
||||
{
|
||||
if (str_contains($imageReference, '@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lastColon = strrpos($imageReference, ':');
|
||||
$lastSlash = strrpos($imageReference, '/');
|
||||
|
||||
if ($lastColon === false || ($lastSlash !== false && $lastColon < $lastSlash)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return substr($imageReference, $lastColon + 1) ?: null;
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
|
||||
@@ -1584,6 +1584,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
'save' => 'local_image_found',
|
||||
]);
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
||||
$this->application_deployment_queue->addLogEntry("Image ({$this->production_image_name}) not found on the server. Pulling it from the Docker registry.");
|
||||
$this->execute_remote_command([
|
||||
'docker pull '.escapeshellarg($this->production_image_name).' 2>/dev/null',
|
||||
'ignore_errors' => true,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\GetRollbackImages;
|
||||
use App\Models\Application;
|
||||
use App\Traits\AuditsApplicationSettings;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
@@ -17,7 +18,7 @@ class Rollback extends Component
|
||||
|
||||
public $images = [];
|
||||
|
||||
public ?string $current;
|
||||
public ?string $current = null;
|
||||
|
||||
public array $parameters;
|
||||
|
||||
@@ -82,30 +83,7 @@ class Rollback extends Component
|
||||
$this->authorize('view', $this->application);
|
||||
|
||||
try {
|
||||
$image = $this->application->docker_registry_image_name ?? $this->application->uuid;
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
$output = instant_remote_process([
|
||||
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
||||
], $this->application->destination->server, throwError: false);
|
||||
$current_tag = str($output)->trim()->explode(':');
|
||||
$this->current = data_get($current_tag, 1);
|
||||
|
||||
$output = instant_remote_process([
|
||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
||||
], $this->application->destination->server);
|
||||
$this->images = str($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
||||
return str($item)->contains($image);
|
||||
})->map(function ($item) {
|
||||
$item = str($item)->explode('#');
|
||||
$is_current = $item[1] === $this->current;
|
||||
|
||||
return [
|
||||
'tag' => $item[1],
|
||||
'created_at' => $item[2],
|
||||
'is_current' => $is_current,
|
||||
];
|
||||
})->toArray();
|
||||
}
|
||||
['current' => $this->current, 'images' => $this->images] = GetRollbackImages::run($this->application);
|
||||
$showToast && $this->dispatch('success', 'Images loaded.');
|
||||
|
||||
return [];
|
||||
|
||||
+13
-3
@@ -5688,7 +5688,7 @@
|
||||
"Applications"
|
||||
],
|
||||
"summary": "List Rollback Images",
|
||||
"description": "List available Docker images for rolling back an application. Returns an empty list when the server is unavailable or remote inspection is not possible.",
|
||||
"description": "List available Docker images for rolling back an application: images on the server (`local`) and, when a Docker registry image name is set, images of recent successful deployments in the registry (`registry`). Returns an empty list when the server is unavailable or remote inspection is not possible.",
|
||||
"operationId": "list-application-rollback-images",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -5726,6 +5726,13 @@
|
||||
},
|
||||
"is_current": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"local",
|
||||
"registry"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -13164,7 +13171,7 @@
|
||||
"$ref": "#\/components\/responses\/401"
|
||||
},
|
||||
"403": {
|
||||
"description": "The token needs the deploy ability to set post_install_script_id or public_key_ids."
|
||||
"description": "The token needs the deploy permission for a post-install script or extra SSH keys."
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation failed."
|
||||
@@ -17883,7 +17890,7 @@
|
||||
"Servers"
|
||||
],
|
||||
"summary": "Import server transfer bundle",
|
||||
"description": "Import a server transfer bundle into this Coolify instance (adopt mode by default). This instance then manages the server, and Sentinel reports to it.",
|
||||
"description": "Import a server transfer bundle into this Coolify instance (adopt mode by default). This instance then manages the server, and Sentinel reports to it. Needs a token with the write and deploy permissions.",
|
||||
"operationId": "import-server-transfer-bundle",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
@@ -17924,6 +17931,9 @@
|
||||
"401": {
|
||||
"$ref": "#\/components\/responses\/401"
|
||||
},
|
||||
"403": {
|
||||
"description": "The token needs the write and deploy permissions."
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation failed"
|
||||
}
|
||||
|
||||
+6
-4
@@ -3716,7 +3716,7 @@ paths:
|
||||
tags:
|
||||
- Applications
|
||||
summary: 'List Rollback Images'
|
||||
description: 'List available Docker images for rolling back an application. Returns an empty list when the server is unavailable or remote inspection is not possible.'
|
||||
description: 'List available Docker images for rolling back an application: images on the server (`local`) and, when a Docker registry image name is set, images of recent successful deployments in the registry (`registry`). Returns an empty list when the server is unavailable or remote inspection is not possible.'
|
||||
operationId: list-application-rollback-images
|
||||
parameters:
|
||||
-
|
||||
@@ -3734,7 +3734,7 @@ paths:
|
||||
schema:
|
||||
properties:
|
||||
current: { type: [string, 'null'] }
|
||||
images: { type: array, items: { properties: { tag: { type: string }, created_at: { type: string }, is_current: { type: boolean } }, type: object } }
|
||||
images: { type: array, items: { properties: { tag: { type: string }, created_at: { type: string }, is_current: { type: boolean }, source: { type: string, enum: [local, registry] } }, type: object } }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
@@ -8441,7 +8441,7 @@ paths:
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'403':
|
||||
description: 'The token needs the deploy ability to set post_install_script_id or public_key_ids.'
|
||||
description: 'The token needs the deploy permission for a post-install script or extra SSH keys.'
|
||||
'422':
|
||||
description: 'Validation failed.'
|
||||
'429':
|
||||
@@ -11318,7 +11318,7 @@ paths:
|
||||
tags:
|
||||
- Servers
|
||||
summary: 'Import server transfer bundle'
|
||||
description: 'Import a server transfer bundle into this Coolify instance (adopt mode by default). This instance then manages the server, and Sentinel reports to it.'
|
||||
description: 'Import a server transfer bundle into this Coolify instance (adopt mode by default). This instance then manages the server, and Sentinel reports to it. Needs a token with the write and deploy permissions.'
|
||||
operationId: import-server-transfer-bundle
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -11347,6 +11347,8 @@ paths:
|
||||
description: Imported
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'403':
|
||||
description: 'The token needs the write and deploy permissions.'
|
||||
'422':
|
||||
description: 'Validation failed'
|
||||
security:
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</form>
|
||||
|
||||
<x-application.settings-section id="rollback-images-section" title="Available images"
|
||||
helper="Rollback uses an existing local image without rebuilding the application." flush>
|
||||
helper="Rollback uses an existing image without rebuilding the application. Images in the Docker registry are pulled to the server first." flush>
|
||||
<x-slot:actions>
|
||||
@can('view', $application)
|
||||
<x-forms.button wire:click="loadImages(true)">
|
||||
@@ -41,6 +41,7 @@
|
||||
$isPrTag = preg_match('/^pr-\d+$/', $tag);
|
||||
$isRollbackable = $isCommitSha || $isPrTag;
|
||||
$isCurrent = data_get($image, 'is_current');
|
||||
$isRegistryImage = data_get($image, 'source') === 'registry';
|
||||
@endphp
|
||||
<div
|
||||
class="flex flex-col gap-3 border-b border-neutral-200 px-4 py-3.5 last:border-b-0 sm:flex-row sm:items-center dark:border-white/[0.07]">
|
||||
@@ -58,9 +59,12 @@
|
||||
@elseif (!$isRollbackable)
|
||||
<x-status-badge status="Rollback unavailable" type="neutral" />
|
||||
@endif
|
||||
@if ($isRegistryImage)
|
||||
<x-status-badge status="In registry" type="neutral" />
|
||||
@endif
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-fg-dim">
|
||||
Built {{ $createdAt->diffForHumans() }}
|
||||
{{ $isRegistryImage ? 'Deployed' : 'Built' }} {{ $createdAt->diffForHumans() }}
|
||||
<span class="mx-1 text-neutral-300 dark:text-fg-faint">·</span>
|
||||
{{ $date }}
|
||||
</p>
|
||||
@@ -84,7 +88,7 @@
|
||||
</div>
|
||||
@empty
|
||||
<x-empty title="No rollback images"
|
||||
description="No previous application images are currently stored on this server."
|
||||
description="No previous application images are stored on this server or in the Docker registry."
|
||||
icon-name="layers" />
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\ApplicationsController;
|
||||
|
||||
function currentRollbackImageTag(string $imageReference): ?string
|
||||
{
|
||||
$method = new ReflectionMethod(ApplicationsController::class, 'currentRollbackImageTag');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return $method->invoke(null, $imageReference);
|
||||
}
|
||||
use App\Actions\Application\GetRollbackImages;
|
||||
|
||||
it('extracts the current rollback image tag when the registry includes a port', function () {
|
||||
expect(currentRollbackImageTag('registry.example.com:5000/team/application:commit-sha'))
|
||||
expect(GetRollbackImages::imageTag('registry.example.com:5000/team/application:commit-sha'))
|
||||
->toBe('commit-sha');
|
||||
});
|
||||
|
||||
it('does not treat a digest as the current rollback image tag', function () {
|
||||
expect(currentRollbackImageTag('registry.example.com:5000/team/application@sha256:'.str_repeat('a', 64)))
|
||||
expect(GetRollbackImages::imageTag('registry.example.com:5000/team/application@sha256:'.str_repeat('a', 64)))
|
||||
->toBeNull();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
use App\Actions\Application\GetRollbackImages;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$team = Team::factory()->create();
|
||||
// The server is not reachable, so only registry images are listed and no SSH command runs.
|
||||
$server = Server::factory()->create(['team_id' => $team->id]);
|
||||
$destination = StandaloneDocker::factory()->create(['server_id' => $server->id, 'network' => 'rollback-test']);
|
||||
$environment = Environment::factory()->create(['project_id' => Project::factory()->create(['team_id' => $team->id])->id]);
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'build_pack' => 'nixpacks',
|
||||
'docker_registry_image_name' => 'ghcr.io/coollabs/app',
|
||||
]);
|
||||
});
|
||||
|
||||
function rollbackDeployment(Application $application, string $commit, string $finishedAt, array $attributes = []): void
|
||||
{
|
||||
ApplicationDeploymentQueue::create([
|
||||
'application_id' => $application->id,
|
||||
'deployment_uuid' => fake()->uuid(),
|
||||
'commit' => $commit,
|
||||
'status' => ApplicationDeploymentStatus::FINISHED->value,
|
||||
'pull_request_id' => 0,
|
||||
...$attributes,
|
||||
])->forceFill(['updated_at' => $finishedAt])->saveQuietly();
|
||||
}
|
||||
|
||||
it('lists the commits of successful deployments as registry images, newest first and once per commit', function () {
|
||||
rollbackDeployment($this->application, 'aaaaaaa', '2026-10-01 10:00:00');
|
||||
rollbackDeployment($this->application, 'bbbbbbb', '2026-10-02 10:00:00');
|
||||
rollbackDeployment($this->application, 'aaaaaaa', '2026-10-03 10:00:00');
|
||||
rollbackDeployment($this->application, 'ccccccc', '2026-10-04 10:00:00', ['status' => ApplicationDeploymentStatus::FAILED->value]);
|
||||
rollbackDeployment($this->application, 'ddddddd', '2026-10-04 10:00:00', ['pull_request_id' => 5]);
|
||||
rollbackDeployment($this->application, 'HEAD', '2026-10-04 10:00:00');
|
||||
|
||||
$result = GetRollbackImages::run($this->application);
|
||||
|
||||
expect($result['current'])->toBeNull()
|
||||
->and($result['images'])->toBe([
|
||||
['tag' => 'aaaaaaa', 'created_at' => '2026-10-03 10:00:00', 'is_current' => false, 'source' => 'registry'],
|
||||
['tag' => 'bbbbbbb', 'created_at' => '2026-10-02 10:00:00', 'is_current' => false, 'source' => 'registry'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('limits the registry images to the most recent deployments', function () {
|
||||
foreach (range(1, GetRollbackImages::REGISTRY_DEPLOYMENTS_LIMIT + 2) as $day) {
|
||||
rollbackDeployment($this->application, sprintf('%07d', $day), sprintf('2026-10-%02d 10:00:00', $day));
|
||||
}
|
||||
|
||||
$images = GetRollbackImages::run($this->application)['images'];
|
||||
|
||||
expect($images)->toHaveCount(GetRollbackImages::REGISTRY_DEPLOYMENTS_LIMIT)
|
||||
->and($images[0]['tag'])->toBe(sprintf('%07d', GetRollbackImages::REGISTRY_DEPLOYMENTS_LIMIT + 2));
|
||||
});
|
||||
|
||||
it('lists no registry images when the application does not push commit images', function (array $attributes) {
|
||||
$this->application->update($attributes);
|
||||
rollbackDeployment($this->application, 'aaaaaaa', '2026-10-01 10:00:00');
|
||||
|
||||
expect(GetRollbackImages::run($this->application->fresh())['images'])->toBe([]);
|
||||
})->with([
|
||||
'no registry image name' => [['docker_registry_image_name' => null]],
|
||||
'docker image build pack' => [['build_pack' => 'dockerimage']],
|
||||
'docker compose build pack' => [['build_pack' => 'dockercompose']],
|
||||
'dockerfile without git' => [['build_pack' => 'dockerfile', 'dockerfile' => 'FROM nginx']],
|
||||
]);
|
||||
Reference in New Issue
Block a user