mirror of https://github.com/rancher/ui.git
Merge pull request #3184 from westlywright/cluster.templates.bugs
Cluster Templates
This commit is contained in:
commit
dde3c3edbc
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
||||
|
|
@ -118,6 +119,7 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
|
|||
useClusterTemplate: false,
|
||||
clusterTemplateRevisionId: null,
|
||||
clusterTemplatesEnforced: false,
|
||||
selectedClusterTemplateId: null,
|
||||
isNew: equal('mode', 'new'),
|
||||
isEdit: equal('mode', 'edit'),
|
||||
notView: or('isNew', 'isEdit'),
|
||||
|
|
@ -135,6 +137,7 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
|
|||
setProperties(this, {
|
||||
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,24 +803,81 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
|
|||
|
||||
set(this, 'errors', null);
|
||||
|
||||
// TODO
|
||||
if (this.applyClusterTemplate || this.clusterTemplateCreate) {
|
||||
return true;
|
||||
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 {
|
||||
return this.validate();
|
||||
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 { config, intl } = this;
|
||||
|
||||
|
||||
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') &&
|
||||
!get(this, 'primaryResource.defaultPodSecurityPolicyTemplateId') ) {
|
||||
errors.push(get(this, 'intl').t('clusterNew.psp.required'));
|
||||
|
|
@ -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,6 +970,7 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
|
|||
loadToken() {
|
||||
const cluster = get(this, 'primaryResource');
|
||||
|
||||
if (cluster.getOrCreateToken) {
|
||||
setProperties(this, {
|
||||
step: 2,
|
||||
loading: true
|
||||
|
|
@ -945,6 +994,9 @@ export default InputTextFile.extend(ManageLabels, ClusterDriver, {
|
|||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</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 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>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
if (applyClusterTemplate && clusterTemplateRevision) {
|
||||
if (clusterTemplateRevision.questions) {
|
||||
let found = clusterTemplateRevision.questions.filter((ctr) => {
|
||||
return ctr.variable.includes('rancherKubernetesEngineConfig.cloudProvider');
|
||||
});
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export default Component.extend({
|
|||
ignoreFields,
|
||||
} = this;
|
||||
|
||||
allQuestions = allQuestions.slice();
|
||||
allQuestions = ( allQuestions || []).slice();
|
||||
|
||||
allQuestions.forEach((q) => {
|
||||
if (ignoreFields.includes(q.variable)) {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
if (this.isEdit) {
|
||||
this.cluster.merge(newOpts);
|
||||
} else {
|
||||
this.cluster.replaceWith(newOpts);
|
||||
}
|
||||
},
|
||||
|
||||
clickNext() {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
{{/accordion-list}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless routeLoading}}
|
||||
{{component driverInfo.driverComponent
|
||||
applyClusterTemplate=applyClusterTemplate
|
||||
clusterTemplateQuestions=model.clusterTemplateRevision.questions
|
||||
|
|
@ -98,6 +99,7 @@
|
|||
registerHook=(action "registerHook")
|
||||
updateFromYaml=(action "updateFromYaml")
|
||||
}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if (and isEdit (not provider))}}
|
||||
{{top-errors errors=errors}}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
if (overrideMatch.satisfies) {
|
||||
supportedVersionsRange = overrideMatch.satisfies;
|
||||
}
|
||||
|
||||
maxVersion = maxSatisfying(versions, supportedVersionsRange);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue