Skip to content

Commit e412bfd

Browse files
Merge pull request #784 from smartlyio/anttiharju/docker-compose
Add fallback to `docker compose` for `docker compose` for `v2`
2 parents ad2a7cb + ae14c1b commit e412bfd

2 files changed

Lines changed: 96 additions & 2 deletions

File tree

__tests__/compose.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import {Context} from '../src/context';
2-
import {runCompose, runAction, runCleanup, ComposeError} from '../src/compose';
2+
import {
3+
runCompose,
4+
runAction,
5+
runCleanup,
6+
ComposeError,
7+
composeCommand
8+
} from '../src/compose';
39
import {mocked} from 'jest-mock';
410
import {exec} from '@actions/exec';
511

@@ -11,12 +17,61 @@ const OLD_ENV = process.env;
1117
beforeEach(() => {
1218
process.env = {...OLD_ENV};
1319
mocked(exec).mockReset();
20+
composeCommand.init(); // In most tests we are not interested in testing the docker-compose/docker compose fallback logic
1421
});
1522

1623
afterEach(() => {
1724
process.env = OLD_ENV;
1825
});
1926

27+
describe('fall back to docker compose', () => {
28+
test('basic command', async () => {
29+
composeCommand.reset(); // Here we are interested in the fallback logic
30+
const command = 'build';
31+
const projectName = 'test-name';
32+
const context: Context = {
33+
composeFiles: ['docker-compose.ci.yml'],
34+
serviceName: 'test',
35+
composeCommand: 'up',
36+
composeArguments: ['--abort-on-container-exit'],
37+
runCommand: [],
38+
build: true,
39+
buildArgs: [],
40+
registryCache: 'hub.artifactor.ee',
41+
push: false,
42+
postCommand: ['down --remove-orphans --volumes', 'rm -f'],
43+
isPost: false,
44+
projectName: projectName
45+
};
46+
47+
const mockExec = mocked(exec);
48+
mockExec.mockRejectedValueOnce(new Error('docker-compose not found'));
49+
mockExec.mockResolvedValue(0);
50+
51+
await runCompose(command, [], context);
52+
53+
const calls = mockExec.mock.calls;
54+
expect(calls.length).toBe(2);
55+
56+
const expectedArgs: string[] = [
57+
'-f',
58+
context.composeFiles[0],
59+
'-p',
60+
projectName,
61+
command
62+
];
63+
expect(mockExec).toHaveBeenNthCalledWith(1, 'docker-compose', [
64+
'--version'
65+
]);
66+
expect(mockExec).toHaveBeenNthCalledWith(
67+
2,
68+
'docker compose',
69+
expectedArgs,
70+
undefined
71+
);
72+
});
73+
});
74+
2075
describe('run docker-compose', () => {
2176
test('basic command', async () => {
2277
const command = 'build';

src/compose.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,44 @@ export class ComposeError extends Error {
1313
}
1414
}
1515

16+
class ComposeCommand {
17+
// Not using a constructor as the singleton implementation due to await, constructors can't be async.
18+
private initialised = false;
19+
private composeCommand = 'docker-compose';
20+
21+
/**
22+
* Checks docker-compose availability, using docker compose as fallback.
23+
*
24+
* @returns {Promise<string>} 'docker-compose' or 'docker compose'
25+
*/
26+
async get(): Promise<string> {
27+
if (!this.initialised) {
28+
this.initialised = true;
29+
try {
30+
await exec.exec('docker-compose', ['--version']);
31+
} catch (error) {
32+
console.warn(
33+
'docker-compose not available, falling back to docker compose'
34+
);
35+
this.composeCommand = 'docker compose';
36+
}
37+
}
38+
return this.composeCommand;
39+
}
40+
41+
init(): void {
42+
this.initialised = true;
43+
this.composeCommand = 'docker-compose';
44+
}
45+
46+
reset(): void {
47+
this.initialised = false;
48+
this.composeCommand = 'docker-compose';
49+
}
50+
}
51+
52+
export const composeCommand = new ComposeCommand();
53+
1654
export async function runCompose(
1755
command: string,
1856
args: string[],
@@ -30,7 +68,8 @@ export async function runCompose(
3068
for (const part of args) {
3169
composeArgs.push(part);
3270
}
33-
return await exec.exec('docker-compose', composeArgs, execOptions);
71+
const dockerCompose = await composeCommand.get();
72+
return await exec.exec(dockerCompose, composeArgs, execOptions);
3473
}
3574

3675
export async function getContainerId(context: Context): Promise<string | null> {

0 commit comments

Comments
 (0)