volumes: Allow the use of the 'driver' setting in volumes

A compose file can now contain a volume which uses a different driver
from the default one; local.

Change-type: patch
Signed-off-by: Rich Bayliss <rich@balena.io>
This commit is contained in:
Rich Bayliss 2019-12-02 10:05:31 +00:00
parent a29784a58c
commit c3cf8fbca2
No known key found for this signature in database
GPG Key ID: E53C4B4D18499E1A
3 changed files with 63 additions and 14 deletions

View File

@ -666,11 +666,12 @@ module.exports = class ApplicationManager extends EventEmitter
Promise.map(JSON.parse(app.services), (service) => @createTargetService(service, configOpts)) Promise.map(JSON.parse(app.services), (service) => @createTargetService(service, configOpts))
.then (services) => .then (services) =>
# If a named volume is defined in a service, we add it app-wide so that we can track it and purge it # If a named volume is defined in a service but NOT in the volumes of the compose file, we add it app-wide so that we can track it and purge it
# !! DEPRECATED, WILL BE REMOVED IN NEXT MAJOR RELEASE !!
for s in services for s in services
serviceNamedVolumes = s.getNamedVolumes() serviceNamedVolumes = s.getNamedVolumes()
for name in serviceNamedVolumes for name in serviceNamedVolumes
volumes[name] = @createTargetVolume(name, app.appId, { labels: {} }) volumes[name] ?= @createTargetVolume(name, app.appId, { labels: {} })
outApp = { outApp = {
appId: app.appId appId: app.appId
name: app.name name: app.name

View File

@ -17,34 +17,28 @@ export interface VolumeConstructOpts {
export interface VolumeConfig { export interface VolumeConfig {
labels: LabelObject; labels: LabelObject;
driver: string;
driverOpts: Docker.VolumeInspectInfo['Options']; driverOpts: Docker.VolumeInspectInfo['Options'];
} }
export interface ComposeVolumeConfig { export interface ComposeVolumeConfig {
driver: string;
driver_opts: Dictionary<string>; driver_opts: Dictionary<string>;
labels: LabelObject; labels: LabelObject;
} }
export class Volume { export class Volume {
public appId: number;
public name: string;
public config: VolumeConfig;
private logger: Logger; private logger: Logger;
private docker: Docker; private docker: Docker;
private constructor( private constructor(
name: string, public name: string,
appId: number, public appId: number,
config: VolumeConfig, public config: VolumeConfig,
opts: VolumeConstructOpts, opts: VolumeConstructOpts,
) { ) {
this.name = name;
this.appId = appId;
this.logger = opts.logger; this.logger = opts.logger;
this.docker = opts.docker; this.docker = opts.docker;
this.config = config;
} }
public static fromDockerVolume( public static fromDockerVolume(
@ -54,6 +48,7 @@ export class Volume {
// Convert the docker inspect to the config // Convert the docker inspect to the config
const config: VolumeConfig = { const config: VolumeConfig = {
labels: inspect.Labels || {}, labels: inspect.Labels || {},
driver: inspect.Driver,
driverOpts: inspect.Options || {}, driverOpts: inspect.Options || {},
}; };
@ -71,6 +66,7 @@ export class Volume {
) { ) {
const filledConfig: VolumeConfig = { const filledConfig: VolumeConfig = {
driverOpts: config.driver_opts || {}, driverOpts: config.driver_opts || {},
driver: config.driver || 'local',
labels: ComposeUtils.normalizeLabels(config.labels || {}), labels: ComposeUtils.normalizeLabels(config.labels || {}),
}; };
@ -83,6 +79,7 @@ export class Volume {
public toComposeObject(): ComposeVolumeConfig { public toComposeObject(): ComposeVolumeConfig {
return { return {
driver: this.config.driver,
driver_opts: this.config.driverOpts!, driver_opts: this.config.driverOpts!,
labels: this.config.labels, labels: this.config.labels,
}; };
@ -90,6 +87,7 @@ export class Volume {
public isEqualConfig(volume: Volume): boolean { public isEqualConfig(volume: Volume): boolean {
return ( return (
isEqual(this.config.driver, volume.config.driver) &&
isEqual(this.config.driverOpts, volume.config.driverOpts) && isEqual(this.config.driverOpts, volume.config.driverOpts) &&
isEqual( isEqual(
Volume.omitSupervisorLabels(this.config.labels), Volume.omitSupervisorLabels(this.config.labels),
@ -105,6 +103,7 @@ export class Volume {
await this.docker.createVolume({ await this.docker.createVolume({
Name: Volume.generateDockerName(this.appId, this.name), Name: Volume.generateDockerName(this.appId, this.name),
Labels: this.config.labels, Labels: this.config.labels,
Driver: this.config.driver,
DriverOpts: this.config.driverOpts, DriverOpts: this.config.driverOpts,
}); });
} }

View File

@ -44,9 +44,13 @@ describe('Compose volumes', () => {
.to.have.property('config') .to.have.property('config')
.that.has.property('driverOpts') .that.has.property('driverOpts')
.that.deep.equals({}); .that.deep.equals({});
expect(volume)
.to.have.property('config')
.that.has.property('driver')
.that.equals('local');
}); });
it('should correctly parse compose volumes', () => { it('should correctly parse compose volumes without an explicit driver', () => {
const volume = Volume.fromComposeObject( const volume = Volume.fromComposeObject(
'one_volume', 'one_volume',
1032480, 1032480,
@ -80,6 +84,51 @@ describe('Compose volumes', () => {
.that.deep.equals({ .that.deep.equals({
opt1: 'test', opt1: 'test',
}); });
expect(volume)
.to.have.property('config')
.that.has.property('driver')
.that.equals('local');
});
it('should correctly parse compose volumes with an explicit driver', () => {
const volume = Volume.fromComposeObject(
'one_volume',
1032480,
{
driver: 'other',
driver_opts: {
opt1: 'test',
},
labels: {
'my-label': 'test-label',
},
},
opts,
);
expect(volume)
.to.have.property('appId')
.that.equals(1032480);
expect(volume)
.to.have.property('name')
.that.equals('one_volume');
expect(volume)
.to.have.property('config')
.that.has.property('labels')
.that.deep.equals({
'io.balena.supervised': 'true',
'my-label': 'test-label',
});
expect(volume)
.to.have.property('config')
.that.has.property('driverOpts')
.that.deep.equals({
opt1: 'test',
});
expect(volume)
.to.have.property('config')
.that.has.property('driver')
.that.equals('other');
}); });
}); });