Skip to content

Commit

Permalink
feat[Breaking]: Additive merge patch (#320)
Browse files Browse the repository at this point in the history
* feat: create new additiveMergePatch mode

allow user to create resources without the last-applied annotation being created. Note this will prevent fields from being auto reconciled if the user ever wants to remove something from their yaml. they can work around this by manually setting the old field to null in the yaml definition.

* set last-applied to a hint

* remove kapitan references

* Update BaseController.js

* Update BaseController.js

* catch edge case of moving from additive to apply

* Update BaseController.js

* Update BaseController.js

* better logging
  • Loading branch information
alewitt2 authored Jun 3, 2022
1 parent 03b6f17 commit 563c7ea
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 23 deletions.
38 changes: 25 additions & 13 deletions lib/BaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ module.exports = class BaseController {
}
get reconcileDefault() {
let result;
result = objectPath.get(this._data, ['object', 'metadata', 'labels', 'deploy.razee.io/Reconcile']) ||
objectPath.get(this._data, ['object', 'metadata', 'labels', 'kapitan.razee.io/Reconcile'], 'true');
result = objectPath.get(this._data, ['object', 'metadata', 'labels', 'deploy.razee.io/Reconcile'], 'true');
return result;
}

Expand Down Expand Up @@ -526,6 +525,8 @@ module.exports = class BaseController {
let name = objectPath.get(file, 'metadata.name');
let namespace = objectPath.get(file, 'metadata.namespace');
let uri = krm.uri({ name: objectPath.get(file, 'metadata.name'), namespace: objectPath.get(file, 'metadata.namespace') });
const mode = objectPath.get(options, 'mode', 'MergePatch');
const additiveMergPatchWarning = 'AdditiveMergePatch - Skipping reconcileFields from last-applied.';
this._logger.debug(`Apply ${uri}`);
let opt = { simple: false, resolveWithFullResponse: true };
let liveResource;
Expand All @@ -541,8 +542,7 @@ module.exports = class BaseController {
}

if (liveResource) {
let debug = objectPath.get(liveResource, ['metadata', 'labels', 'deploy.razee.io/debug']) ||
objectPath.get(liveResource, ['metadata', 'labels', 'kapitan.razee.io/debug'], 'false');
let debug = objectPath.get(liveResource, ['metadata', 'labels', 'deploy.razee.io/debug'], 'false');
if (debug.toLowerCase() === 'true') {
this.log.warn(`${uri}: Debug enabled on resource: skipping modifying resource - adding annotation deploy.razee.io/pending-configuration.`);
let patchObject = { metadata: { annotations: { 'deploy.razee.io/pending-configuration': JSON.stringify(file) } } };
Expand All @@ -557,47 +557,59 @@ module.exports = class BaseController {
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/pending-configuration'], null);
}
}
let lastApplied = objectPath.get(liveResource, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration']) ||
objectPath.get(liveResource, ['metadata', 'annotations', 'kapitan.razee.io/last-applied-configuration']);
if (!lastApplied) {
// ensure annotations is not null before we start working with it
if (objectPath.get(file, ['metadata', 'annotations']) === null) {
objectPath.set(file, ['metadata', 'annotations'], {});
}
let lastApplied = objectPath.get(liveResource, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration']);
if (mode.toLowerCase() === 'additivemergepatch') {
// skip the last applied reconcileFields logic and replace our last-applied object with a warning.
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration'], additiveMergPatchWarning);
} else if (!lastApplied || lastApplied == additiveMergPatchWarning) {
this.log.warn(`${uri}: No deploy.razee.io/last-applied-configuration found`);
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration'], JSON.stringify(file));
} else {
lastApplied = JSON.parse(lastApplied);

let original = clone(file);
this.reconcileFields(file, lastApplied);
// If reconcileFields set annotations to null, make sure its an empty object instead
if (objectPath.get(file, ['metadata', 'annotations']) === null) {
objectPath.set(file, ['metadata', 'annotations'], {});
}
objectPath.set(file, ['metadata', 'annotations', 'kapitan.razee.io/last-applied-configuration'], null);
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration'], JSON.stringify(original));
}
if (objectPath.get(options, 'mode', 'MergePatch').toLowerCase() == 'strategicmergepatch') {
if (mode.toLowerCase() === 'strategicmergepatch') {
let res = await krm.strategicMergePatch(name, namespace, file, opt);
this._logger.debug(`strategicMergePatch ${res.statusCode} ${uri}`);
this._logger.debug(`StrategicMergePatch ${res.statusCode} ${uri}`);
if (res.statusCode === 415) {
// let fall through
} else if (res.statusCode < 200 || res.statusCode >= 300) {
return Promise.reject({ statusCode: res.statusCode, body: res.body });
} else {
return { statusCode: res.statusCode, body: res.body };
}
}
} // else mode: MergePatch or AdditiveMergePatch
let res = await krm.mergePatch(name, namespace, file, opt);
this._logger.debug(`mergePatch ${res.statusCode} ${uri}`);
this._logger.debug(`${mode} ${res.statusCode} ${uri}`);
if (res.statusCode < 200 || res.statusCode >= 300) {
return Promise.reject({ statusCode: res.statusCode, body: res.body });
} else {
return { statusCode: res.statusCode, body: res.body };
}
} else {
this._logger.debug(`Post ${uri}`);

// Add last-applied to be used in future apply reconciles
if (objectPath.get(file, ['metadata', 'annotations']) === null) {
objectPath.set(file, ['metadata', 'annotations'], {});
}
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration'], JSON.stringify(file));
if (mode.toLowerCase() === 'additivemergepatch') {
// Set last applied with a warning.
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration'], additiveMergPatchWarning);
} else {
objectPath.set(file, ['metadata', 'annotations', 'deploy.razee.io/last-applied-configuration'], JSON.stringify(file));
}
let post = await krm.post(file, opt);
if (!(post.statusCode === 200 || post.statusCode === 201 || post.statusCode === 202)) {
this._logger.debug(`Post ${post.statusCode} ${uri}`);
Expand Down
25 changes: 15 additions & 10 deletions lib/CompositeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ module.exports = class CompositeController extends BaseController {
// if cleanup fails, do not return successful response => Promise.reject(err) or throw Error(err)
let children = objectPath.get(this.data, ['object', 'status', 'children'], {});
let res = await Promise.all(Object.entries(children).map(async ([selfLink, child]) => {
let reconcile = objectPath.get(child, ['deploy.razee.io/Reconcile']) ||
objectPath.get(child, ['kapitan.razee.io/Reconcile'], this.reconcileDefault);
let reconcile = objectPath.get(child, ['deploy.razee.io/Reconcile'], this.reconcileDefault);
if (reconcile.toLowerCase() == 'true') {
try {
await this._deleteChild(selfLink);
Expand Down Expand Up @@ -150,30 +149,37 @@ module.exports = class CompositeController extends BaseController {
}

let res;
let reconcile = objectPath.get(child, ['metadata', 'labels', 'deploy.razee.io/Reconcile']) ||
objectPath.get(child, ['metadata', 'labels', 'kapitan.razee.io/Reconcile'], this.reconcileDefault);
let mode = objectPath.get(child, ['metadata', 'labels', 'deploy.razee.io/mode']) ||
objectPath.get(child, ['metadata', 'labels', 'kapitan.razee.io/mode'], 'Apply');
let reconcile = objectPath.get(child, ['metadata', 'labels', 'deploy.razee.io/Reconcile'], this.reconcileDefault);
let mode = objectPath.get(child, ['metadata', 'labels', 'deploy.razee.io/mode'], 'Apply');
let modeUsed = '';
if (!objectPath.has(child, ['metadata', 'namespace']) && krm.namespaced) {
objectPath.set(child, ['metadata', 'namespace'], this.namespace);
childNamespace = objectPath.get(child, 'metadata.namespace');
}
childUri = krm.uri({ name: childName, namespace: childNamespace });
let childUid = objectPath.get(res, 'body.metadata.uid');


try {
switch (mode.toLowerCase()) {
case 'StrategicMergePatch'.toLowerCase():
modeUsed = 'StrategicMergePatch';
res = await this.apply(krm, child, { mode: 'StrategicMergePatch' });
break;
case 'AdditiveMergePatch'.toLowerCase():
modeUsed = 'AdditiveMergePatch';
res = await this.apply(krm, child, { mode: 'AdditiveMergePatch' });
break;
case 'EnsureExists'.toLowerCase():
modeUsed = 'EnsureExists';
res = await this.ensureExists(krm, child);
break;
default:
default: // Apply - MergePatch
modeUsed = 'Apply';
res = await this.apply(krm, child);
}
await this.addChildren({ uid: childUid, selfLink: childUri, 'deploy.razee.io/Reconcile': reconcile, 'Impersonate-User': impersonateUser });
this.log.info(`${mode} ${res.statusCode} ${childUri}`);
this.log.info(`${modeUsed} ${res.statusCode} ${childUri}`);
} catch (e) {
res = e;
}
Expand All @@ -190,8 +196,7 @@ module.exports = class CompositeController extends BaseController {

let res = await Promise.all(Object.entries(oldChildren).map(async ([selfLink, child]) => {
const newChild = clone(child);
let reconcile = objectPath.get(child, ['deploy.razee.io/Reconcile']) ||
objectPath.get(child, ['kapitan.razee.io/Reconcile'], this.reconcileDefault);
let reconcile = objectPath.get(child, ['deploy.razee.io/Reconcile'], this.reconcileDefault);
let exists = objectPath.has(newChildren, [selfLink]);
if (!exists && reconcile.toLowerCase() == 'true') {
this.log.info(`${selfLink} no longer applied.. Reconcile ${reconcile.toLowerCase()}.. removing from cluster`);
Expand Down

0 comments on commit 563c7ea

Please sign in to comment.