From 9bd0980b81ddc2d51cf640a80cbf6905e15a8b64 Mon Sep 17 00:00:00 2001 From: Westly Wright Date: Thu, 18 Apr 2019 16:01:03 -0700 Subject: [PATCH] Refactor new catalog to handle requiredNamespace correctly rancher/rancher#19697 --- app/catalog-tab/launch/route.js | 143 +++++++++++++---------- app/catalog-tab/launch/template.hbs | 8 +- app/components/new-catalog/component.js | 148 ++++++++++++++---------- app/components/new-catalog/template.hbs | 22 +++- translations/en-us.yaml | 1 + 5 files changed, 197 insertions(+), 125 deletions(-) diff --git a/app/catalog-tab/launch/route.js b/app/catalog-tab/launch/route.js index 67eab7ae3..cb5d33bcd 100644 --- a/app/catalog-tab/launch/route.js +++ b/app/catalog-tab/launch/route.js @@ -5,12 +5,14 @@ import Route from '@ember/routing/route'; import { get, set, setProperties } from '@ember/object'; import { randomStr } from 'shared/utils/util'; import C from 'ui/utils/constants'; +import Util from 'ui/utils/util'; export default Route.extend({ modalService: service('modal'), catalog: service(), scope: service(), clusterStore: service(), + settings: service(), parentRoute: 'catalog-tab', @@ -39,71 +41,92 @@ export default Route.extend({ } return hash(dependencies, 'Load dependencies').then((results) => { - let neuNSN = results.tpl.get('displayName'); - let dupe = results.namespaces.findBy('id', neuNSN); + var def = get(results, 'tpl.defaultVersion'); + var links = get(results, 'tpl.versionLinks'); + var app = get(results, 'app'); + var catalogTemplateUrl = null; - if ( !results.namespace ) { - let { newNamespaceName, newNS } = this.newNamespace(dupe, neuNSN); - - if ( dupe ) { - neuNSN = newNamespaceName; - } - - results.namespace = newNS; + if (app && params.appId && !params.upgrade) { + def = get(app, 'externalIdInfo.version'); } - let kind = 'helm'; - let neuApp = null; - var links; + catalogTemplateUrl = links[def]; - links = results.tpl.versionLinks; + var version = get(this, 'settings.rancherVersion'); - var verArr = Object.keys(links).filter((key) => !!links[key]) - .map((key) => ({ - version: key, - sortVersion: key, - link: links[key] - })); + if ( version ) { + catalogTemplateUrl = Util.addQueryParam(catalogTemplateUrl, 'rancherVersion', version); + } - if (results.app) { - if (get(params, 'clone')) { - let { newNamespaceName, newNS } = this.newNamespace(dupe, neuNSN); + return this.catalog.fetchByUrl(catalogTemplateUrl).then((catalogTemplate) => { + let { requiredNamespace } = catalogTemplate; + let neuNamespaceName = requiredNamespace ? requiredNamespace : results.tpl.get('displayName'); + let existingNamespace = results.namespaces.findBy('id', neuNamespaceName); + let { namespace } = results; + let kind = 'helm'; + let neuApp = null; - if ( dupe ) { - neuNSN = newNamespaceName; + let newNamespaceName; + + if ( !namespace ) { + if (requiredNamespace) { + if (existingNamespace) { + // we dont want a unique namespace in this case. + namespace = existingNamespace; + } else { + ( { newNamespaceName: neuNamespaceName, newNS: namespace } = this.newNamespace(existingNamespace, neuNamespaceName) ); + } + } else { + ( { newNamespaceName, newNS: namespace } = this.newNamespace(existingNamespace, neuNamespaceName) ); + + if ( existingNamespace ) { + neuNamespaceName = newNamespaceName; + } } - - results.namespace = newNS; - - neuApp = results.app.cloneForNew(); - set(neuApp, 'name', results.namespace.name); - } else { - neuApp = results.app; } - } else { - neuApp = store.createRecord({ - type: 'app', - name: results.namespace.name, + + var verArr = Object.keys(links).filter((key) => !!links[key]) + .map((key) => ({ + version: key, + sortVersion: key, + link: links[key] + })); + + if (results.app) { + if (get(params, 'clone')) { + neuApp = results.app.cloneForNew(); + + set(neuApp, 'name', this.dedupeName(get(namespace, 'displayName'))); + } else { + neuApp = results.app; + } + } else { + neuApp = store.createRecord({ + type: 'app', + name: neuNamespaceName, + }); + } + + if ( neuApp.id ) { + verArr.filter((ver) => ver.version === get(neuApp, 'externalIdInfo.version')) + .forEach((ver) => { + set(ver, 'version', `${ ver.version } (current)`); + }) + } + + return EmberObject.create({ + allTemplates: this.modelFor(get(this, 'parentRoute')).get('catalog'), + catalogApp: neuApp, + catalogTemplateUrl: links[def], + catalogTemplate, + namespace, + namespaces: results.namespaces, + tpl: results.tpl, + tplKind: kind, + upgradeTemplate: results.upgrade, + versionLinks: links, + versionsArray: verArr, }); - } - - if ( neuApp.id ) { - verArr.filter((ver) => ver.version === get(neuApp, 'externalIdInfo.version')) - .forEach((ver) => { - set(ver, 'version', `${ ver.version } (current)`); - }) - } - - return EmberObject.create({ - allTemplates: this.modelFor(get(this, 'parentRoute')).get('catalog'), - catalogApp: neuApp, - namespace: results.namespace, - namespaces: results.namespaces, - tpl: results.tpl, - tplKind: kind, - upgradeTemplate: results.upgrade, - versionLinks: links, - versionsArray: verArr, }); }); }, @@ -132,11 +155,15 @@ export default Route.extend({ }, }, - newNamespace(duplicateName, newNamespaceName) { + dedupeName(name) { const suffix = randomStr(5, 5, 'novowels'); - if ( duplicateName ) { - newNamespaceName = `${ get(duplicateName, 'displayName') }-${ suffix }`; + return `${ name }-${ suffix }`; + }, + + newNamespace(duplicateNamespace, newNamespaceName) { + if ( duplicateNamespace ) { + newNamespaceName = this.dedupeName(get(duplicateNamespace, 'displayName')); } const newNS = get(this, 'clusterStore').createRecord({ diff --git a/app/catalog-tab/launch/template.hbs b/app/catalog-tab/launch/template.hbs index e341a40cf..68d34cc4e 100644 --- a/app/catalog-tab/launch/template.hbs +++ b/app/catalog-tab/launch/template.hbs @@ -1,13 +1,15 @@ {{new-catalog allTemplates=model.allTemplates - namespaces=model.namespaces cancel=(action "cancel") catalogApp=model.catalogApp + catalogTemplate=model.catalogTemplate namespaceResource=model.namespace + namespaces=model.namespaces parentRoute=parentRoute - upgrade=model.upgradeTemplate - templateResource=model.tpl + selectedTemplateUrl=model.catalogTemplateUrl templateKind=model.tplKind + templateResource=model.tpl + upgrade=model.upgradeTemplate versionLinks=model.versionLinks versionsArray=model.versionsArray }} diff --git a/app/components/new-catalog/component.js b/app/components/new-catalog/component.js index 27c15f4eb..f5b12c8da 100644 --- a/app/components/new-catalog/component.js +++ b/app/components/new-catalog/component.js @@ -57,6 +57,7 @@ export default Component.extend(NewOrEdit, CatalogApp, ChildHook, { questionsArray: null, selectedTemplateUrl: null, selectedTemplateModel: null, + catalogTemplate: null, readmeContent: null, appReadmeContent: null, pastedAnswers: null, @@ -67,7 +68,7 @@ export default Component.extend(NewOrEdit, CatalogApp, ChildHook, { primaryResource: alias('namespaceResource'), editing: notEmpty('catalogApp.id'), - requiredNamespace: alias('selectedTemplateModel.requiredNamespace'), + requiredNamespace: alias('selectedTemplateModel.requiredNamespace'), init() { this._super(...arguments); @@ -75,11 +76,15 @@ export default Component.extend(NewOrEdit, CatalogApp, ChildHook, { scheduleOnce('afterRender', () => { if ( get(this, 'selectedTemplateUrl') ) { - this.templateChanged(); + if (this.catalogTemplate) { + this.initTemplateModel(this.catalogTemplate); + } else { + this.templateChanged(); + } } else { - var def = get(this, 'templateResource.defaultVersion'); + var def = get(this, 'templateResource.defaultVersion'); var links = get(this, 'versionLinks'); - var app = get(this, 'catalogApp'); + var app = get(this, 'catalogApp'); if (get(app, 'id') && !get(this, 'upgrade')) { def = get(app, 'externalIdInfo.version'); @@ -96,11 +101,12 @@ export default Component.extend(NewOrEdit, CatalogApp, ChildHook, { didRender() { if (!this.get('srcSet')) { - this.set('srcSet', true); + set(this, 'srcSet', true); const $icon = this.$('img'); $icon.attr('src', $icon.data('src')); + this.$('img').on('error', () => { $icon.attr('src', `${ this.get('app.baseAssets') }assets/images/generic-catalog.svg`); }); @@ -213,71 +219,97 @@ export default Component.extend(NewOrEdit, CatalogApp, ChildHook, { var selectedTemplateModel = yield get(this, 'catalog').fetchByUrl(url) .then((response) => { if (response.questions) { - const questions = []; - const customAnswers = {}; - - response.questions.forEach((q) => { - questions.push(q); - const subquestions = get(q, 'subquestions'); - - if ( subquestions ) { - questions.pushObjects(subquestions); - } - }); - questions.forEach((item) => { - // This will be the component that is rendered to edit this answer - item.inputComponent = `schema/input-${ item.type }`; - - // Only types marked supported will show the component, Ember will explode if the component doesn't exist - item.supported = C.SUPPORTED_SCHEMA_INPUTS.indexOf(item.type) >= 0; - - if (typeof current[item.variable] !== 'undefined') { - // If there's an existing value, use it (for upgrade) - item.answer = current[item.variable]; - } else if (item.type === 'service' || item.type === 'certificate') { - // Loaded async and then the component picks the default - } else if ( item.type === 'boolean' ) { - // Coerce booleans - item.answer = (item.default === 'true' || item.default === true); - } else { - // Everything else - item.answer = item.default || null; - } - }); - - Object.keys(current).forEach((key) => { - const q = questions.findBy('variable', key); - - if ( !q ) { - customAnswers[key] = current[key]; - } - }); - - response.customAnswers = customAnswers; + this.parseQuestionsAndAnswers(response, current); } return response; }); + if (selectedTemplateModel && selectedTemplateModel.requiredNamespace) { + set(this, 'primaryResource.name', selectedTemplateModel.requiredNamespace); + } + set(this, 'selectedTemplateModel', selectedTemplateModel); - const files = Object.keys(selectedTemplateModel.get('files')) || []; - - if ( files.length > 0 ) { - const valuesYaml = files.find((file) => file.endsWith('/values.yaml')); - - set(this, 'previewTab', valuesYaml ? valuesYaml : files[0]); - } + this.initPreviewTab(selectedTemplateModel); } else { - set(this, 'selectedTemplateModel', null); - set(this, 'readmeContent', null); - set(this, 'appReadmeContent', null); - set(this, 'noAppReadme', false); + setProperties(this, { + selectedTemplateModel: null, + readmeContent: null, + appReadmeContent: null, + noAppReadme: false, + }); } this.updateReadme(); }), + initTemplateModel(templateModel) { + let currentAnswers = get(this, 'catalogApp.answers') || {}; + + this.parseQuestionsAndAnswers(templateModel, currentAnswers); + this.initPreviewTab(templateModel); + + set(this, 'selectedTemplateModel', templateModel); + + this.updateReadme(); + }, + + initPreviewTab(selectedTemplateModel) { + const files = Object.keys(selectedTemplateModel.get('files')) || []; + + if ( files.length > 0 ) { + const valuesYaml = files.find((file) => file.endsWith('/values.yaml')); + + set(this, 'previewTab', valuesYaml ? valuesYaml : files[0]); + } + }, + + parseQuestionsAndAnswers(template, currentAnswers) { + const questions = []; + const customAnswers = {}; + + template.questions.forEach((q) => { + questions.push(q); + const subquestions = get(q, 'subquestions'); + + if ( subquestions ) { + questions.pushObjects(subquestions); + } + }); + + questions.forEach((item) => { + // This will be the component that is rendered to edit this answer + item.inputComponent = `schema/input-${ item.type }`; + + // Only types marked supported will show the component, Ember will explode if the component doesn't exist + item.supported = C.SUPPORTED_SCHEMA_INPUTS.indexOf(item.type) >= 0; + + if (typeof currentAnswers[item.variable] !== 'undefined') { + // If there's an existing value, use it (for upgrade) + item.answer = currentAnswers[item.variable]; + } else if (item.type === 'service' || item.type === 'certificate') { + // Loaded async and then the component picks the default + } else if ( item.type === 'boolean' ) { + // Coerce booleans + item.answer = (item.default === 'true' || item.default === true); + } else { + // Everything else + item.answer = item.default || null; + } + }); + + Object.keys(currentAnswers).forEach((key) => { + const q = questions.findBy('variable', key); + + if ( !q ) { + customAnswers[key] = currentAnswers[key]; + } + }); + + template.customAnswers = customAnswers; + }, + validate() { this._super(); @@ -300,8 +332,6 @@ export default Component.extend(NewOrEdit, CatalogApp, ChildHook, { if ( requiredNamespace && (get(this, 'namespaces') || []).findBy('id', requiredNamespace) ) { return resolve(get(this, 'primaryResource')); - } else if ( requiredNamespace ) { - set(this, 'primaryResource.name', requiredNamespace); } return this._super(...arguments); diff --git a/app/components/new-catalog/template.hbs b/app/components/new-catalog/template.hbs index 860a5186d..514bc700b 100644 --- a/app/components/new-catalog/template.hbs +++ b/app/components/new-catalog/template.hbs @@ -120,11 +120,23 @@
- {{form-namespace - namespace=primaryResource - errors=namespaceErrors - registerHook=(action "registerHook") - }} + {{#if selectedTemplateModel.requiredNamespace}} + +

+ {{primaryResource.displayName}} +

+

+ This app requires the specified namespace to be unchanged. +

+ {{else}} + {{form-namespace + namespace=primaryResource + errors=namespaceErrors + registerHook=(action "registerHook") + }} + {{/if}}
diff --git a/translations/en-us.yaml b/translations/en-us.yaml index 3e96678de..29da63d20 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -5715,6 +5715,7 @@ newCatalog: forceUpgrade: Delete and recreate resources if needed during the upgrade forceRollback: Delete and recreate resources if needed during the rollback templateFiles: Template Files + requiredNamespace: Required Namespace seeMore: More information... saveConfigure: Configure saveNew: Launch