mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-31 08:25:36 +00:00
Merge pull request #1802 from balena-os/service-config-helpers
Check config for networks and volumes inside Service
This commit is contained in:
commit
98a53767c2
@ -145,17 +145,13 @@ export class App {
|
|||||||
// Generate volume steps
|
// Generate volume steps
|
||||||
steps = steps.concat(
|
steps = steps.concat(
|
||||||
this.generateStepsForComponent(volumeChanges, servicePairs, (v, svc) =>
|
this.generateStepsForComponent(volumeChanges, servicePairs, (v, svc) =>
|
||||||
// TODO: Volumes are stored without the appId prepended, but networks are stored
|
svc.hasVolume(v.name),
|
||||||
// with it prepended. Sort out this inequality
|
|
||||||
svc.config.volumes.includes(v.name),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// Generate network steps
|
// Generate network steps
|
||||||
steps = steps.concat(
|
steps = steps.concat(
|
||||||
this.generateStepsForComponent(
|
this.generateStepsForComponent(networkChanges, servicePairs, (n, svc) =>
|
||||||
networkChanges,
|
svc.hasNetwork(n.name),
|
||||||
servicePairs,
|
|
||||||
(n, svc) => `${this.appId}_${n.name}` in svc.config.networks,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -570,21 +566,14 @@ export class App {
|
|||||||
networkPairs: Array<ChangingPair<Network>>,
|
networkPairs: Array<ChangingPair<Network>>,
|
||||||
volumePairs: Array<ChangingPair<Volume>>,
|
volumePairs: Array<ChangingPair<Volume>>,
|
||||||
): boolean {
|
): boolean {
|
||||||
const serviceVolumes = svc.config.volumes;
|
return (
|
||||||
for (const { current } of volumePairs) {
|
volumePairs.some(
|
||||||
if (current && serviceVolumes.includes(`${this.appId}_${current.name}`)) {
|
({ current }) => current && svc.hasVolume(current.name),
|
||||||
return true;
|
) ||
|
||||||
}
|
networkPairs.some(
|
||||||
}
|
({ current }) => current && svc.hasNetwork(current.name),
|
||||||
|
)
|
||||||
const serviceNetworks = svc.config.networks;
|
);
|
||||||
for (const { current } of networkPairs) {
|
|
||||||
if (current && `${this.appId}_${current.name}` in serviceNetworks) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateContainerStep(current: Service, target: Service) {
|
private generateContainerStep(current: Service, target: Service) {
|
||||||
@ -657,33 +646,20 @@ export class App {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for networks to be created before starting the service
|
||||||
if (
|
if (
|
||||||
_.some(
|
networkPairs.some(
|
||||||
networkPairs,
|
(pair) => pair.target && target.hasNetworkMode(pair.target.name),
|
||||||
(pair) =>
|
|
||||||
`${this.appId}_${pair.target?.name}` === target.config.networkMode,
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for volumes to be created before starting the service
|
||||||
if (
|
if (
|
||||||
_.some(target.config.volumes, (volumeDefinition) => {
|
volumePairs.some(
|
||||||
const [sourceName, destName] = volumeDefinition.split(':');
|
(pair) => pair.target && target.hasVolume(pair.target.name),
|
||||||
if (destName == null) {
|
)
|
||||||
// If this is not a named volume, ignore it
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (sourceName[0] === '/') {
|
|
||||||
// Absolute paths should also be ignored
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.some(
|
|
||||||
volumePairs,
|
|
||||||
(pair) => `${target.appId}_${pair.target?.name}` === sourceName,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -877,32 +877,6 @@ export class Service {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNamedVolumes() {
|
|
||||||
const defaults = Service.defaultBinds(
|
|
||||||
this.appId || 0,
|
|
||||||
this.serviceName || '',
|
|
||||||
);
|
|
||||||
const validVolumes = _.map(this.config.volumes, (volume) => {
|
|
||||||
if (_.includes(defaults, volume) || !_.includes(volume, ':')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const bindSource = volume.split(':')[0];
|
|
||||||
if (!path.isAbsolute(bindSource)) {
|
|
||||||
const match = bindSource.match(/[0-9]+_(.+)/);
|
|
||||||
if (match == null) {
|
|
||||||
log.error(
|
|
||||||
`Error: There was an error parsing a volume bind source, ignoring.\nBind source: ${bindSource}`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
return _.reject(validVolumes, _.isNil);
|
|
||||||
}
|
|
||||||
|
|
||||||
public handoverCompleteFullPathsOnHost(): string[] {
|
public handoverCompleteFullPathsOnHost(): string[] {
|
||||||
const lockPath = updateLock.lockPath(
|
const lockPath = updateLock.lockPath(
|
||||||
this.appId || 0,
|
this.appId || 0,
|
||||||
@ -995,6 +969,32 @@ export class Service {
|
|||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasNetwork(networkName: string) {
|
||||||
|
// TODO; we could probably export network naming methods to another
|
||||||
|
// module to avoid duplicate code
|
||||||
|
return `${this.appId}_${networkName}` in this.config.networks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasNetworkMode(networkName: string) {
|
||||||
|
return `${this.appId}_${networkName}` === this.config.networkMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasVolume(volumeName: string) {
|
||||||
|
return this.config.volumes.some((volumeDefinition) => {
|
||||||
|
const [sourceName, destName] = volumeDefinition.split(':');
|
||||||
|
if (destName == null) {
|
||||||
|
// If this is not a named volume, ignore it
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sourceName[0] === '/') {
|
||||||
|
// Absolute paths should also be ignored
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.appId}_${volumeName}` === sourceName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private isSameNetwork(
|
private isSameNetwork(
|
||||||
current: ServiceConfig['networks'][0],
|
current: ServiceConfig['networks'][0],
|
||||||
target: ServiceConfig['networks'][0],
|
target: ServiceConfig['networks'][0],
|
||||||
@ -1086,6 +1086,8 @@ export class Service {
|
|||||||
log.warn(`Ignoring invalid bind mount ${volume}`);
|
log.warn(`Ignoring invalid bind mount ${volume}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Push anonymous volume. Anonymous volumes can only be
|
||||||
|
// set by using the `VOLUME` instruction inside a dockerfile
|
||||||
volumes.push(volume);
|
volumes.push(volume);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -76,7 +76,13 @@ function createImage(
|
|||||||
...extra
|
...extra
|
||||||
} = {} as Partial<Image>,
|
} = {} as Partial<Image>,
|
||||||
) {
|
) {
|
||||||
return { appId, dependent, name, serviceName, ...extra } as Image;
|
return {
|
||||||
|
appId,
|
||||||
|
dependent,
|
||||||
|
name,
|
||||||
|
serviceName,
|
||||||
|
...extra,
|
||||||
|
} as Image;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectSteps = (
|
const expectSteps = (
|
||||||
@ -240,11 +246,19 @@ describe('compose/app', () => {
|
|||||||
|
|
||||||
it('should kill dependencies of a volume before changing config', async () => {
|
it('should kill dependencies of a volume before changing config', async () => {
|
||||||
const current = createApp({
|
const current = createApp({
|
||||||
services: [await createService({ volumes: ['test-volume'] })],
|
services: [
|
||||||
|
await createService({
|
||||||
|
volumes: ['test-volume:/data'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
volumes: [Volume.fromComposeObject('test-volume', 1, {})],
|
volumes: [Volume.fromComposeObject('test-volume', 1, {})],
|
||||||
});
|
});
|
||||||
const target = createApp({
|
const target = createApp({
|
||||||
services: [await createService({ volumes: ['test-volume'] })],
|
services: [
|
||||||
|
await createService({
|
||||||
|
volumes: ['test-volume:/data'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
volumes: [
|
volumes: [
|
||||||
Volume.fromComposeObject('test-volume', 1, {
|
Volume.fromComposeObject('test-volume', 1, {
|
||||||
labels: { test: 'test' },
|
labels: { test: 'test' },
|
||||||
@ -276,7 +290,9 @@ describe('compose/app', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not output a kill step for a service which is already stopping when changing a volume', async () => {
|
it('should not output a kill step for a service which is already stopping when changing a volume', async () => {
|
||||||
const service = await createService({ volumes: ['test-volume'] });
|
const service = await createService({
|
||||||
|
volumes: ['test-volume:/data'],
|
||||||
|
});
|
||||||
service.status = 'Stopping';
|
service.status = 'Stopping';
|
||||||
const current = createApp({
|
const current = createApp({
|
||||||
services: [service],
|
services: [service],
|
||||||
@ -298,7 +314,7 @@ describe('compose/app', () => {
|
|||||||
|
|
||||||
it('should generate the correct step sequence for a volume purge request', async () => {
|
it('should generate the correct step sequence for a volume purge request', async () => {
|
||||||
const service = await createService({
|
const service = await createService({
|
||||||
volumes: ['db-volume'],
|
volumes: ['db-volume:/data'],
|
||||||
image: 'test-image',
|
image: 'test-image',
|
||||||
});
|
});
|
||||||
const volume = Volume.fromComposeObject('db-volume', service.appId, {});
|
const volume = Volume.fromComposeObject('db-volume', service.appId, {});
|
||||||
@ -364,7 +380,7 @@ describe('compose/app', () => {
|
|||||||
volumes: [],
|
volumes: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 3: start & createVolume
|
// Step 3: createVolume
|
||||||
service.status = 'Running';
|
service.status = 'Running';
|
||||||
const target = createApp({
|
const target = createApp({
|
||||||
services: [service],
|
services: [service],
|
||||||
@ -372,13 +388,26 @@ describe('compose/app', () => {
|
|||||||
volumes: [volume],
|
volumes: [volume],
|
||||||
isTarget: true,
|
isTarget: true,
|
||||||
});
|
});
|
||||||
const finalSteps = currentWithVolumesRemoved.nextStepsForAppUpdate(
|
const recreateVolumeSteps = currentWithVolumesRemoved.nextStepsForAppUpdate(
|
||||||
contextWithImages,
|
contextWithImages,
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
expect(finalSteps).to.have.length(2);
|
|
||||||
expectSteps('start', finalSteps);
|
expect(recreateVolumeSteps).to.have.length(1);
|
||||||
expectSteps('createVolume', finalSteps);
|
expectSteps('createVolume', recreateVolumeSteps);
|
||||||
|
|
||||||
|
// Final step: start service
|
||||||
|
const currentWithVolumeRecreated = createApp({
|
||||||
|
services: [],
|
||||||
|
networks: [defaultNetwork],
|
||||||
|
volumes: [volume],
|
||||||
|
});
|
||||||
|
|
||||||
|
const createServiceSteps = currentWithVolumeRecreated.nextStepsForAppUpdate(
|
||||||
|
contextWithImages,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
expectSteps('start', createServiceSteps);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user