mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-19 05:37:53 +00:00
Migrate volume-manager tests to integration
Now the tests are ran against the actual docker engine instead of against mockerode. The new tests actually caught a bug in `volumeManager.removeOrphanedVolumes`, where that function would try to remove volumes for stopped containers, causing an exception. This commit also fixes that bug.
This commit is contained in:
parent
18c2f8cec9
commit
a69fbf6eac
@ -117,7 +117,7 @@ export async function removeOrphanedVolumes(
|
||||
// *all* containers. This means we don't remove
|
||||
// something that's part of a sideloaded container
|
||||
const [dockerContainers, dockerVolumes] = await Promise.all([
|
||||
docker.listContainers(),
|
||||
docker.listContainers({ all: true }),
|
||||
docker.listVolumes(),
|
||||
]);
|
||||
|
||||
|
305
test/integration/compose/volume-manager.spec.ts
Normal file
305
test/integration/compose/volume-manager.spec.ts
Normal file
@ -0,0 +1,305 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import * as volumeManager from '~/src/compose/volume-manager';
|
||||
import Volume from '~/src/compose/volume';
|
||||
import { createDockerImage } from '~/test-lib/docker-helper';
|
||||
|
||||
import * as Docker from 'dockerode';
|
||||
|
||||
describe('compose/volume-manager', () => {
|
||||
const docker = new Docker();
|
||||
after(async () => {
|
||||
await docker.pruneContainers();
|
||||
await docker.pruneVolumes();
|
||||
await docker.pruneImages();
|
||||
});
|
||||
|
||||
describe('Retrieving volumes from the engine', () => {
|
||||
it('gets all supervised Volumes', async () => {
|
||||
// Setup volume data
|
||||
await Promise.all([
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(1, 'redis'),
|
||||
// Recently created volumes contain io.balena.supervised label
|
||||
Labels: { 'io.balena.supervised': '1' },
|
||||
}),
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(1, 'mysql'),
|
||||
// Recently created volumes contain io.balena.supervised label and app-uuid
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
'io.balena.app-uuid': 'deadbeef',
|
||||
},
|
||||
}),
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(2, 'backend'),
|
||||
// Old Volumes will not have labels
|
||||
}),
|
||||
// Volume not created by the Supervisor
|
||||
docker.createVolume({ Name: 'user_created_volume' }),
|
||||
docker.createVolume({
|
||||
Name: 'decoy',
|
||||
// Added decoy to really test the inference (should not return)
|
||||
Labels: { 'io.balena.supervised': '1' },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Perform test
|
||||
await expect(volumeManager.getAll()).to.eventually.have.deep.members([
|
||||
{
|
||||
appId: 1,
|
||||
appUuid: undefined,
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
},
|
||||
name: 'redis',
|
||||
},
|
||||
{
|
||||
appId: 1,
|
||||
appUuid: 'deadbeef',
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {
|
||||
'io.balena.supervised': '1',
|
||||
'io.balena.app-uuid': 'deadbeef',
|
||||
},
|
||||
},
|
||||
name: 'mysql',
|
||||
},
|
||||
{
|
||||
appId: 2,
|
||||
appUuid: undefined,
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {},
|
||||
},
|
||||
name: 'backend',
|
||||
},
|
||||
]);
|
||||
|
||||
// Cleanup volumes
|
||||
await Promise.all([
|
||||
docker.getVolume(Volume.generateDockerName(1, 'redis')).remove(),
|
||||
docker.getVolume(Volume.generateDockerName(1, 'mysql')).remove(),
|
||||
docker.getVolume(Volume.generateDockerName(2, 'backend')).remove(),
|
||||
docker.getVolume('user_created_volume').remove(),
|
||||
docker.getVolume('decoy').remove(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('can parse null Volumes', async () => {
|
||||
// Perform test with no volumes
|
||||
await expect(volumeManager.getAll()).to.eventually.deep.equal([]);
|
||||
});
|
||||
|
||||
it('gets the volume for specific application', async () => {
|
||||
// Setup volume data
|
||||
await Promise.all([
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(111, 'app'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(222, 'otherApp'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
// Perform test
|
||||
await expect(volumeManager.getAllByAppId(111)).to.eventually.deep.equal([
|
||||
{
|
||||
appId: 111,
|
||||
appUuid: undefined,
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
},
|
||||
name: 'app',
|
||||
},
|
||||
]);
|
||||
|
||||
// Cleanup volumes
|
||||
await Promise.all([
|
||||
docker.getVolume(Volume.generateDockerName(111, 'app')).remove(),
|
||||
docker.getVolume(Volume.generateDockerName(222, 'otherApp')).remove(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Creating volumes', () => {
|
||||
it('creates a volume if it does not exist', async () => {
|
||||
// The volume does not exist on the engine before
|
||||
await expect(
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).inspect(),
|
||||
).to.be.rejected;
|
||||
|
||||
// Volume to create
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
|
||||
// Create volume
|
||||
await volumeManager.create(volume);
|
||||
|
||||
// Check the volume should have been created
|
||||
await expect(
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).inspect(),
|
||||
).to.not.be.rejected;
|
||||
|
||||
// Cleanup volumes
|
||||
await Promise.all([
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).remove(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not try to create a volume that already exists', async () => {
|
||||
// Setup volume data
|
||||
await docker.createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
});
|
||||
|
||||
// Create compose object for volume already set up in mock engine
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
sinon.spy(volume, 'create');
|
||||
|
||||
// Create volume
|
||||
await volumeManager.create(volume);
|
||||
|
||||
// Check volume was not created
|
||||
expect(volume.create).to.not.have.been.called;
|
||||
|
||||
// Cleanup volumes
|
||||
await Promise.all([
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).remove(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Removing volumes', () => {
|
||||
it('removes a volume if it exists', async () => {
|
||||
// Setup volume data
|
||||
await Promise.all([
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
// Volume to remove
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
|
||||
// Remove volume
|
||||
await volumeManager.remove(volume);
|
||||
|
||||
// Check volume was removed
|
||||
await expect(
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).inspect(),
|
||||
).to.be.rejected;
|
||||
});
|
||||
|
||||
it('does nothing on removal if the volume does not exist', async () => {
|
||||
// Setup volume data
|
||||
await Promise.all([
|
||||
docker.createVolume({
|
||||
Name: 'decoy-volume',
|
||||
}),
|
||||
]);
|
||||
|
||||
// Volume to remove
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
|
||||
// Remove volume
|
||||
await expect(volumeManager.remove(volume)).to.not.be.rejected;
|
||||
|
||||
// Cleanup volumes
|
||||
await Promise.all([docker.getVolume('decoy-volume').remove()]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Removing orphaned volumes', () => {
|
||||
it('removes any remaining unreferenced volumes after services have been deleted', async () => {
|
||||
// Setup volume data
|
||||
await Promise.all([
|
||||
docker.createVolume({
|
||||
Name: 'some-volume',
|
||||
}),
|
||||
// This volume is still referenced in the target state
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
docker.createVolume({
|
||||
Name: Volume.generateDockerName(222, 'old'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
// This volume is referenced by a container
|
||||
docker.createVolume({
|
||||
Name: 'other-volume',
|
||||
}),
|
||||
]);
|
||||
|
||||
// Create an empty image
|
||||
await createDockerImage('hello', ['io.balena.testing=1'], docker);
|
||||
|
||||
// Create a container from the image
|
||||
const { id: containerId } = await docker.createContainer({
|
||||
Image: 'hello',
|
||||
Cmd: ['true'],
|
||||
HostConfig: {
|
||||
Binds: ['other-volume:/data'],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
volumeManager.removeOrphanedVolumes([
|
||||
// Keep any volumes in the target state
|
||||
Volume.generateDockerName(111, 'main'),
|
||||
]),
|
||||
).to.not.be.rejected;
|
||||
|
||||
// All volumes should have been deleted
|
||||
expect(await docker.listVolumes())
|
||||
.to.have.property('Volumes')
|
||||
.that.has.lengthOf(2);
|
||||
|
||||
// Reference volume should have been kept
|
||||
await expect(
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).inspect(),
|
||||
).to.not.be.rejected;
|
||||
await expect(docker.getVolume('other-volume').inspect()).to.not.be
|
||||
.rejected;
|
||||
|
||||
// Cleanup
|
||||
await Promise.all([
|
||||
docker.getVolume(Volume.generateDockerName(111, 'main')).remove(),
|
||||
docker.getContainer(containerId).remove(),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
docker.getImage('hello').remove(),
|
||||
docker.getVolume('other-volume').remove(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,360 +0,0 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import {
|
||||
createVolume,
|
||||
createContainer,
|
||||
withMockerode,
|
||||
} from '~/test-lib/mockerode';
|
||||
import * as volumeManager from '~/src/compose/volume-manager';
|
||||
import log from '~/lib/supervisor-console';
|
||||
import Volume from '~/src/compose/volume';
|
||||
|
||||
describe('compose/volume-manager', () => {
|
||||
describe('Retrieving volumes from the engine', () => {
|
||||
let logDebug: sinon.SinonStub;
|
||||
before(() => {
|
||||
logDebug = sinon.stub(log, 'debug');
|
||||
});
|
||||
after(() => {
|
||||
logDebug.restore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
logDebug.reset();
|
||||
});
|
||||
|
||||
it('gets all supervised Volumes', async () => {
|
||||
// Setup volume data
|
||||
const volumeData = [
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(1, 'redis'),
|
||||
// Recently created volumes contain io.balena.supervised label
|
||||
Labels: { 'io.balena.supervised': '1' },
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(1, 'mysql'),
|
||||
// Recently created volumes contain io.balena.supervised label and app-uuid
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
'io.balena.app-uuid': 'deadbeef',
|
||||
},
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(1, 'backend'),
|
||||
// Old Volumes will not have labels
|
||||
}),
|
||||
// Volume not created by the Supervisor
|
||||
createVolume({ Name: 'user_created_volume' }),
|
||||
createVolume({
|
||||
Name: 'decoy',
|
||||
// Added decoy to really test the inference (should not return)
|
||||
Labels: { 'io.balena.supervised': '1' },
|
||||
}),
|
||||
];
|
||||
|
||||
// Perform test
|
||||
await withMockerode(
|
||||
async () => {
|
||||
await expect(volumeManager.getAll()).to.eventually.deep.equal([
|
||||
{
|
||||
appId: 1,
|
||||
appUuid: undefined,
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
},
|
||||
name: 'redis',
|
||||
},
|
||||
{
|
||||
appId: 1,
|
||||
appUuid: 'deadbeef',
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {
|
||||
'io.balena.supervised': '1',
|
||||
'io.balena.app-uuid': 'deadbeef',
|
||||
},
|
||||
},
|
||||
name: 'mysql',
|
||||
},
|
||||
{
|
||||
appId: 1,
|
||||
appUuid: undefined,
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {},
|
||||
},
|
||||
name: 'backend',
|
||||
},
|
||||
]);
|
||||
// Check that debug message was logged saying we found a Volume not created by us
|
||||
expect(logDebug.lastCall.lastArg).to.equal(
|
||||
'Found unmanaged or anonymous Volume: decoy',
|
||||
);
|
||||
},
|
||||
{ volumes: volumeData },
|
||||
);
|
||||
});
|
||||
|
||||
it('can parse null Volumes', async () => {
|
||||
// Perform test with no volumes
|
||||
await withMockerode(async () => {
|
||||
await expect(volumeManager.getAll()).to.eventually.deep.equal([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('gets the volume for specific application', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(111, 'app'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(222, 'otherApp'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
];
|
||||
// Perform test
|
||||
await withMockerode(
|
||||
async () => {
|
||||
await expect(
|
||||
volumeManager.getAllByAppId(111),
|
||||
).to.eventually.deep.equal([
|
||||
{
|
||||
appId: 111,
|
||||
appUuid: undefined,
|
||||
config: {
|
||||
driver: 'local',
|
||||
driverOpts: {},
|
||||
labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
},
|
||||
name: 'app',
|
||||
},
|
||||
]);
|
||||
},
|
||||
{ volumes },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Creating volumes', () => {
|
||||
it('creates a volume if it does not exist', async () => {
|
||||
// Perform test
|
||||
await withMockerode(async (mockerode) => {
|
||||
// The volume does not exist on the engine before
|
||||
expect(
|
||||
mockerode.getVolume(Volume.generateDockerName(111, 'main')).inspect(),
|
||||
).to.be.rejected;
|
||||
|
||||
// Volume to create
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
sinon.spy(volume, 'create');
|
||||
|
||||
// Create volume
|
||||
await volumeManager.create(volume);
|
||||
|
||||
// Check that the creation function was called
|
||||
expect(volume.create).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not try to create a volume that already exists', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
];
|
||||
// Perform test
|
||||
await withMockerode(
|
||||
async () => {
|
||||
// Create compose object for volume already set up in mock engine
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
sinon.spy(volume, 'create');
|
||||
|
||||
// Create volume
|
||||
await volumeManager.create(volume);
|
||||
|
||||
// Check volume was not created
|
||||
expect(volume.create).to.not.have.been.called;
|
||||
},
|
||||
{ volumes },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Removing volumes', () => {
|
||||
it('removes a volume if it exists', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
];
|
||||
// Perform test
|
||||
await withMockerode(
|
||||
async (mockerode) => {
|
||||
// Volume to remove
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
sinon.spy(volume, 'remove');
|
||||
|
||||
// Remove volume
|
||||
await volumeManager.remove(volume);
|
||||
|
||||
// Check volume was removed
|
||||
expect(volume.remove).to.be.calledOnce;
|
||||
expect(mockerode.removeVolume).to.have.been.calledOnceWith(
|
||||
Volume.generateDockerName(111, 'main'),
|
||||
);
|
||||
},
|
||||
{ volumes },
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing on removal if the volume does not exist', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: 'decoy-volume',
|
||||
}),
|
||||
];
|
||||
|
||||
// Perform test
|
||||
await withMockerode(
|
||||
async (mockerode) => {
|
||||
// Volume to remove
|
||||
const volume = Volume.fromComposeObject('main', 111, 'deadbeef', {});
|
||||
sinon.spy(volume, 'remove');
|
||||
|
||||
// Remove volume
|
||||
await expect(volumeManager.remove(volume)).to.not.be.rejected;
|
||||
expect(mockerode.removeVolume).to.not.have.been.called;
|
||||
},
|
||||
{ volumes },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Removing orphaned volumes', () => {
|
||||
it('removes any remaining unreferenced volumes after services have been deleted', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: 'some-volume',
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
await withMockerode(
|
||||
async (mockerode) => {
|
||||
await volumeManager.removeOrphanedVolumes([]);
|
||||
|
||||
expect(mockerode.removeVolume).to.have.been.calledTwice;
|
||||
expect(mockerode.removeVolume).to.have.been.calledWith('some-volume');
|
||||
expect(mockerode.removeVolume).to.have.been.calledWith(
|
||||
Volume.generateDockerName(111, 'main'),
|
||||
);
|
||||
},
|
||||
{ volumes },
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps volumes still referenced in target state', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: 'some-volume',
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(222, 'old'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
await withMockerode(
|
||||
async (mockerode) => {
|
||||
await volumeManager.removeOrphanedVolumes([
|
||||
Volume.generateDockerName(111, 'main'),
|
||||
]);
|
||||
|
||||
expect(mockerode.removeVolume).to.have.been.calledTwice;
|
||||
expect(mockerode.removeVolume).to.have.been.calledWith('some-volume');
|
||||
expect(mockerode.removeVolume).to.have.been.calledWith(
|
||||
Volume.generateDockerName(222, 'old'),
|
||||
);
|
||||
},
|
||||
{ volumes },
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps volumes still referenced by a container', async () => {
|
||||
// Setup volume data
|
||||
const volumes = [
|
||||
createVolume({
|
||||
Name: 'some-volume',
|
||||
}),
|
||||
createVolume({
|
||||
Name: Volume.generateDockerName(111, 'main'),
|
||||
Labels: {
|
||||
'io.balena.supervised': '1',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const containers = [
|
||||
createContainer({
|
||||
Id: 'some-service',
|
||||
Mounts: [
|
||||
{
|
||||
Name: 'some-volume',
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
await withMockerode(
|
||||
async (mockerode) => {
|
||||
await volumeManager.removeOrphanedVolumes([]);
|
||||
|
||||
// Container that has a volume should not be removed
|
||||
expect(mockerode.removeVolume).to.have.been.calledOnceWith(
|
||||
Volume.generateDockerName(111, 'main'),
|
||||
);
|
||||
},
|
||||
{ volumes, containers },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
28
test/lib/docker-helper.ts
Normal file
28
test/lib/docker-helper.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import * as Docker from 'dockerode';
|
||||
import * as tar from 'tar-stream';
|
||||
|
||||
// Creates an image from scratch with just some labels
|
||||
export async function createDockerImage(
|
||||
name: string,
|
||||
labels: [string, ...string[]],
|
||||
docker = new Docker(),
|
||||
) {
|
||||
const pack = tar.pack(); // pack is a streams2 stream
|
||||
pack.entry(
|
||||
{ name: 'Dockerfile' },
|
||||
['FROM scratch'].concat(labels.map((l) => `LABEL ${l}`)).join('\n'),
|
||||
(err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
pack.finalize();
|
||||
},
|
||||
);
|
||||
// Create an empty image
|
||||
const stream = await docker.buildImage(pack, { t: name });
|
||||
return await new Promise((resolve, reject) => {
|
||||
docker.modem.followProgress(stream, (err: any, res: any) =>
|
||||
err ? reject(err) : resolve(res),
|
||||
);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user