Merge pull request #3184 from westlywright/cluster.templates.bugs

Cluster Templates
This commit is contained in:
Westly Wright 2019-08-02 14:49:05 -07:00 committed by GitHub
commit dde3c3edbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 278 additions and 165 deletions

View File

@ -1,7 +1,7 @@
import Resource from '@rancher/ember-api-store/models/resource';
import { reference } from '@rancher/ember-api-store/utils/denormalize';
import { inject as service } from '@ember/service';
import { computed, set } from '@ember/object';
import { computed, get, set } from '@ember/object';
import { alias } from '@ember/object/computed';
export default Resource.extend({
@ -114,12 +114,14 @@ export default Resource.extend({
validationErrors() {
let errors = [];
if (!get(this, 'name')) {
errors.push('Revision name is required');
}
if (errors.length > 0) {
return errors;
}
errors = this._super(...arguments);
return errors;
},
});

View File

@ -9,7 +9,8 @@
"ember-auto-import": "*",
"ember-cli-htmlbars": "*",
"ember-href-to": "*",
"ember-cli-babel": "*"
"ember-cli-babel": "*",
"ember-deep-set": "*"
},
"ember-addon": {
"paths": [

View File

@ -21,6 +21,7 @@ import ManageLabels from 'shared/mixins/manage-labels';
import { typeOf } from '@ember/utils';
import Semver, { major, minor } from 'semver';
import { on } from '@ember/object/evented';
import deepSet from 'ember-deep-set';
const EXCLUDED_KEYS = ['extra_args'];
@ -66,62 +67,63 @@ const {
} = C;
export default InputTextFile.extend(ManageLabels, ClusterDriver, {
globalStore: service(),
settings: service(),
growl: service(),
intl: service(),
clusterTemplates: service(),
access: service(),
router: service(),
globalStore: service(),
settings: service(),
growl: service(),
intl: service(),
clusterTemplates: service(),
access: service(),
router: service(),
layout,
authChoices: AUTHCHOICES,
ingressChoices: INGRESSCHOICES,
availableStrategies: AVAILABLE_STRATEGIES,
ingornedRkeOverrides: CLUSTER_TEMPLATE_IGNORED_OVERRIDES,
authChoices: AUTHCHOICES,
ingressChoices: INGRESSCHOICES,
availableStrategies: AVAILABLE_STRATEGIES,
ingornedRkeOverrides: CLUSTER_TEMPLATE_IGNORED_OVERRIDES,
configField: 'rancherKubernetesEngineConfig',
registry: 'default',
accept: '.yml, .yaml',
backupStrategy: 'local',
overrideCreatLabel: null,
loading: false,
pasteOrUpload: false,
model: null,
initialVersion: null,
registryUrl: null,
registryUser: null,
registryPass: null,
clusterOptErrors: null,
nodeNameErrors: null,
configField: 'rancherKubernetesEngineConfig',
registry: 'default',
accept: '.yml, .yaml',
backupStrategy: 'local',
overrideCreatLabel: null,
loading: false,
pasteOrUpload: false,
model: null,
initialVersion: null,
registryUrl: null,
registryUser: null,
registryPass: null,
clusterOptErrors: null,
nodeNameErrors: null,
existingNodes: null,
initialNodeCounts: null,
step: 1,
token: null,
taints: null,
labels: null,
etcd: false,
controlplane: false,
worker: true,
defaultDockerRootDir: null,
nodePoolErrors: null,
existingNodes: null,
initialNodeCounts: null,
step: 1,
token: null,
taints: null,
labels: null,
etcd: false,
controlplane: false,
worker: true,
defaultDockerRootDir: null,
nodePoolErrors: null,
windowsEnable: false,
isLinux: true,
weaveCustomPassword: false,
clusterTemplateCreate: false,
clusterTemplateQuestions: null,
forceExpandOnInit: false,
forceExpandAll: false,
applyClusterTemplate: null,
useClusterTemplate: false,
clusterTemplateRevisionId: null,
clusterTemplatesEnforced: false,
isNew: equal('mode', 'new'),
isEdit: equal('mode', 'edit'),
notView: or('isNew', 'isEdit'),
clusterState: alias('model.originalCluster.state'),
windowsEnable: false,
isLinux: true,
weaveCustomPassword: false,
clusterTemplateCreate: false,
clusterTemplateQuestions: null,
forceExpandOnInit: false,
forceExpandAll: false,
applyClusterTemplate: null,
useClusterTemplate: false,
clusterTemplateRevisionId: null,
clusterTemplatesEnforced: false,
selectedClusterTemplateId: null,
isNew: equal('mode', 'new'),
isEdit: equal('mode', 'edit'),
notView: or('isNew', 'isEdit'),
clusterState: alias('model.originalCluster.state'),
// Custom stuff
isCustom: equal('nodeWhich', 'custom'),
@ -133,8 +135,9 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
if (!this.useClusterTemplate && this.clusterTemplateRevisionId) {
setProperties(this, {
useClusterTemplate: true,
forceExpandOnInit: true,
useClusterTemplate: true,
forceExpandOnInit: true,
selectedClusterTemplateId: this.model.clusterTemplateRevision.clusterTemplateId,
})
}
@ -413,6 +416,14 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
return [...(errors || []), ...(clusterErrors || []), ...(clusterOptErrors || []), ...(otherErrors || [])];
}),
filteredClusterTemplates: computed('model.clusterTemplates.@each.{id,state,name,members}', function() {
return get(this, 'model.clusterTemplates');
}),
filteredTemplateRevisions: computed('selectedClusterTemplateId', 'model.clusterTemplateRevisions.@each.{id,state,name,members}', function() {
return get(this, 'model.clusterTemplateRevisions').filterBy('enabled').filterBy('clusterTemplateId', this.selectedClusterTemplateId);
}),
allTemplates: computed('model.clusterTemplates.[]', 'model.clusterTemplateRevisions.[]', function() {
const remapped = [];
let { clusterTemplates, clusterTemplateRevisions } = this.model;
@ -767,7 +778,12 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
},
willSave() {
const { cluster, configField: field } = this;
const {
applyClusterTemplate,
cluster,
configField: field,
} = this;
let ok = true;
this.checkKubernetesVersionSemVer();
@ -779,19 +795,6 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
}
}
if (typeOf(cluster.clearProvidersExcept) === 'function' || this.applyClusterTemplate && typeOf(cluster.buildClusterAnswersFromConfig) === 'function') {
if (this.applyClusterTemplate) {
// need to add overrides + user entry to answers on cluster, drop rkeconfig
let answers = this.buildClusterAnswersFromConfig(cluster, get(this, 'model.clusterTemplateRevision.questions'));
this.cluster.clearConfigFieldsForClusterTemplate();
set(cluster, 'answers', { values: answers });
} else {
cluster.clearProvidersExcept(field);
}
}
if (get(cluster, 'localClusterAuthEndpoint')) {
if (!get(cluster, 'rancherKubernetesEngineConfig') || isEmpty(get(cluster, 'rancherKubernetesEngineConfig'))) {
delete cluster.localClusterAuthEndpoint;
@ -800,22 +803,79 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
set(this, 'errors', null);
// TODO
if (this.applyClusterTemplate || this.clusterTemplateCreate) {
return true;
} else {
return this.validate();
ok = this.validate();
if (ok) {
if (typeOf(cluster.clearProvidersExcept) === 'function' || applyClusterTemplate && typeOf(this.buildClusterAnswersFromConfig) === 'function') {
if (applyClusterTemplate) {
let questions = get(this, 'model.clusterTemplateRevision.questions') || [];
let answers = [];
if (questions.length > 0) {
answers = this.buildClusterAnswersFromConfig(cluster, questions);
let errors = this.checkRequiredQuestionsHaveAnswers(questions, answers);
if (isEmpty(errors)) {
set(cluster, 'answers', { values: answers });
this.cluster.clearConfigFieldsForClusterTemplate();
} else {
set(this, 'errors', errors);
return false;
}
}
} else {
cluster.clearProvidersExcept(field);
}
}
}
return ok;
},
checkRequiredQuestionsHaveAnswers(questions, answers) {
const { intl } = this;
const required = questions.filterBy('required', true);
const errors = [];
if (questions.length > 0 && required.length > 0) {
required.forEach((rq) => {
if (!answers[rq.variable]) {
errors.push(intl.t('validation.required', { key: rq.variable }));
}
})
}
return errors;
},
validate() {
this._super(...arguments);
let errors = [];
let config = get(this, `config`);
let errors = [];
let { config, intl } = this;
if ( !get(this, 'isCustom') ) {
errors.pushObjects(get(this, 'nodePoolErrors'));
if (this.clusterTemplateCreate) {
if (this.model.clusterTemplateRevision) {
errors.pushObjects(this.model.clusterTemplateRevision.validationErrors());
}
} else {
if ( !get(this, 'isCustom') ) {
errors.pushObjects(get(this, 'nodePoolErrors'));
}
if ( get(config, 'cloudProvider.name') === 'azure' ) {
Object.keys(AzureInfo).forEach((key) => {
if ( get(AzureInfo, `${ key }.required`) && !get(config, `cloudProvider.azureCloudProvider.${ key }`)) {
if ( this.isNew || this.isEdit && key !== 'aadClientSecret' ) {
errors.push(intl.t('validation.required', { key }));
}
}
});
}
}
if ( get(config, 'services.kubeApi.podSecurityPolicy') &&
@ -833,18 +893,6 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
const clusterOptErrors = get(this, 'clusterOptErrors') || [];
if ( get(config, 'cloudProvider.name') === 'azure' ) {
const intl = get(this, 'intl');
Object.keys(AzureInfo).forEach((key) => {
if ( get(AzureInfo, `${ key }.required`) && !get(config, `cloudProvider.azureCloudProvider.${ key }`)) {
if ( this.isNew || this.isEdit && key !== 'aadClientSecret' ) {
errors.push(intl.t('validation.required', { key }));
}
}
});
}
set(this, 'errors', errors);
return errors.length === 0 && clusterOptErrors.length === 0;
@ -922,29 +970,33 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
loadToken() {
const cluster = get(this, 'primaryResource');
setProperties(this, {
step: 2,
loading: true
});
return cluster.getOrCreateToken().then((token) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
if (cluster.getOrCreateToken) {
setProperties(this, {
token,
loading: false
step: 2,
loading: true
});
}).catch((err) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
get(this, 'growl').fromError('Error getting command', err);
return cluster.getOrCreateToken().then((token) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
set(this, 'loading', false);
});
setProperties(this, {
token,
loading: false
});
}).catch((err) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
get(this, 'growl').fromError('Error getting command', err);
set(this, 'loading', false);
});
} else {
return;
}
},
findExcludedKeys(resourceFields) {
@ -1268,7 +1320,7 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
let path = question.variable;
if (!question.variable.includes('uiOverride') && question.default) {
set(primaryResource, path, question.default);
deepSet(primaryResource, path, question.default);
}
set(question, 'primaryResource', primaryResource);

View File

@ -33,31 +33,44 @@
<section class="cluster-template-select mb-5">
<div class="row">
{{#if (or (or model.clusterTemplateRevisions model.clusterTemplateRevision) clusterTemplatesEnforced)}}
<div class="col span-6">
<div>
<label class="acc-label" for="use-existing-cluster-template">
{{input
class="input-lg"
type="checkbox"
checked=useClusterTemplate
id="use-existing-cluster-template"
disabled=(or clusterTemplatesEnforced pasteOrUpload)
disabled=(or isEdit (or clusterTemplatesEnforced pasteOrUpload))
}}
{{t "clusterNew.rke.clustersSelectTemplate.label"}}
</label>
{{#if useClusterTemplate}}
</div>
{{#if useClusterTemplate}}
<div class="col span-6">
{{new-select
id="input-cluster-template-select"
classNames="form-control"
optionValuePath="clusterTemplateRevisionId"
optionLabelPath="clusterTemplateRevisionId"
optionGroupPath="clusterTemplateName"
content=allTemplates
value=clusterTemplateRevisionId
optionValuePath="id"
optionLabelPath="name"
content=filteredClusterTemplates
value=selectedClusterTemplateId
prompt=(t "clusterNew.rke.clustersSelectTemplate.select.prompt")
localizedPrompt=true
}}
{{/if}}
</div>
</div>
<div class="col span-6">
{{new-select
classNames="form-control"
optionValuePath="id"
optionLabelPath="name"
content=filteredTemplateRevisions
value=clusterTemplateRevisionId
disabled=(not selectedClusterTemplateId)
prompt=(t "clusterNew.rke.clustersSelectTemplateRevision.select.prompt")
localizedPrompt=true
}}
</div>
{{/if}}
{{/if}}
</div>
</section>

View File

@ -8,6 +8,7 @@ import { alias } from '@ember/object/computed';
import { isEmpty } from '@ember/utils';
import C from 'ui/utils/constants';
import { azure as AzureInfo } from './cloud-provider-info';
import { next } from '@ember/runloop';
const azureDefaults = C.AZURE_DEFAULTS;
const GENERIC_PATH = 'cluster.rancherKubernetesEngineConfig.cloudProvider.cloudConfig';
@ -110,30 +111,47 @@ export default Component.extend({
'clusterTemplateCreate',
'applyClusterTemplate',
'clusterTemplateRevision.questions',
'clusterTemplateRevision.id',
function() {
let { clusterTemplateRevision, applyClusterTemplate } = this;
if (applyClusterTemplate && clusterTemplateRevision && clusterTemplateRevision.questions) {
let found = clusterTemplateRevision.questions.filter((ctr) => {
return ctr.variable.includes('rancherKubernetesEngineConfig.cloudProvider');
});
if (applyClusterTemplate && clusterTemplateRevision) {
if (clusterTemplateRevision.questions) {
let found = clusterTemplateRevision.questions.filter((ctr) => {
return ctr.variable.includes('rancherKubernetesEngineConfig.cloudProvider');
});
return found.length >= 1;
if (found.length === 0 && this.selectedCloudProvider !== 'none') {
set(this, 'selectedCloudProvider', 'none');
}
return found.length >= 1;
} else {
if (this.configName) {
next(() => {
set(this, 'selectedCloudProvider', this.configName);
});
}
}
} else {
if (!this.configName) {
next(() => {
set(this, 'selectedCloudProvider', 'none');
});
}
}
return false;
}),
isCreateClusterOrClusterTemplate: computed('', function() {
const { clusterTemplateCreate, applyClusterTemplate } = this;
isCreateClusterOrClusterTemplate: computed('applyClusterTemplate', function() {
const { applyClusterTemplate } = this;
if (!clusterTemplateCreate && !applyClusterTemplate) {
return true;
} else if (clusterTemplateCreate) {
if (applyClusterTemplate) {
return false;
} else {
return true;
}
return false;
}),
checkDefaults(record) {

View File

@ -196,7 +196,7 @@ export default Component.extend({
ignoreFields,
} = this;
allQuestions = allQuestions.slice();
allQuestions = ( allQuestions || []).slice();
allQuestions.forEach((q) => {
if (ignoreFields.includes(q.variable)) {

View File

@ -6,11 +6,8 @@ import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';
import Errors from 'ui/utils/errors';
import { randomStr } from 'shared/utils/util';
import { reject } from 'rsvp';
export default Component.extend(ViewNewEdit, ChildHook, {
globalStore: service(),
router: service(),
@ -36,12 +33,6 @@ export default Component.extend(ViewNewEdit, ChildHook, {
this._super(...arguments);
set(this, 'originalCluster', get(this, 'clusterTemplateRevision.clusterConfig').clone());
let { clusterTemplateRevision } = this;
if (!clusterTemplateRevision.name) {
set(clusterTemplateRevision, 'name', `revision-${ randomStr(8, 8, 'loweralpha') }`);
}
},
actions: {

View File

@ -18,6 +18,7 @@ export default Component.extend(ViewNewEdit, ChildHook, {
intl: service(),
access: service(),
cookies: service(),
router: service(),
layout,
step: 1,
@ -28,6 +29,7 @@ export default Component.extend(ViewNewEdit, ChildHook, {
reloadingSchema: false,
schemaReloaded: false,
applyClusterTemplate: false,
routeLoading: false,
showClassicLauncher: false,
nodePoolErrors: null,
@ -58,11 +60,26 @@ export default Component.extend(ViewNewEdit, ChildHook, {
if ( isEmpty(get(this, 'cluster.id')) ){
set(this, 'newCluster', true);
}
this.router.on('routeWillChange', (/* transition */) => {
if ( !this.isDestroyed || !this.isDestroying ) {
set(this, 'routeLoading', true);
}
});
this.router.on('routeDidChange', (/* transition */) => {
if ( !this.isDestroyed || !this.isDestroying ) {
set(this, 'routeLoading', false);
}
});
},
actions: {
updateFromYaml(newOpts) {
this.cluster.replaceWith(newOpts);
if (this.isEdit) {
this.cluster.merge(newOpts);
} else {
this.cluster.replaceWith(newOpts);
}
},
clickNext() {

View File

@ -82,22 +82,24 @@
{{/accordion-list}}
{{/if}}
{{component driverInfo.driverComponent
applyClusterTemplate=applyClusterTemplate
clusterTemplateQuestions=model.clusterTemplateRevision.questions
clusterTemplateRevisionId=clusterTemplateRevisionId
clusterErrors=errors
mode=mode
model=model
nodePoolErrors=nodePoolErrors
nodeWhich=driverInfo.nodeWhich
originalCluster=originalCluster
otherErrors=memberErrors
save=(action "save")
close=(action "close")
registerHook=(action "registerHook")
updateFromYaml=(action "updateFromYaml")
}}
{{#unless routeLoading}}
{{component driverInfo.driverComponent
applyClusterTemplate=applyClusterTemplate
clusterTemplateQuestions=model.clusterTemplateRevision.questions
clusterTemplateRevisionId=clusterTemplateRevisionId
clusterErrors=errors
mode=mode
model=model
nodePoolErrors=nodePoolErrors
nodeWhich=driverInfo.nodeWhich
originalCluster=originalCluster
otherErrors=memberErrors
save=(action "save")
close=(action "close")
registerHook=(action "registerHook")
updateFromYaml=(action "updateFromYaml")
}}
{{/unless}}
{{#if (and isEdit (not provider))}}
{{top-errors errors=errors}}

View File

@ -54,7 +54,6 @@ export default Component.extend({
initialVersion,
defaultK8sVersion,
applyClusterTemplate = false,
clusterTemplateCreate = false,
clusterTemplateQuestions = [],
} = this;
@ -70,7 +69,7 @@ export default Component.extend({
let maxVersion = maxSatisfying(versions, defaultK8sVersionRange);
if (applyClusterTemplate || clusterTemplateCreate) {
if ( applyClusterTemplate ) {
var overrideMatch = ( clusterTemplateQuestions || [] ).findBy('variable', 'rancherKubernetesEngineConfig.kubernetesVersion');
if (overrideMatch) {
@ -78,7 +77,10 @@ export default Component.extend({
// the template creator lets them override this but the initial version is a dot x so we should choose the biggest version in the .x range
maxVersion = maxSatisfying(versions, initialVersion);
} else {
supportedVersionsRange = overrideMatch.satisfies;
if (overrideMatch.satisfies) {
supportedVersionsRange = overrideMatch.satisfies;
}
maxVersion = maxSatisfying(versions, supportedVersionsRange);
}
}

View File

@ -69,6 +69,10 @@ export default Mixin.create({
cb(true);
})
.catch((err) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
this.send('error', err);
this.errorSaving(err);
cb(false);

View File

@ -61,6 +61,7 @@
"ember-concurrency": "^0.8.24",
"ember-copy": "^1.0.0",
"ember-credit-card": "^2.4.0",
"ember-deep-set": "^0.2.0",
"ember-drag-drop": "^0.4.7",
"ember-engines": "^0.6.1",
"ember-export-application-global": "^2.0.0",

View File

@ -3190,10 +3190,13 @@ clusterNew:
label: EIP Share Type
rke:
clustersSelectTemplate:
label: "Use an existing cluster template"
label: "Use an existing cluster template and revision"
select:
label: Cluster Templates
prompt: Select a cluster template
clustersSelectTemplateRevision:
select:
prompt: Select a cluster template revision
etcd:
enabled:
label: Recurring etcd Snapshot Enabled

View File

@ -4641,6 +4641,13 @@ ember-credit-card@^2.4.0:
ember-cli-htmlbars "2.0.3"
ember-model-validator "^2.18.0"
ember-deep-set@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ember-deep-set/-/ember-deep-set-0.2.0.tgz#93428b599f884c3da0550cbcc062b9ec5969a71e"
integrity sha512-3vg9Cw4CIInXzufZMQmScClg23mUw+2ybO53L51spFYP/eGaVmGduWmhrVljyl4lHKN7hW/jvG/YVWtwTPSTKA==
dependencies:
ember-cli-babel "^7.1.2"
ember-diff-attrs@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/ember-diff-attrs/-/ember-diff-attrs-0.2.2.tgz#57baf6907957de004d9aff947809dfe78a054b3b"