Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashmate): handle docker pull error on images update #1685

Merged
merged 8 commits into from
May 6, 2024
8 changes: 7 additions & 1 deletion packages/dashmate/src/commands/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export default class UpdateCommand extends ConfigBaseCommand {
) {
const updateInfo = await updateNode(config);

const colors = {
updated: chalk.yellow,
'up to date': chalk.green,
error: chalk.red,
};

// Draw table or show json
printArrayOfObjects(updateInfo
.reduce(
Expand All @@ -43,7 +49,7 @@ export default class UpdateCommand extends ConfigBaseCommand {
}) => ([
...acc,
format === OUTPUT_FORMATS.PLAIN
? { Service: title, Image: image, Updated: updated ? chalk.yellow('updated') : chalk.green('up to date') }
? { Service: title, Image: image, Updated: colors[updated](updated) }
: {
name, title, updated, image,
},
Expand Down
26 changes: 21 additions & 5 deletions packages/dashmate/src/update/updateNodeFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,17 @@ export default function updateNodeFactory(getServiceList, docker) {

return Promise.all(
lodash.uniqBy(services, 'image')
.map(async ({ name, image, title }) => new Promise((resolve, reject) => {
.map(async ({ name, image, title }) => new Promise((resolve) => {
docker.pull(image, (err, stream) => {
if (err) {
reject(err);
if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.error(`Failed to update ${name} service, image ${image}, error: ${err}`);
}

resolve({
name, title, image, updated: 'error',
});
} else {
let updated = null;

Expand All @@ -36,12 +43,21 @@ export default function updateNodeFactory(getServiceList, docker) {
.filter((obj) => obj.status.startsWith('Status: '));
shumkov marked this conversation as resolved.
Show resolved Hide resolved

if (status?.status.includes('Image is up to date for')) {
updated = false;
updated = 'up to date';
} else if (status?.status.includes('Downloaded newer image for')) {
updated = true;
updated = 'updated';
}
});
stream.on('error', reject);
stream.on('error', () => {
if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.error(`Failed to update ${name} service, image ${image}, error: ${err}`);
}

resolve({
name, title, image, updated: 'error',
});
});
stream.on('end', () => resolve({
name, title, image, updated,
}));
Expand Down
40 changes: 40 additions & 0 deletions packages/dashmate/test/unit/commands/update.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,44 @@ describe('Update command', () => {
expect(mockGetServicesList).to.have.been.calledOnceWithExactly(config);
expect(mockDocker.pull).to.have.been.calledOnceWith(mockServicesList[0].image);
});

it('should update other services if one of them fails', async function it() {
const command = new UpdateCommand();
mockDockerResponse = { status: 'Status: Image is up to date for' };
mockServicesList = [{ name: 'fake', image: 'fake', title: 'FAKE' },
{ name: 'fake_docker_pull_error', image: 'fake_err_image', title: 'FAKE_ERROR' }];

// test docker.pull returns error
mockDocker = {
pull: this.sinon.stub()
.callsFake((image, cb) => (image === mockServicesList[1].image ? cb(new Error(), null)
: cb(false, mockDockerStream))),
};

let updateNode = updateNodeFactory(mockGetServicesList, mockDocker);

await command.runWithDependencies({}, { format: 'json' }, mockDocker, config, updateNode);

expect(mockGetServicesList).to.have.been.calledOnceWithExactly(config);
expect(mockDocker.pull.firstCall.firstArg).to.equal(mockServicesList[0].image);
expect(mockDocker.pull.secondCall.firstArg).to.equal(mockServicesList[1].image);

// test docker.pull stream returns error
mockDocker = { pull: this.sinon.stub().callsFake((image, cb) => cb(false, mockDockerStream)) };
mockDockerStream = {
on: this.sinon.stub().callsFake((channel, cb) => (channel === 'error' ? cb(new Error()) : null)),
};

// reset
mockGetServicesList = this.sinon.stub().callsFake(() => mockServicesList);
mockDocker = { pull: this.sinon.stub().callsFake((image, cb) => cb(false, mockDockerStream)) };

updateNode = updateNodeFactory(mockGetServicesList, mockDocker);

await command.runWithDependencies({}, { format: 'json' }, mockDocker, config, updateNode);

expect(mockGetServicesList).to.have.been.calledOnceWithExactly(config);
expect(mockDocker.pull.firstCall.firstArg).to.equal(mockServicesList[0].image);
expect(mockDocker.pull.secondCall.firstArg).to.equal(mockServicesList[1].image);
});
});
Loading