Merge pull request #1067 from balena-io/selective-xor-compare

Only consider certain array fields without order
This commit is contained in:
CameronDiver 2019-08-19 16:58:54 +01:00 committed by GitHub
commit b9800ae3d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 15 deletions

View File

@ -46,8 +46,6 @@ export class Service {
'devices',
'capAdd',
'capDrop',
'dns',
'dnsSearch',
'dnsOpt',
'tmpfs',
'extraHosts',
@ -56,6 +54,13 @@ export class Service {
'groupAdd',
'securityOpt',
];
public static orderedConfigArrayFields: ServiceConfigArrayField[] = [
'dns',
'dnsSearch',
];
public static allConfigArrayFields: ServiceConfigArrayField[] = Service.configArrayFields.concat(
Service.orderedConfigArrayFields,
);
// A list of fields to ignore when comparing container configuration
private static omitFields = [
@ -69,7 +74,7 @@ export class Service {
// These fields are special case, due to network_mode:service:<service>
'networkMode',
'hostname',
].concat(Service.configArrayFields);
].concat(Service.allConfigArrayFields);
private constructor() {}
@ -684,17 +689,23 @@ export class Service {
let sameConfig = _.isEqual(thisOmitted, otherOmitted);
const nonArrayEquals = sameConfig;
// Check for array fields which don't match
const differentArrayFields: string[] = [];
sameConfig =
sameConfig &&
_.every(Service.configArrayFields, (field: keyof ServiceConfig) => {
const eq = _.isEqual(this.config[field], service.config[field]);
if (!eq) {
differentArrayFields.push(field);
}
return eq;
});
// Because the service config does not have an index
// field, we must first convert to unknown. We know that
// this conversion is fine as the
// Service.configArrayFields and
// Service.orderedConfigArrayFields are defined as
// fields inside of Service.config
const arrayEq = ComposeUtils.compareArrayFields(
(this.config as unknown) as Dictionary<unknown>,
(service.config as unknown) as Dictionary<unknown>,
Service.configArrayFields,
Service.orderedConfigArrayFields,
);
sameConfig = sameConfig && arrayEq.equal;
let differentArrayFields: string[] = [];
if (!arrayEq.equal) {
differentArrayFields = arrayEq.difference;
}
if (!(sameConfig && sameNetworks)) {
// Add some console output for why a service is not matching

View File

@ -499,3 +499,60 @@ export function normalizeLabels(labels: {
);
return _.assign({}, otherLabels, legacyLabels, balenaLabels);
}
function compareArrayField(
arr1: unknown[],
arr2: unknown[],
ordered: boolean,
): boolean {
if (!ordered) {
arr1 = _.sortBy(arr1);
arr2 = _.sortBy(arr2);
}
return _.isEqual(arr1, arr2);
}
export function compareArrayFields<T extends Dictionary<unknown>>(
obj1: T,
obj2: T,
nonOrderedFields: Array<keyof T>,
orderedFields: Array<keyof T>,
): { equal: false; difference: string[] };
export function compareArrayFields<T extends Dictionary<unknown>>(
obj1: T,
obj2: T,
nonOrderedFields: Array<keyof T>,
orderedFields: Array<keyof T>,
): { equal: true };
export function compareArrayFields<T extends Dictionary<unknown>>(
obj1: T,
obj2: T,
nonOrderedFields: Array<keyof T>,
orderedFields: Array<keyof T>,
): { equal: boolean; difference?: string[] } {
let equal = true;
const difference: string[] = [];
for (const { fields, ordered } of [
{ fields: nonOrderedFields, ordered: false },
{ fields: orderedFields, ordered: true },
]) {
for (const field of fields) {
if (
!compareArrayField(
obj1[field] as unknown[],
obj2[field] as unknown[],
ordered,
)
) {
equal = false;
difference.push(field as string);
}
}
}
if (equal) {
return { equal };
} else {
return { equal, difference };
}
}

View File

@ -1,4 +1,4 @@
{ expect } = require './lib/chai-config'
{ assert, expect } = require './lib/chai-config'
_ = require 'lodash'
@ -210,6 +210,101 @@ describe 'compose/service', ->
expect(service.config).to.have.property('expose').that.deep.equals(['80/tcp', '100/tcp'])
describe 'Ordered array parameters', ->
it 'Should correctly compare ordered array parameters', ->
svc1 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
dns: [
'8.8.8.8',
'1.1.1.1',
]
}, { appName: 'test' })
svc2 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
dns: [
'8.8.8.8',
'1.1.1.1',
]
}, { appName: 'test' })
assert(svc1.isEqualConfig(svc2))
svc2 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
dns: [
'1.1.1.1',
'8.8.8.8',
]
}, { appName: 'test' })
assert(!svc1.isEqualConfig(svc2))
it 'should correctly compare non-ordered array parameters', ->
svc1 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
volumes: [
'abcdef',
'ghijk',
]
}, { appName: 'test' })
svc2 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
volumes: [
'abcdef',
'ghijk',
]
}, { appName: 'test' })
assert(svc1.isEqualConfig(svc2))
svc2 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
volumes: [
'ghijk',
'abcdef',
]
}, { appName: 'test' })
assert(svc1.isEqualConfig(svc2))
it 'should correctly compare both ordered and non-ordered array parameters', ->
svc1 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
volumes: [
'abcdef',
'ghijk',
],
dns: [
'8.8.8.8',
'1.1.1.1',
]
}, { appName: 'test' })
svc2 = Service.fromComposeObject({
appId: 1,
serviceId: 1,
serviceName: 'test',
volumes: [
'ghijk',
'abcdef',
],
dns: [
'8.8.8.8',
'1.1.1.1',
]
}, { appName: 'test' })
assert(svc1.isEqualConfig(svc2))
describe 'parseMemoryNumber()', ->
makeComposeServiceWithLimit = (memLimit) ->
Service.fromComposeObject({