Rework for new requirements that node groups need the entire object.

This commit is contained in:
Westly Wright 2020-09-11 16:07:15 -07:00
parent 42a8ce7cad
commit 79c00cf7d7
No known key found for this signature in database
GPG Key ID: 4FAB3D8673DC54A3
2 changed files with 178 additions and 135 deletions

View File

@ -18,6 +18,40 @@ const TRUE = 'True';
const CLUSTER_TEMPLATE_ID_PREFIX = 'cattle-global-data:';
const SCHEDULE_CLUSTER_SCAN_QUESTION_KEY = 'scheduledClusterScan.enabled';
export const DEFAULT_NODE_GROUP_CONFIG = {
desiredSize: 2,
diskSize: 20,
ec2SshKey: '',
gpu: false,
instanceType: 't3.medium',
maxSize: 2,
minSize: 2,
nodegroupName: '',
subnets: [],
type: 'nodeGroup',
};
export const DEFAULT_EKS_CONFIG = {
amazonCredentialSecret: '',
displayName: '',
imported: false,
kmsKey: '',
kubernetesVersion: '',
loggingTypes: [],
nodeGroups: [],
privateAccess: false,
publicAccess: true,
publicAccessSources: [],
region: 'us-west-2',
secretsEncryption: false,
securityGroups: [],
serviceRole: '',
subnets: [],
tags: {},
type: 'eksclusterconfigspec',
};
export default Resource.extend(Grafana, ResourceUsage, {
globalStore: service(),
growl: service(),
@ -662,47 +696,45 @@ export default Resource.extend(Grafana, ResourceUsage, {
if (get(this, 'driver') === 'EKS' || !this.isEmptyObject(get(this, 'eksConfig'))) {
const options = ({
...opt,
data: {}
data: {
name: this.name,
eksConfig: {},
}
});
const config = jsondiffpatch.clone(get(this, 'eksConfig'));
const eksClusterConfigSpec = get(this, 'globalStore').getById('schema', 'eksclusterconfigspec');
const nodeGroupConfigSpec = get(this, 'globalStore').getById('schema', 'nodegroup');
if (isEmpty(this.id)) {
const eksClusterConfigSpec = get(this, 'globalStore').getById('schema', 'eksclusterconfigspec');
const resourceFields = get(eksClusterConfigSpec, 'resourceFields');
Object.keys(config).forEach((ck) => {
const configValue = get(config, ck);
replaceNullWithEmptyDefaults(get(this, 'eksConfig'), get(eksClusterConfigSpec, 'resourceFields'));
if (configValue === null || typeof configValue === 'undefined') {
const resourceField = resourceFields[ck];
if (!isEmpty(get(this, 'eksConfig.nodeGroups'))) {
get(this, 'eksConfig.nodeGroups').forEach((ng) => {
replaceNullWithEmptyDefaults(ng, get(nodeGroupConfigSpec, 'resourceFields'));
});
}
if (resourceField.type === 'string') {
set(config, ck, '');
} else if (resourceField.type.includes('array')) {
set(config, ck, []);
} else if (resourceField.type.includes('map')) {
set(config, ck, {});
} else if (resourceField.type.includes('boolean')) {
if (resourceField.default) {
set(config, ck, resourceField.default);
} else {
// we shouldn't get here, there are not that many fields in EKS and I've set the defaults for bools that are there
// but if we do hit this branch my some magic case imo a bool isn't something we can default cause its unknown...just dont do anything.
}
}
}
})
return this._super(...arguments);
} else {
const config = jsondiffpatch.clone(get(this, 'eksConfig'));
const upstreamSpec = jsondiffpatch.clone(get(this, 'eksStatus.upstreamSpec'));
if (isEmpty(upstreamSpec)) {
return reject(this.intl('clusterNew.amazoneks.errors.clusterSpec'));
}
set(options, 'data', diff(upstreamSpec, config));
set(options, 'data.eksConfig', this.diffEksUpstream(upstreamSpec, config));
if (!isEmpty(get(options, 'data.eksConfig.nodeGroups'))) {
get(options, 'data.eksConfig.nodeGroups').forEach((ng) => {
replaceNullWithEmptyDefaults(ng, get(nodeGroupConfigSpec, 'resourceFields'));
});
}
if (get(options, 'qp._replace')) {
delete options.qp['_replace'];
}
return this._super(options);
}
@ -710,119 +742,162 @@ export default Resource.extend(Grafana, ResourceUsage, {
return this._super(...arguments);
}
function replaceNullWithEmptyDefaults(config, resourceFields) {
Object.keys(config).forEach((ck) => {
const configValue = get(config, ck);
if (configValue === null || typeof configValue === 'undefined') {
const resourceField = resourceFields[ck];
if (resourceField.type === 'string') {
set(config, ck, '');
} else if (resourceField.type.includes('array')) {
set(config, ck, []);
} else if (resourceField.type.includes('map')) {
set(config, ck, {});
} else if (resourceField.type.includes('boolean')) {
if (resourceField.default) {
set(config, ck, resourceField.default);
} else {
if ( !isEmpty(get(DEFAULT_EKS_CONFIG, ck)) || !isEmpty(get(DEFAULT_NODE_GROUP_CONFIG, ck)) ) {
let match = isEmpty(get(DEFAULT_EKS_CONFIG, ck)) ? get(DEFAULT_NODE_GROUP_CONFIG, ck) : get(DEFAULT_EKS_CONFIG, ck);
set(config, ck, match);
}
// we shouldn't get here, there are not that many fields in EKS and I've set the defaults for bools that are there
// but if we do hit this branch my some magic case imo a bool isn't something we can default cause its unknown...just dont do anything.
}
}
}
});
return config;
}
},
diffEksUpstream(lhs, rhs) {
// this is NOT a generic object diff.
// It tries to be as generic as possible but it does make certain assumptions regarding nulls and emtpy arrays/objects
// if LHS (upstream) is null and RHS (eks config) is empty we do not count this as a change
// additionally null values on the RHS will be ignored as null cant be sent in this case
function diff(lhs, rhs) {
const delta = {};
const rhsKeys = Object.keys(rhs);
const delta = {};
const rhsKeys = Object.keys(rhs);
rhsKeys.forEach((k) => {
if (k === 'type') {
rhsKeys.forEach((k) => {
if (k === 'type') {
return;
}
const lhsMatch = get(lhs, k);
const rhsMatch = get(rhs, k);
try {
if (isEqual(JSON.stringify(lhsMatch), JSON.stringify(rhsMatch))) {
return;
}
} catch (e){}
const lhsMatch = get(lhs, k);
const rhsMatch = get(rhs, k);
if (k === 'nodeGroups') {
if (!isEmpty(lhsMatch)) {
// node groups need ALL data so short circut and send it all
set(delta, k, lhsMatch);
} else {
// all node groups were deleted
set(delta, k, []);
}
try {
if (isEqual(JSON.stringify(lhsMatch), JSON.stringify(rhsMatch))) {
return;
}
} catch (e){}
return;
}
if (isEmpty(lhsMatch) || this.isEmptyObject(lhsMatch)) {
if (isEmpty(rhsMatch) || this.isEmptyObject(rhsMatch)) {
if (lhsMatch !== null && (isArray(rhsMatch) || this.isObject(rhsMatch))) {
// Empty Arrays and Empty Maps must be sent as such unless the upstream value is null, then the empty array or obj is just a init value from ember
set(delta, k, rhsMatch);
}
return;
} else {
// lhs is empty, rhs is not, just set
if (isEmpty(lhsMatch) || this.isEmptyObject(lhsMatch)) {
if (isEmpty(rhsMatch) || this.isEmptyObject(rhsMatch)) {
if (lhsMatch !== null && (isArray(rhsMatch) || this.isObject(rhsMatch))) {
// Empty Arrays and Empty Maps must be sent as such unless the upstream value is null, then the empty array or obj is just a init value from ember
set(delta, k, rhsMatch);
}
return;
} else {
if (rhsMatch !== null) {
// entry in og obj
if (isArray(lhsMatch)) {
if (isArray(rhsMatch)) {
if (rhsMatch.every((m) => this.isObject(m))) {
// You have more diffing to do
rhsMatch.forEach((match) => {
// our most likely candiate for a match is node group name, but lets check the others just incase.
const matchId = get(match, 'nodegroupName') || get(match, 'name') || get(match, 'id') || false;
// lhs is empty, rhs is not, just set
set(delta, k, rhsMatch);
}
} else {
if (rhsMatch !== null) {
// entry in og obj
if (isArray(lhsMatch)) {
if (isArray(rhsMatch)) {
if (rhsMatch.every((m) => this.isObject(m))) {
// You have more diffing to do
rhsMatch.forEach((match) => {
// our most likely candiate for a match is node group name, but lets check the others just incase.
const matchId = get(match, 'name') || get(match, 'id') || false;
if (matchId) {
let lmatchIdx;
if (matchId) {
let lmatchIdx;
// we have soime kind of identifier to find a match in the upstream, so we can diff and insert to new array
const lMatch = lhsMatch.find((l, idx) => {
const lmatchId = get(l, 'nodegroupName') || get(l, 'name') || get(l, 'id');
// we have soime kind of identifier to find a match in the upstream, so we can diff and insert to new array
const lMatch = lhsMatch.find((l, idx) => {
const lmatchId = get(l, 'name') || get(l, 'id');
if (lmatchId === matchId) {
lmatchIdx = idx;
if (lmatchId === matchId) {
lmatchIdx = idx;
return l;
}
});
return l;
}
});
if (lMatch) {
// we have a match in the upstream, meaning we've probably made updates to the object itself
const diffedMatch = diff(lMatch, match);
if (lMatch) {
// we have a match in the upstream, meaning we've probably made updates to the object itself
const diffedMatch = this.diffEksUpstream(lMatch, match);
if (!isArray(get(delta, k))) {
set(delta, k, [diffedMatch]);
} else {
// diff and push into new array
delta[k].insertAt(lmatchIdx, diffedMatch);
}
if (!isArray(get(delta, k))) {
set(delta, k, [diffedMatch]);
} else {
// no match in upstream, new entry
if (!isArray(get(delta, k))) {
set(delta, k, [match]);
} else {
delta[k].pushObject(match);
}
// diff and push into new array
delta[k].insertAt(lmatchIdx, diffedMatch);
}
} else {
// no match id, all we can do is dumb add
// no match in upstream, new entry
if (!isArray(get(delta, k))) {
set(delta, k, [match]);
} else {
delta[k].pushObject(match);
}
}
})
} else {
set(delta, k, rhsMatch);
}
} else {
// no match id, all we can do is dumb add
if (!isArray(get(delta, k))) {
set(delta, k, [match]);
} else {
delta[k].pushObject(match);
}
}
})
} else {
set(delta, k, rhsMatch);
}
} else if (this.isObject(lhsMatch)) {
if (!isEmpty(rhsMatch) && !this.isEmptyObject(rhsMatch)) {
if ((Object.keys(lhsMatch) || []).length > 0) {
// You have more diffing to do
set(delta, k, diff(lhsMatch, rhsMatch));
} else if (this.isEmptyObject(lhsMatch)) {
// we had a map now we have an empty map
set(delta, k, {});
}
}
} else { // lhsMatch not an array or object
} else {
set(delta, k, rhsMatch);
}
} else if (this.isObject(lhsMatch)) {
if (!isEmpty(rhsMatch) && !this.isEmptyObject(rhsMatch)) {
if ((Object.keys(lhsMatch) || []).length > 0) {
// You have more diffing to do
set(delta, k, this.diffEksUpstream(lhsMatch, rhsMatch));
} else if (this.isEmptyObject(lhsMatch)) {
// we had a map now we have an empty map
set(delta, k, {});
}
}
} else { // lhsMatch not an array or object
set(delta, k, rhsMatch);
}
}
});
}
});
return delta;
}
return delta;
},
isObject(obj) {
return obj !== null && obj.constructor.name === 'Object';
},

View File

@ -14,39 +14,7 @@ import ClusterDriver from 'shared/mixins/cluster-driver';
import { EKS_REGIONS, EKS_VERSIONS, INSTANCE_TYPES, nameFromResource } from 'shared/utils/amazon';
import { randomStr } from 'shared/utils/util';
import layout from './template';
const DEFAULT_NODE_GROUP_CONFIG = {
desiredSize: 2,
diskSize: 20,
ec2SshKey: '',
gpu: false,
instanceType: 't3.medium',
maxSize: 2,
minSize: 2,
nodegroupName: '',
subnets: [],
type: 'nodeGroup',
};
const DEFAULT_EKS_CONFIG = {
amazonCredentialSecret: '',
displayName: '',
imported: false,
kmsKey: '',
kubernetesVersion: '',
loggingTypes: [],
nodeGroups: [],
privateAccess: false,
publicAccess: true,
publicAccessSources: [],
region: 'us-west-2',
secretsEncryption: false,
securityGroups: [],
serviceRole: '',
subnets: [],
tags: {},
type: 'eksclusterconfigspec',
};
import { DEFAULT_NODE_GROUP_CONFIG, DEFAULT_EKS_CONFIG } from 'ui/models/cluster';
export default Component.extend(ClusterDriver, {
intl: service(),