mirror of
https://github.com/tiennm99/is-a-dev.git
synced 2026-08-05 14:23:56 +00:00
Changes diff to add/delete actions
This commit is contained in:
@@ -2,16 +2,16 @@ const R = require('ramda');
|
||||
const { getDomainService, diffRecords } = require('../utils/domain-service');
|
||||
const {DOMAIN_DOMAIN} = require('../utils/constants');
|
||||
|
||||
const getCpanel = ({ zone, addZone, editZone, redir, addRedir, editRedir } = {}) => ({
|
||||
const getCpanel = ({ zone, addZone, removeZone, redir, addRedir, removeRedir } = {}) => ({
|
||||
zone: {
|
||||
fetch: (_) => zone(),
|
||||
add: (rec) => addZone(rec),
|
||||
edit: (rec) => editZone(rec),
|
||||
remove: (rec) => removeZone(rec),
|
||||
},
|
||||
redirection: {
|
||||
fetch: (_) => redir(),
|
||||
add: (rec) => addRedir(rec),
|
||||
edit: (rec) => editRedir(rec),
|
||||
remove: (rec) => removeRedir(rec),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('diffRecords', () => {
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
edit: [],
|
||||
remove: [],
|
||||
add: [
|
||||
{ name: 'boo', type: 'CNAME', address: 'x.com' },
|
||||
],
|
||||
@@ -48,10 +48,12 @@ describe('diffRecords', () => {
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
edit: [
|
||||
remove: [
|
||||
{ name: 'xa', type: 'A', address: '111.1.1212.1' },
|
||||
],
|
||||
add: [
|
||||
{ name: 'xa', type: 'A', address: '69.69.69.69' },
|
||||
],
|
||||
add: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,36 +70,67 @@ describe('diffRecords', () => {
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
edit: [],
|
||||
remove: [],
|
||||
add: [
|
||||
{ name: 'xa', type: 'A', address: '69.69.4.20' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should diff complex changes', () => {
|
||||
const oldRecords = [
|
||||
{ name: 'a', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'b', type: 'A', address: '69.69.69.69' },
|
||||
{ name: '111', type: 'CNAME', address: 'x' },
|
||||
{ name: 'd', type: 'A', address: '69.69.4.20' },
|
||||
];
|
||||
const newRecords = [
|
||||
{ name: '111', type: 'CNAME', address: 'x' },
|
||||
{ name: 'd', type: 'CNAME', address: 'duck.com' },
|
||||
{ name: 'a', type: 'CNAME', address: 'og.com' },
|
||||
{ name: 'b', type: 'A', address: '69.69.69.69' },
|
||||
{ name: 'b', type: 'A', address: '69.69.4.20' },
|
||||
{ name: 'c', type: 'CNAME', address: 'ccc.cc' },
|
||||
];
|
||||
|
||||
const result = diffRecords(oldRecords, newRecords);
|
||||
expect(result).toEqual({
|
||||
remove: [
|
||||
{ name: 'a', type: 'CNAME', address: 'fck.com.' },
|
||||
{ name: 'd', type: 'A', address: '69.69.4.20' },
|
||||
],
|
||||
add: [
|
||||
{ name: 'd', type: 'CNAME', address: 'duck.com' },
|
||||
{ name: 'a', type: 'CNAME', address: 'og.com' },
|
||||
{ name: 'b', type: 'A', address: '69.69.4.20' },
|
||||
{ name: 'c', type: 'CNAME', address: 'ccc.cc' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Domain service', () => {
|
||||
const addZone = jest.fn(async () => ({}));
|
||||
const editZone = jest.fn(async () => ({}));
|
||||
const removeZone = jest.fn(async () => ({}));
|
||||
const addRedir = jest.fn(async () => ({}));
|
||||
const editRedir = jest.fn(async () => ({}));
|
||||
const removeRedir = jest.fn(async () => ({}));
|
||||
|
||||
const mockDS = ({ zones, redirections }) => getDomainService({ cpanel: getCpanel({
|
||||
zone: async () => zones,
|
||||
redir: async () => redirections,
|
||||
addZone,
|
||||
addRedir,
|
||||
editZone,
|
||||
editRedir,
|
||||
removeZone,
|
||||
removeRedir,
|
||||
}) });
|
||||
|
||||
const getRecordCalls = recfn => recfn.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address', 'redirect', 'domain']));
|
||||
const getRecordCalls = recfn => recfn.mock.calls.map(R.head).map(R.pick(['name', 'type', 'address', 'redirect', 'domain', 'line']));
|
||||
|
||||
beforeEach(() => {
|
||||
addZone.mockClear();
|
||||
editZone.mockClear();
|
||||
removeZone.mockClear();
|
||||
addRedir.mockClear();
|
||||
editRedir.mockClear();
|
||||
removeRedir.mockClear();
|
||||
});
|
||||
|
||||
describe('getHosts', () => {
|
||||
@@ -144,8 +177,8 @@ describe('Domain service', () => {
|
||||
describe('updateHosts', () => {
|
||||
it('should append new hosts with existing ones and set it', async () => {
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
@@ -160,13 +193,13 @@ describe('Domain service', () => {
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'c', type: 'A', address: '12.131321.213' },
|
||||
]);
|
||||
expect(editZone).toBeCalledTimes(0);
|
||||
expect(removeZone).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
@@ -176,18 +209,21 @@ describe('Domain service', () => {
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
]);
|
||||
|
||||
expect(addZone).toBeCalledTimes(0);
|
||||
expect(editZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(editZone)).toEqual([
|
||||
expect(addZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
]);
|
||||
expect(removeZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update matching host and set it', async () => {
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ someid: 2, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ line: 2, name: 'b', type: 'CNAME', address: 'goo' },
|
||||
{ line: 3, name: 'b', type: 'CNAME', address: 'xaa' },
|
||||
];
|
||||
const redirections = [];
|
||||
|
||||
@@ -198,20 +234,24 @@ describe('Domain service', () => {
|
||||
{ name: 'b', type: 'CNAME', address: 'farboo' },
|
||||
]);
|
||||
|
||||
expect(addZone).toBeCalledTimes(0);
|
||||
expect(editZone).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(editZone)).toEqual([
|
||||
expect(addZone).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'b', type: 'CNAME', address: 'googoogaga' },
|
||||
{ name: 'b', type: 'CNAME', address: 'farboo' },
|
||||
]);
|
||||
expect(removeZone).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 2 },
|
||||
{ line: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should workout this complex example', async () => {
|
||||
const zones = [
|
||||
{ someid: 1, name: 'a', type: 'CNAME', address: 'world' },
|
||||
{ someid: 2, name: 'b', type: 'A', address: '1' },
|
||||
{ someid: 2, name: 'b', type: 'A', address: '2' },
|
||||
{ someid: 2, name: 'c', type: 'CNAME', address: 'hello.com' },
|
||||
{ line: 1, name: 'a', type: 'CNAME', address: 'world' },
|
||||
{ line: 2, name: 'b', type: 'A', address: '1' },
|
||||
{ line: 3, name: 'b', type: 'A', address: '2' },
|
||||
{ line: 4, name: 'c', type: 'CNAME', address: 'hello.com' },
|
||||
];
|
||||
const redirections = [
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, destination: 'https://foobar.com' },
|
||||
@@ -233,24 +273,27 @@ describe('Domain service', () => {
|
||||
{ name: 'x', type: 'URL', address: 'https://example69.com' },
|
||||
]);
|
||||
|
||||
expect(addZone).toBeCalledTimes(2);
|
||||
expect(editZone).toBeCalledTimes(1);
|
||||
expect(addRedir).toBeCalledTimes(1);
|
||||
expect(editRedir).toBeCalledTimes(2);
|
||||
expect(addZone).toBeCalledTimes(3);
|
||||
expect(getRecordCalls(addZone)).toEqual([
|
||||
{ name: 'b', type: 'A', address: '3' },
|
||||
{ name: 'd', type: 'CNAME', address: 'helo.com' }
|
||||
]);
|
||||
expect(getRecordCalls(editZone)).toEqual([
|
||||
{ name: 'a', type: 'CNAME', address: 'boo' },
|
||||
{ name: 'b', type: 'A', address: '3' },
|
||||
{ name: 'd', type: 'CNAME', address: 'helo.com' },
|
||||
]);
|
||||
expect(removeZone).toBeCalledTimes(1);
|
||||
expect(getRecordCalls(removeZone)).toEqual([
|
||||
{ line: 1 },
|
||||
]);
|
||||
expect(addRedir).toBeCalledTimes(3);
|
||||
expect(getRecordCalls(addRedir)).toEqual([
|
||||
{ domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' },
|
||||
]);
|
||||
expect(getRecordCalls(editRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://wowow.com' },
|
||||
{ domain: `d.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://hhh.com' },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}`, type: 'permanent', redirect: 'https://example69.com' },
|
||||
]);
|
||||
expect(removeRedir).toBeCalledTimes(2);
|
||||
expect(getRecordCalls(removeRedir)).toEqual([
|
||||
{ domain: `b.${DOMAIN_DOMAIN}` },
|
||||
{ domain: `x.${DOMAIN_DOMAIN}` },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+21
-37
@@ -34,37 +34,16 @@ const redirectionToRecord = ({ domain, destination }) => ({
|
||||
address: `${destination}`.replace(/\/$/g, ''),
|
||||
});
|
||||
|
||||
const getHostKey = host => `${host.name}##${host.type}`;
|
||||
|
||||
const toHostMap = hosts => hosts.reduce((acc, host) => {
|
||||
const key = getHostKey(host);
|
||||
return { ...acc, [key]: [ ...(acc[key] || []), host ] };
|
||||
}, {});
|
||||
const getHostKey = host => `${host.name}##${host.type}##${host.address}`;
|
||||
|
||||
const diffRecords = (oldRecords, newRecords) => {
|
||||
const remoteHostMap = toHostMap(oldRecords);
|
||||
const localHostMap = toHostMap(newRecords);
|
||||
const isMatchingRecord = (a, b) => getHostKey(a) === getHostKey(b);
|
||||
|
||||
return R.toPairs(localHostMap).reduce((acc, [key, local]) => {
|
||||
const remote = remoteHostMap[key];
|
||||
const remove = R.differenceWith(isMatchingRecord, oldRecords, newRecords);
|
||||
const add = R.differenceWith(isMatchingRecord, newRecords, oldRecords)
|
||||
.filter(r => !['www', '@'].includes(r.name));
|
||||
|
||||
if (remote) {
|
||||
let adds = [];
|
||||
let edits = [];
|
||||
|
||||
const diff = R.differenceWith((a, b) => a.address === b.address, local, remote);
|
||||
|
||||
if (diff.length === local.length - remote.length) {
|
||||
adds = diff;
|
||||
} else {
|
||||
edits = diff;
|
||||
}
|
||||
|
||||
return { ...acc, add: acc.add.concat(adds), edit: acc.edit.concat(edits) };
|
||||
}
|
||||
|
||||
return { ...acc, add: acc.add.concat(local) };
|
||||
}, { add: [], edit: [] });
|
||||
return { add, remove };
|
||||
};
|
||||
|
||||
const print = fn => x => log(fn(x)) || x;
|
||||
@@ -108,8 +87,9 @@ const getDomainService = ({ cpanel }) => {
|
||||
recordToZone,
|
||||
print(({ name }) => `Adding zone for ${name}...`),
|
||||
));
|
||||
const editZoneRecord = lazyTask(R.compose(
|
||||
cpanel.zone.edit,
|
||||
const removeZoneRecord = lazyTask(R.compose(
|
||||
cpanel.zone.remove,
|
||||
R.pick(['line']),
|
||||
recordToZone,
|
||||
print(({ name }) => `Editing zone for ${name}...`),
|
||||
));
|
||||
@@ -118,8 +98,9 @@ const getDomainService = ({ cpanel }) => {
|
||||
recordToRedirection,
|
||||
print(({ name }) => `Adding redirection for ${name}`),
|
||||
));
|
||||
const editRedirection = lazyTask(R.compose(
|
||||
cpanel.redirection.edit,
|
||||
const removeRedirection = lazyTask(R.compose(
|
||||
cpanel.redirection.remove,
|
||||
R.pick(['domain']),
|
||||
recordToRedirection,
|
||||
print(({ name }) => `Editing redirection for ${name}`),
|
||||
));
|
||||
@@ -140,17 +121,20 @@ const getDomainService = ({ cpanel }) => {
|
||||
[ R.propEq('type', 'URL'), addRedirection ],
|
||||
[ R.T, addZoneRecord ],
|
||||
])));
|
||||
const editRecords = R.compose(batchLazyTasks(BATCH_SIZE), R.map(R.cond([
|
||||
[ R.propEq('type', 'URL'), editRedirection ],
|
||||
[ R.T, editZoneRecord ],
|
||||
const removeRecords = R.compose(batchLazyTasks(BATCH_SIZE), R.map(R.cond([
|
||||
[ R.propEq('type', 'URL'), removeRedirection ],
|
||||
[ R.T, removeZoneRecord ],
|
||||
])));
|
||||
|
||||
const updateHosts = async hosts => {
|
||||
const remoteHostList = await getHosts();
|
||||
const { add, edit } = diffRecords(remoteHostList, hosts);
|
||||
const { add, remove } = diffRecords(remoteHostList, hosts);
|
||||
|
||||
await executeBatch(addRecords(add).concat(editRecords(edit)));
|
||||
return { additions: add.length, edits: edit.length };
|
||||
await executeBatch([
|
||||
...removeRecords(remove),
|
||||
...addRecords(add),
|
||||
]);
|
||||
return { added: add.length, removed: remove.length };
|
||||
};
|
||||
|
||||
return { getHosts, updateHosts };
|
||||
|
||||
Reference in New Issue
Block a user