Start a dependent if all dependencies are started

The previous behavior required that dependencies were running beefore
starting the dependent service. This made it that services dependent on
a one-shot service would not get started and goes against the default
docker behavior.

Depending on a service to be running will require the implementation of
[long syntax depends_on](https://docs.docker.com/reference/compose-file/services/#long-syntax-1) and the condition
`service_healthy`.

Change-type: patch
Closes: #2409
This commit is contained in:
Felipe Lalanne 2025-03-20 12:57:48 -03:00
parent b8032edc04
commit 7764f98c9d
No known key found for this signature in database
GPG Key ID: 03E696BFD472B26A
5 changed files with 43 additions and 12 deletions

View File

@ -921,19 +921,24 @@ class AppImpl implements App {
volumePairs: Array<ChangingPair<Volume>>,
servicePairs: Array<ChangingPair<Service>>,
): boolean {
// Firstly we check if a dependency is not already running (this is
// Firstly we check if a dependency has already been started (this is
// different to a dependency which is in the servicePairs below, as these
// are services which are changing). We could have a dependency which is
// starting up, but is not yet running.
const depInstallingButNotRunning = _.some(this.services, (svc) => {
const depCreatedButNotStarted = _.some(this.services, (svc) => {
if (target.dependsOn?.includes(svc.serviceName)) {
if (!svc.config.running) {
if (
svc.status === 'Installing' ||
svc.startedAt == null ||
svc.createdAt == null ||
svc.startedAt < svc.createdAt
) {
return true;
}
}
});
if (depInstallingButNotRunning) {
if (depCreatedButNotStarted) {
return false;
}

View File

@ -61,6 +61,7 @@ class ServiceImpl implements Service {
public dockerImageId: string | null;
public status: ServiceStatus;
public createdAt: Date | null;
public startedAt: Date | null;
private static configArrayFields: ServiceConfigArrayField[] = [
'volumes',
@ -476,6 +477,7 @@ class ServiceImpl implements Service {
}
svc.createdAt = new Date(container.Created);
svc.startedAt = new Date(container.State.StartedAt);
svc.containerId = container.Id;
svc.exitErrorMessage = container.State.Error;

View File

@ -373,6 +373,7 @@ export interface Service {
// from docker
status: ServiceStatus;
createdAt: Date | null;
startedAt: Date | null;
hasNetwork(networkName: string): boolean;
hasVolume(volumeName: string): boolean;

View File

@ -1128,11 +1128,20 @@ describe('compose/application-manager', () => {
const { currentApps, availableImages, downloading, containerIdsByAppId } =
createCurrentState({
services: [
await createService({
image: 'dep-image',
serviceName: 'dep',
commit: 'new-release',
}),
await createService(
{
image: 'dep-image',
serviceName: 'dep',
commit: 'new-release',
},
{
state: {
createdAt: new Date(Date.now() - 5 * 1000),
// Container was started 5 after creation
startedAt: new Date(),
},
},
),
],
networks: [DEFAULT_NETWORK],
images: [

View File

@ -1458,7 +1458,14 @@ describe('compose/app', () => {
services: [
await createService(
{ appId: 1, serviceName: 'dep' },
{ state: { containerId: 'dep-id' } },
{
state: {
containerId: 'dep-id',
createdAt: new Date(Date.now() - 5 * 1000),
// Container was started 5 after creation
startedAt: new Date(),
},
},
),
],
networks: [DEFAULT_NETWORK],
@ -1475,7 +1482,7 @@ describe('compose/app', () => {
.that.deep.includes({ serviceName: 'main' });
});
it('should not start a container when it depends on a service that is not running', async () => {
it('should not start a container when it depends on a service that has not been started yet', async () => {
const current = createApp({
services: [
await createService(
@ -1535,7 +1542,14 @@ describe('compose/app', () => {
services: [
await createService(
{ appId: 1, serviceName: 'dep' },
{ state: { containerId: 'dep-id' } },
{
state: {
containerId: 'dep-id',
createdAt: new Date(Date.now() - 5 * 1000),
// Container was started 5 after creation
startedAt: new Date(),
},
},
),
],
networks: [DEFAULT_NETWORK],