From 6db1f03411897ec6f8bcf6433782557c75864bd7 Mon Sep 17 00:00:00 2001 From: Cameron Diver Date: Thu, 5 Dec 2019 13:13:04 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E2=AC=87=EF=B8=8F=20Force=20a=20regular?= =?UTF-8?q?=20pull=20when=20moving=20from=20v2=20to=20v3=20deltas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-type: patch Fixes: #1072 Signed-off-by: Cameron Diver --- package-lock.json | 6 +-- package.json | 2 +- src/lib/docker-utils.ts | 27 ++++++++++ test/25-deltas.spec.ts | 106 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 test/25-deltas.spec.ts diff --git a/package-lock.json b/package-lock.json index 2941b040..59b2cc87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -256,9 +256,9 @@ "dev": true }, "@types/dockerode": { - "version": "2.5.20", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.20.tgz", - "integrity": "sha512-g2eM9q+pur7iZc897K/OSq8sCL7VdVcCPzNkdeTukUokfvgl3TaP+nT7G8BMpnSSojrJFKl7VdTciP7hbVgfKA==", + "version": "2.5.21", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.21.tgz", + "integrity": "sha512-Y9kVV7Umw0SAOsGVp06VGjlAiZbjALNhDIw69NeWNEqfI++7nQijzjWsepOkUjKHr5CrJWgK4v++6a+Ms0G/6A==", "dev": true, "requires": { "@types/node": "*" diff --git a/package.json b/package.json index 5506a7e4..a2ae0772 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.2", "@types/common-tags": "^1.8.0", - "@types/dockerode": "^2.5.20", + "@types/dockerode": "^2.5.21", "@types/event-stream": "^3.3.34", "@types/express": "^4.11.1", "@types/knex": "^0.14.14", diff --git a/src/lib/docker-utils.ts b/src/lib/docker-utils.ts index 2d742779..4e761952 100644 --- a/src/lib/docker-utils.ts +++ b/src/lib/docker-utils.ts @@ -89,6 +89,20 @@ export class DockerUtils extends DockerToolbelt { return await this.fetchImageWithProgress(imgDest, deltaOpts, onProgress); } + // We need to make sure that we're not trying to apply a + // v3 delta on top of a v2 delta, as this will cause the + // update to fail, and we must fall back to a standard + // image pull + if ( + deltaOpts.deltaVersion === 3 && + (await DockerUtils.isV2DeltaImage(this, deltaOpts.deltaSourceId)) + ) { + logFn( + `Cannot create a delta from V2 to V3, falling back to regular pull`, + ); + return await this.fetchImageWithProgress(imgDest, deltaOpts, onProgress); + } + // Since the supevisor never calls this function with a source anymore, // this should never happen, but w ehandle it anyway if (deltaOpts.deltaSource == null) { @@ -317,6 +331,19 @@ export class DockerUtils extends DockerToolbelt { return (await docker.getImage(deltaImg).inspect()).Id; } + public static async isV2DeltaImage( + docker: DockerUtils, + imageName: string, + ): Promise { + const inspect = await docker.getImage(imageName).inspect(); + + // It's extremely unlikely that an image is valid if + // it's smaller than 40 bytes, but a v2 delta always is. + // For this reason, this is the method that we use to + // detect when an image is a v2 delta + return inspect.Size < 40 && inspect.VirtualSize < 40; + } + private getAuthToken = memoizee( async ( srcInfo: ImageNameParts, diff --git a/test/25-deltas.spec.ts b/test/25-deltas.spec.ts new file mode 100644 index 00000000..1f239a2a --- /dev/null +++ b/test/25-deltas.spec.ts @@ -0,0 +1,106 @@ +import { expect } from 'chai'; +import { stub } from 'sinon'; + +import DockerUtils from '../src/lib/docker-utils'; + +const dockerUtils = new DockerUtils({}); + +describe('Deltas', () => { + it('should correctly detect a V2 delta', async () => { + const imageStub = stub(dockerUtils, 'getImage').returns({ + inspect: () => { + return Promise.resolve({ + Id: + 'sha256:34ec91fe6e08cb0f867bbc069c5f499d39297eb8e874bb8ce9707537d983bcbc', + RepoTags: [], + RepoDigests: [], + Parent: '', + Comment: '', + Created: '2019-12-05T10:20:51.516Z', + Container: '', + ContainerConfig: { + Hostname: '', + Domainname: '', + User: '', + AttachStdin: false, + AttachStdout: false, + AttachStderr: false, + Tty: false, + OpenStdin: false, + StdinOnce: false, + Env: null, + Cmd: null, + Image: '', + Volumes: null, + WorkingDir: '', + Entrypoint: null, + OnBuild: null, + Labels: null, + }, + DockerVersion: '', + Author: '', + Config: { + Hostname: '7675a23f4fdc', + Domainname: '', + User: '', + AttachStdin: false, + AttachStdout: false, + AttachStderr: false, + Tty: false, + OpenStdin: false, + StdinOnce: false, + Env: [ + 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + 'TINI_VERSION=0.14.0', + 'LC_ALL=C.UTF-8', + 'DEBIAN_FRONTEND=noninteractive', + 'UDEV=on', + 'container=docker', + 'test=123', + ], + Cmd: [ + '/bin/sh', + '-c', + "while true; do echo 'hello'; sleep 10; done;", + ], + ArgsEscaped: true, + Image: + 'sha256:b24946093df7157727b20934d11a7287359d8de42d8a80030f51f46a73d645ec', + Volumes: { + '/sys/fs/cgroup': {}, + }, + WorkingDir: '', + Entrypoint: ['/usr/bin/entry.sh'], + OnBuild: [], + Labels: { + 'io.resin.architecture': 'amd64', + 'io.resin.device-type': 'intel-nuc', + }, + StopSignal: '37', + }, + Architecture: '', + Os: 'linux', + Size: 17, + VirtualSize: 17, + GraphDriver: { + Data: null, + Name: 'aufs', + }, + RootFS: { + Type: 'layers', + Layers: [ + 'sha256:c6e6cd4f95ef00e62f5c9df5798393470c991ca0148cb1e434b28101ed4219d3', + ], + }, + Metadata: { + LastTagTime: '0001-01-01T00:00:00Z', + }, + }); + }, + } as any); + + expect(await DockerUtils.isV2DeltaImage(dockerUtils, 'test')).to.be.true; + expect(imageStub.callCount).to.equal(1); + imageStub.restore(); + }); +});