Skip to content

Commit

Permalink
fix(#40): Retry on rate exceeded errors on up
Browse files Browse the repository at this point in the history
  • Loading branch information
garethbowen authored Apr 25, 2024
2 parents 0c5c8e8 + 48e20ef commit d62eacb
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 4 deletions.
11 changes: 7 additions & 4 deletions src/docker-compose-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const composeCommand = (filePaths, ...params) => {
...filePaths.map(filePath => (['-f', filePath])),
...params.filter(param => param).map(param => param.split(' ')),
].flat();
console.log(`Running cmd: ${DOCKER_CLI} ${args.join(' ')}`);

return new Promise((resolve, reject) => {
const proc = childProcess.spawn(DOCKER_CLI, args, { stdio: ['ignore', 'pipe', 'pipe'] });
Expand All @@ -36,20 +37,22 @@ const composeCommand = (filePaths, ...params) => {
});
};

const pull = async (fileName, retry = 100) => {
const retryCmdOnRateExceeded = async (filenames, command, retry = 100) => {
try {
await composeCommand(fileName, 'pull');
await composeCommand(filenames, command);
} catch (err) {
if (isRateExceededError(err) && retry > 0) {
console.warn('Pull rate limit exceeded. Retrying.');
await new Promise(r => setTimeout(r, 1000));
return pull(fileName, --retry);
return retryCmdOnRateExceeded(filenames, command, --retry);
}
throw err;
}
};

const up = (fileNames) => composeCommand(fileNames, 'up -d --remove-orphans');
const pull = async (fileName) => retryCmdOnRateExceeded(fileName, 'pull');

const up = (fileNames) => retryCmdOnRateExceeded(fileNames, 'up -d --remove-orphans');

const validate = async (fileName) => {
try {
Expand Down
50 changes: 50 additions & 0 deletions test/unit/docker-compose-cli.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,56 @@ describe('docker-compose cli', () => {
expect(console.log.calledWith('thing2')).to.equal(true);
});

// https://docs.aws.amazon.com/AmazonECR/latest/userguide/common-errors.html
it('should retry on rate exceeded error', async () => {
const filename = 'path/to/file.yml';
const result = dockerComposeCli.up([filename]);

expect(childProcess.spawn.callCount).to.equal(1);
spawnedProcess.stderrCb('toomanyrequests: Rate exceeded');
spawnedProcess.events.exit(1);

await Promise.resolve();
clock.tick(1000);
await Promise.resolve();

expect(childProcess.spawn.callCount).to.equal(2);
spawnedProcess.stderrCb('toomanyrequests: Rate exceeded');
spawnedProcess.events.exit(1);

await Promise.resolve();
clock.tick(1000);
await Promise.resolve();

expect(childProcess.spawn.callCount).to.equal(3);
spawnedProcess.events.error({ message: 'Unknown: Rate exceeded' });

await Promise.resolve();
clock.tick(1000);
await Promise.resolve();

expect(childProcess.spawn.callCount).to.equal(4);
spawnedProcess.events.exit(0);

await result;
});

it('should throw error after 100 rate exceeded retries', async () => {
const filename = 'path/to/file.yml';
const result = dockerComposeCli.up([filename]);

for (let i = 0; i <= 100; i++) {
expect(childProcess.spawn.callCount).to.equal(i + 1);
spawnedProcess.stderrCb('toomanyrequests: Rate exceeded');
spawnedProcess.events.exit(1);

await Promise.resolve();
clock.tick(1000);
await Promise.resolve();
}
await expect(result).to.be.rejectedWith('toomanyrequests: Rate exceeded');
});

it('should reject on error', async () => {
const filename = 'path/to/filename.yml';
const result = dockerComposeCli.up(filename);
Expand Down

0 comments on commit d62eacb

Please sign in to comment.