diff --git a/electron/app/js/ipcRendererPreload.js b/electron/app/js/ipcRendererPreload.js index 43ca34ddd..5ecddae6f 100644 --- a/electron/app/js/ipcRendererPreload.js +++ b/electron/app/js/ipcRendererPreload.js @@ -190,6 +190,7 @@ contextBridge.exposeInMainWorld( 'k8s-get-service-details', 'k8s-get-ingresses', 'k8s-get-operator-version-from-domain-config-map', + 'k8s-get-operator-version', 'k8s-get-k8s-config', 'k8s-get-k8s-cluster-info', 'k8s-get-wko-domain-status', diff --git a/electron/app/js/kubectlUtils.js b/electron/app/js/kubectlUtils.js index 4ee0c6548..d7bb01160 100644 --- a/electron/app/js/kubectlUtils.js +++ b/electron/app/js/kubectlUtils.js @@ -209,6 +209,28 @@ async function getOperatorLogs(kubectlExe, operatorNamespace, options) { }); } +async function getOperatorVersion(kubectlExe, operatorNamespace, options) { + const results = { + isSuccess: true + }; + + return new Promise(resolve => { + getOperatorLogs(kubectlExe, operatorNamespace, options).then(logResult => { + if (logResult.isSuccess === false) { + results.isSuccess = false; + results.reason = i18n.t('kubectl-get-operator-version-error-message', {error: logResult.reason}); + return resolve(results); + } + _getOperatorVersionFromLogs(logResult.operatorLogs, results); + resolve(results); + }).catch(err => { + results.isSuccess = false; + results.reason = i18n.t('kubectl-get-operator-version-error-message', {error: getErrorMessage(err)}); + resolve(results); + }); + }); +} + async function getOperatorVersionFromDomainConfigMap(kubectlExe, domainNamespace, options) { const args = [ 'get', 'configmap', 'weblogic-scripts-cm', '-n', domainNamespace, '-o', 'jsonpath={.metadata.labels.weblogic\\.operatorVersion}']; @@ -346,7 +368,7 @@ async function validateApplicationExist(kubectlExe, options, application, namesp return Promise.resolve(result); } -async function isOperatorAlreadyInstalled(kubectlExe, operatorName, operatorNamespace, options) { +async function isOperatorAlreadyInstalled(kubectlExe, operatorNamespace, options) { // We are currently using kubectl to see if the operator deployment exists. The operator deployment // name is always weblogic-operator... // @@ -971,6 +993,43 @@ function isNotFoundError(err) { return /\(NotFound\)/.test(errString); } +function _getOperatorVersionFromLogs(operatorLogs, results) { + const versionRegex = /^Oracle WebLogic Kubernetes Operator, version:\s*(\d+\.\d+\.\d+),.*$/; + if (Array.isArray(operatorLogs) && operatorLogs.length > 0) { + for (const logEntry of operatorLogs) { + const parsedEntry = _parseLogEntryAsJson(logEntry); + if (typeof parsedEntry === 'undefined') { + continue; + } + + const message = parsedEntry.message || ''; + const match = message.match(versionRegex); + if (Array.isArray(match) && match.length > 1) { + results.isSuccess = true; + results.version = match[1]; + getLogger().debug('Found installed operator version %s', results.version); + return; + } + } + results.isSuccess = false; + results.reason = i18n.t('kubectl-get-operator-version-not-found-error-message'); + } else { + results.isSuccess = false; + results.reason = i18n.t('kubectl-get-operator-version-logs-empty-error-message'); + } +} + +function _parseLogEntryAsJson(logEntry) { + let result; + + try { + result = JSON.parse(logEntry); + } catch (err) { + // Assume the entry is not in JSON format and return undefined + } + return result; +} + module.exports = { apply, applyUsingUrl, @@ -982,6 +1041,7 @@ module.exports = { createOrReplaceTLSSecret, createServiceAccountIfNotExists, getCurrentContext, + getOperatorVersion, isOperatorAlreadyInstalled, isVerrazzanoInstalled, setCurrentContext, diff --git a/electron/app/locales/en/electron.json b/electron/app/locales/en/electron.json index d613f14c4..4c17a6d9c 100644 --- a/electron/app/locales/en/electron.json +++ b/electron/app/locales/en/electron.json @@ -315,7 +315,9 @@ "kubectl-cluster-info-error-message": "Unable to get the Kubernetes cluster info: {{error}}", "kubectl-get-wko-domain-status-error-message": "Unable to get Kubernetes wko domain status: {{error}}", "kubectl-get-operator-status-error-message": "Unable to get wko operator status: {{error}}", - "kubectl-get-operator-logs-error-message": "Unable to get wko operator logs: {{error}}", + "kubectl-get-operator-logs-error-message": "Unable to get WebLogic Kubernetes Operator logs: {{error}}", + "kubectl-get-operator-version-error-message": "Unable to determine the WebLogic Kubernetes Operator version: {{error}}", + "kubectl-get-operator-version-logs-empty-error-message": "Unable to determine the WebLogic Kubernetes Operator version because the operator log was empty", "kubectl-get-operator-version-from-cm-error-message": "Unable to get operator version: {{error}}", "kubectl-tlsfile-not-specified-error-message": "TLS file path was not provided", "kubectl-tlsfile-not-exists-error-message": "TLS file {{filePath}} does not exist", @@ -323,6 +325,7 @@ "kubectl-get-vz-install-failed-error-message": "Unable to get Verrazzano installation: {{error}}", "kubectl-get-named-vz-install-error-message": "Unable to get Verrazzano installation with name {{name}}: {{error}}", "kubectl-get-vz-application-status-error-message": "Unable to get Verrazzano application status: {{error}}", + "kubectl-get-operator-version-not-found-error-message": "Failed to find the operator version from its log entries", "helm-not-specified-error-message": "Helm executable path was not provided", "helm-not-exists-error-message": "Helm executable {{filePath}} does not exist", diff --git a/electron/app/locales/en/webui.json b/electron/app/locales/en/webui.json index b2216826d..81870bbfb 100644 --- a/electron/app/locales/en/webui.json +++ b/electron/app/locales/en/webui.json @@ -517,6 +517,9 @@ "domain-design-pv-volume-log-home-enabled-help": "Whether or not to write the WebLogic Server log files to a location outside the domain home.", "domain-design-pv-volume-log-home-label": "Log Home Path", "domain-design-pv-volume-log-home-help": "The path to use to write the WebLogic Server log files.", + "domain-design-wko-installed-version-label": "WebLogic Kubernetes Operator Installed Version", + "domain-design-wko-installed-version-help": "The version of the WebLogic Kubernetes Operator that is installed in the Kubernetes cluster.", + "domain-design-wko-installed-version-tooltip": "Get the installed version of the WebLogic Kubernetes Operator from the Kubernetes cluster.", "domain-design-image-title": "Primary Image to Use for the Domain", "domain-design-hints-goto-create-image": "Go To Create Primary Image Page", @@ -558,6 +561,10 @@ "domain-design-aux-image-registry-address-help": "The image registry hostname and port to use to log in to the registry, if required. This field's value is parsed from the Auxiliary Image Tag field and an empty value assumes Docker Hub is the registry being used.", "domain-design-aux-image-pull-policy-label": "Auxiliary Image Pull Policy", "domain-design-aux-image-pull-policy-help": "When to pull the domain's auxiliary image from the image registry.", + "domain-design-aux-image-source-wdt-home-label": "WebLogic Deploy Tooling Install Home in Auxiliary Image", + "domain-design-aux-image-source-wdt-home-help": "The source location of the WebLogic Deploy Tooling installation within the auxiliary image.", + "domain-design-aux-image-source-model-home-label": "WebLogic Deploy Tooling Model Home in Auxiliary Image", + "domain-design-aux-image-source-model-home-help": "The source directory location of the WebLogic Deploy Tooling model files within the auxiliary image.", "domain-design-aux-image-registry-pull-requires-authentication-label": "Specify Auxiliary Image Pull Credentials", "domain-design-aux-image-registry-pull-requires-authentication-help": "Whether you want to specify authentication credentials for pulling the auxiliary image from the registry.", "domain-design-aux-image-registry-use-existing-pull-secret-label": "Use Existing Auxiliary Image Pull Secret", @@ -1104,6 +1111,17 @@ "kubectl-get-current-context-error-title": "Kubernetes Client Failed", "kubectl-get-current-context-error-message": "kubectl failed to get the current Kubernetes cluster context: {{error}}.", + "wko-get-install-version-aborted-error-title": "Getting Installed WebLogic Kubernetes Operator Version Aborted", + "wko-get-install-version-kubectl-exe-invalid-error-message": "Unable to get the installed WebLogic Kubernetes Operator version because the Kubernetes client executable is invalid: {{error}}.", + "wko-get-install-version-project-not-saved-error-prefix": "Unable to get the installed WebLogic Kubernetes Operator version because project save failed", + "wko-get-install-version-set-context-error-message": "Unable to get the installed WebLogic Kubernetes Operator version because setting the Kubernetes client cluster context failed: {{error}}.", + "wko-get-install-version-checking-installed-in-progress": "Verifying that the WebLogic Kubernetes Operator is installed", + "wko-get-install-version-install-check-failed-error-message": "Unable to get the installed version of WebLogic Kubernetes Operator because checking to see if it is installed failed: {{error}}.", + "wko-get-install-version-not-installed-error-message": "Unable to get the installed version of WebLogic Kubernetes Operator because WebLogic Kubernetes Operator is not installed.", + "wko-get-install-version-get-in-progress": "Getting the installed version of WebLogic Kubernetes Operator", + "wko-get-install-version-get-failed-title": "Getting Installed WebLogic Kubernetes Operator Version Failed", + "wko-get-install-version-get-failed-error-message": "Unable to get the installed version of WebLogic Kubernetes Operator from Kubernetes namespace {{operatorNamespace}}: {{error}}.", + "wko-installer-aborted-error-title": "WebLogic Kubernetes Operator Installation Aborted", "wko-installer-kubectl-exe-invalid-error-message": "Unable to install WebLogic Kubernetes Operator because the Kubernetes client executable is invalid: {{error}}.", "wko-installer-helm-exe-invalid-error-message": "Unable to install WebLogic Kubernetes Operator because the Helm executable is invalid: {{error}}.", @@ -1270,6 +1288,7 @@ "flow-verify-kubectl-connectivity-name": "Verify Connectivity", "flow-install-operator-name": "Install Operator", "flow-update-operator-name": "Update Operator", + "flow-wko-get-install-version-name": "Get WebLogic Kubernetes Operator Installed Version", "flow-uninstall-operator-name": "Uninstall Operator", "flow-deploy-domain-name": "Deploy Domain", "flow-domain-exists-check-in-progress": "Verifying Domain {{domainUid}} exists", @@ -1443,9 +1462,9 @@ "vz-install-status-checker-status-catch-all-error-message": "Checking the status of the Verrazzano installation with an unexpected error: {{error}}", "vz-get-install-version-aborted-error-title": "Getting Installed Verrazzano Version Aborted", - "vz-get-install-version-kubectl-exe-invalid-error-message": "Unable to install Verrazzano because the Kubernetes client executable is invalid: {{error}}.", - "vz-get-install-version-project-not-saved-error-prefix": "Unable to install Verrazzano because project save failed", - "vz-get-install-version-set-context-error-message": "Unable to install Verrazzano because setting the Kubernetes client cluster context failed: {{error}}.", + "vz-get-install-version-kubectl-exe-invalid-error-message": "Unable to get the installed Verrazzano version because the Kubernetes client executable is invalid: {{error}}.", + "vz-get-install-version-project-not-saved-error-prefix": "Unable to get the installed Verrazzano version because project save failed", + "vz-get-install-version-set-context-error-message": "Unable to get the installed Verrazzano version because setting the Kubernetes client cluster context failed: {{error}}.", "vz-get-install-version-get-in-progress": "Getting the installed version of Verrazzano", "vz-get-install-version-install-check-failed-error-message": "Unable to get the installed version of Verrazzano because checking to see if it is installed failed: {{error}}.", "vz-get-install-version-not-installed-error-message": "unable to get the installed version of Verrazzano because Verrazzano is not installed.", diff --git a/electron/app/main.js b/electron/app/main.js index b0883f2cb..4ec007e62 100644 --- a/electron/app/main.js +++ b/electron/app/main.js @@ -765,8 +765,8 @@ class Main { return kubectlUtils.getApplicationStatus(kubectlExe, application, namespace, options); }); - ipcMain.handle('is-wko-installed', async (event, kubectlExe, operatorName, operatorNamespace, kubectlOptions) => { - return kubectlUtils.isOperatorAlreadyInstalled(kubectlExe, operatorName, operatorNamespace, kubectlOptions); + ipcMain.handle('is-wko-installed', async (event, kubectlExe, operatorNamespace, kubectlOptions) => { + return kubectlUtils.isOperatorAlreadyInstalled(kubectlExe, operatorNamespace, kubectlOptions); }); ipcMain.handle('k8s-create-namespace', async (event, kubectlExe, namespace, kubectlOptions) => { @@ -810,16 +810,31 @@ class Main { return helmUtils.addOrUpdateWkoHelmChart(helmExe, helmOptions); }); - ipcMain.handle('helm-install-wko', async (event, helmExe, helmReleaseName, operatorNamespace, helmChartValues, helmOptions) => { - return helmUtils.installWko(helmExe, helmReleaseName, operatorNamespace, helmChartValues, helmOptions); + ipcMain.handle('helm-install-wko',async (event, helmExe, helmReleaseName, operatorNamespace, helmChartValues, helmOptions, kubectlExe, kubectlOptions) => { + const results = await helmUtils.installWko(helmExe, helmReleaseName, operatorNamespace, helmChartValues, helmOptions); + if (results.isSuccess) { + const versionResults = await kubectlUtils.getOperatorVersion(kubectlExe, operatorNamespace, kubectlOptions); + if (versionResults.isSuccess) { + results.version = versionResults.version; + } + } + return Promise.resolve(results); }); ipcMain.handle('helm-uninstall-wko', async (event, helmExe, helmReleaseName, operatorNamespace, helmOptions) => { return helmUtils.uninstallWko(helmExe, helmReleaseName, operatorNamespace, helmOptions); }); - ipcMain.handle('helm-update-wko', async (event, helmExe, operatorName, operatorNamespace, helmChartValues, helmOptions) => { - return helmUtils.updateWko(helmExe, operatorName, operatorNamespace, helmChartValues, helmOptions); + ipcMain.handle('helm-update-wko', async (event, helmExe, operatorName, + operatorNamespace, helmChartValues, helmOptions, kubectlExe = undefined, kubectlOptions = undefined) => { + const results = await helmUtils.updateWko(helmExe, operatorName, operatorNamespace, helmChartValues, helmOptions); + if (kubectlExe && results.isSuccess) { + const versionResults = await kubectlUtils.getOperatorVersion(kubectlExe, operatorNamespace, kubectlOptions); + if (versionResults.isSuccess) { + results.version = versionResults.version; + } + } + return Promise.resolve(results); }); ipcMain.handle('helm-list-all-namespaces', async (event, helmExe, helmOptions) => { @@ -880,6 +895,10 @@ class Main { return kubectlUtils.getOperatorVersionFromDomainConfigMap(kubectlExe, domainNamespace, options); }); + ipcMain.handle('k8s-get-operator-version', async (event, kubectlExe, operatorNamespace, options) => { + return kubectlUtils.getOperatorVersion(kubectlExe, operatorNamespace, options); + }); + ipcMain.handle('get-tls-keyfile', async (event) => { const title = i18n.t('dialog-tls-keyfile', {}); return chooseFromFileSystem(event.sender.getOwnerBrowserWindow(), { diff --git a/electron/package-lock.json b/electron/package-lock.json index b9fe3ebe2..06c2a436c 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "wktui", - "version": "1.3.0", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wktui", - "version": "1.3.0", + "version": "1.4.0", "license": "UPL-1.0", "dependencies": { "electron-updater": "^5.2.1", diff --git a/electron/package.json b/electron/package.json index e384cb857..c09bac86e 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,7 +1,7 @@ { "name": "wktui", "productName": "WebLogic Kubernetes Toolkit UI", - "version": "1.3.1", + "version": "1.4.0", "description": "WebLogic Kubernetes Toolkit UI", "copyright": "Copyright (c) 2021, 2022, Oracle and/or its affiliates.", "homepage": "https://github.com/oracle/weblogic-toolkit-ui", diff --git a/webui/src/js/models/k8s-domain-definition.js b/webui/src/js/models/k8s-domain-definition.js index a0588696c..71bc37340 100644 --- a/webui/src/js/models/k8s-domain-definition.js +++ b/webui/src/js/models/k8s-domain-definition.js @@ -56,6 +56,8 @@ define(['knockout', 'utils/observable-properties', 'utils/common-utilities', 'ut this.auxImageRegistryPullEmail = props.createProperty(); this.auxImageRegistryPullEmail.addValidator(...validationHelper.getEmailAddressValidators()); this.auxImagePullPolicy = props.createProperty('IfNotPresent'); + this.auxImageSourceModelHome = props.createProperty('/auxiliary/models'); + this.auxImageSourceWDTInstallHome = props.createProperty('/auxiliary/weblogic-deploy'); this.clusterKeys = [ 'uid', 'name', 'maxServers', 'replicas', 'minHeap', 'maxHeap', 'cpuRequest', 'cpuLimit', 'memoryRequest', diff --git a/webui/src/js/models/wko-definition.js b/webui/src/js/models/wko-definition.js index 8e216af3f..7f2bae125 100644 --- a/webui/src/js/models/wko-definition.js +++ b/webui/src/js/models/wko-definition.js @@ -70,6 +70,8 @@ define(['utils/observable-properties', 'utils/validation-helper'], this.helmTimeoutMinutes = props.createProperty(5); + this.installedVersion = props.createProperty(); + // internal fields that should not be read or written to the project file. this.internal = { operatorImagePullRegistryAddress: props.createProperty() diff --git a/webui/src/js/utils/k8s-domain-deployer.js b/webui/src/js/utils/k8s-domain-deployer.js index efb1c50c6..57c2e0547 100644 --- a/webui/src/js/utils/k8s-domain-deployer.js +++ b/webui/src/js/utils/k8s-domain-deployer.js @@ -124,6 +124,10 @@ function (K8sDomainActionsBase, project, wktConsole, i18n, projectIo, dialogHelp } // Run helm upgrade so that operator picks up the new namespace. + // + // Skip passing kubectlExe and kubectlOptions args since the installed version + // of operator was already set. + // const helmOptions = helmHelper.getHelmOptions(); const upgradeResults = await window.api.ipc.invoke('helm-update-wko', helmExe, operatorName, operatorNamespace, helmChartValues, helmOptions); @@ -423,7 +427,7 @@ function (K8sDomainActionsBase, project, wktConsole, i18n, projectIo, dialogHelp async checkOperatorIsInstalled(kubectlExe, kubectlOptions, operatorName, operatorNamespace, errTitle) { try { const isInstalledResults = - await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorName, operatorNamespace, kubectlOptions); + await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorNamespace, kubectlOptions); if (!isInstalledResults.isInstalled) { let errMessage; if (isInstalledResults.reason) { diff --git a/webui/src/js/utils/k8s-domain-resource-generator.js b/webui/src/js/utils/k8s-domain-resource-generator.js index eb60b519f..b2e7c6ffa 100644 --- a/webui/src/js/utils/k8s-domain-resource-generator.js +++ b/webui/src/js/utils/k8s-domain-resource-generator.js @@ -5,398 +5,36 @@ */ 'use strict'; -define(['models/wkt-project', 'utils/k8s-domain-configmap-generator', 'js-yaml', 'utils/i18n', 'utils/wkt-logger'], - function(project, K8sDomainConfigMapGenerator, jsYaml, i18n) { - class K8sDomainResourceGenerator { - constructor() { - this.project = project; - this.k8sConfigMapGenerator = new K8sDomainConfigMapGenerator(); - } +define(['models/wkt-project', 'utils/k8s-domain-v8-resource-generator', 'utils/k8s-domain-v9-resource-generator', + 'utils/k8s-domain-configmap-generator', 'utils/wkt-logger'], +function(project, K8sDomainV8ResourceGenerator, K8sDomainV9ResourceGenerator, K8sDomainConfigMapGenerator, wktLogger) { - generate(generateYaml = true) { - const domainResource = { - apiVersion: 'weblogic.oracle/v8', - kind: 'Domain', - metadata: { - name: this.project.k8sDomain.uid.value, - namespace: this.project.k8sDomain.kubernetesNamespace.value, - labels: { - 'weblogic.domainUID': this.project.k8sDomain.uid.value - } - }, - spec: { - domainUID: this.project.k8sDomain.uid.value, - domainHomeSourceType: getOperatorNameForTargetDomainLocation(this.project.settings.targetDomainLocation.value), - image: this.project.image.imageTag.value, - imagePullPolicy: this.project.k8sDomain.imagePullPolicy.value, - introspectVersion: Date.now().toString(), - webLogicCredentialsSecret: { - name: this.project.k8sDomain.credentialsSecretName.value - } - } - }; + const V9_SWITCHOVER_VERSION = '4.0.0'; + const DEFAULT_OPERATOR_VERSION = V9_SWITCHOVER_VERSION; - if (this.project.k8sDomain.domainHome.value) { - domainResource.spec.domainHome = this.project.k8sDomain.domainHome.value; - } - - if (usingAuxImage()) { - domainResource.spec.auxiliaryImageVolumes = [ - { - name: `${domainResource.spec.domainUID}-aux-image-volume`, - mountPath: '/auxiliary' - } - ]; - } - - if (this.project.settings.targetDomainLocation.value === 'pv') { - if (this.project.k8sDomain.domainPersistentVolumeLogHomeEnabled.value) { - domainResource.spec.logHomeEnabled = true; - domainResource.spec.logHome = this.project.k8sDomain.domainPersistentVolumeLogHome.value; - } - } - - const serverPod = this._getDomainServerPod(); - if (serverPod) { - domainResource.spec.serverPod = serverPod; - } - - const wdtRelatedPaths = this._getWdtRelatedPaths(domainResource); - if (usingAuxImage()) { - const volumeName = domainResource.spec.auxiliaryImageVolumes[0].name; - const command = getAuxImageCopyCommand(wdtRelatedPaths); - const auxiliaryImage = { - image: this.project.image.auxImageTag.value, - command: command, - volume: volumeName - }; - if (this.project.k8sDomain.auxImagePullPolicy.hasValue()) { - auxiliaryImage.imagePullPolicy = this.project.k8sDomain.auxImagePullPolicy.value; - } - serverPod.auxiliaryImages = [ auxiliaryImage ]; - } - - if (this.project.k8sDomain.clusters.value.length === 0) { - domainResource.spec.replicas = this.project.k8sDomain.replicas.value; - } else { - const specClusters = []; - for (const cluster of this.project.k8sDomain.clusters.value) { - const specCluster = { clusterName: cluster.name }; - if (cluster.replicas !== undefined && cluster.replicas !== null) { - specCluster.replicas = cluster.replicas; - } - const clusterServerPod = getServerPodForCluster(cluster); - if (clusterServerPod) { - specCluster.serverPod = clusterServerPod; - } - specClusters.push(specCluster); - } - if (specClusters.length > 0) { - domainResource.spec.clusters = specClusters; - } - } - - const imagePullSecrets = []; - if (this.project.k8sDomain.imageRegistryPullRequireAuthentication.value && this.project.k8sDomain.imageRegistryPullSecretName.value) { - imagePullSecrets.push({ name: this.project.k8sDomain.imageRegistryPullSecretName.value }); - } - if (usingAuxImage()) { - if (this.project.k8sDomain.auxImageRegistryPullRequireAuthentication.value && this.project.k8sDomain.auxImageRegistryPullSecretName.value) { - const auxImagePullSecretName = this.project.k8sDomain.auxImageRegistryPullSecretName.value; - - let secretNotAlreadyAdded = true; - for (const imagePullSecret of imagePullSecrets) { - if (auxImagePullSecretName === imagePullSecret.name) { - secretNotAlreadyAdded = false; - break; - } - } - - if (secretNotAlreadyAdded) { - imagePullSecrets.push({ name: auxImagePullSecretName }); - } - } - } - if (imagePullSecrets.length > 0) { - domainResource.spec.imagePullSecrets = imagePullSecrets; - } - - if (this.project.settings.targetDomainLocation.value === 'mii') { - domainResource.spec.configuration = { }; - domainResource.spec.configuration.model = { - domainType: this.project.k8sDomain.domainType.value, - runtimeEncryptionSecret: this.project.k8sDomain.runtimeSecretName.value - }; - - if (usingAuxImage()) { - const mountPoint = domainResource.spec.auxiliaryImageVolumes[0].mountPath; - // Always set these since WKO aux image support makes these required - domainResource.spec.configuration.model.wdtInstallHome = - window.api.path.join(mountPoint, wdtRelatedPaths.targetWdtHomeDirName); - domainResource.spec.configuration.model.modelHome = - window.api.path.join(mountPoint, wdtRelatedPaths.targetModelHomeDirName); - } else { - // Only set these if they are specified; otherwise, rely on the default values - if (wdtRelatedPaths.wdtHome) { - domainResource.spec.configuration.model.wdtHome = wdtRelatedPaths.wdtHome; - } - if (wdtRelatedPaths.modelHome) { - domainResource.spec.configuration.model.modelHome = wdtRelatedPaths.modelHome; - } - } - - if (this.k8sConfigMapGenerator.shouldCreateConfigMap()) { - domainResource.spec.configuration.model.configMap = this.project.k8sDomain.modelConfigMapName.value; - } - - if (this.project.k8sDomain.secrets.value.length > 0) { - domainResource.spec.configuration.secrets = []; - for (const secret of this.project.k8sDomain.secrets.value) { - domainResource.spec.configuration.secrets.push(secret.name); - } - } - - // The operator default value is 120 but the UI sets it to 900 so - // set the value unless it is empty or 120. - // - const deadline = this.project.k8sDomain.introspectorJobActiveDeadlineSeconds.value; - if (deadline && deadline !== 120) { - domainResource.spec.configuration.introspectorJobActiveDeadlineSeconds = - this.project.k8sDomain.introspectorJobActiveDeadlineSeconds.value; - } - } - return generateYaml ? jsYaml.dump(domainResource).split('\n') : domainResource; - } - - _getDomainServerPod() { - let serverPod = this._getServerPod(); - - if (this.project.k8sDomain.domainNodeSelector.value.length > 0) { - if (!serverPod) { - serverPod = {}; - } - serverPod.nodeSelector = {}; - - this.project.k8sDomain.domainNodeSelector.value.forEach(selector => { - serverPod.nodeSelector[selector.name] = selector.value; - }); - } - return serverPod; - } - - _getServerPod() { - const serverPod = _getServerPod(getJavaOptions(this.project.k8sDomain), getUserMemArgs(this.project.k8sDomain), - getKubernetesResources(this.project.k8sDomain)); - - if (this.project.settings.targetDomainLocation.value === 'pv') { - const volumeName = this.project.k8sDomain.domainPersistentVolumeName.value || 'volume-name-not-set'; - serverPod.volumes = [ - { - name: volumeName, - persistentVolumeClaim: { - claimName: this.project.k8sDomain.domainPersistentVolumeClaimName.value || 'volume-claim-name-not-set' - } - } - ]; - serverPod.volumeMounts = [ - { - name: volumeName, - mountPath: this.project.k8sDomain.domainPersistentVolumeMountPath.value - } - ]; - } - return serverPod; - } - - _getWdtRelatedPaths() { - let result; - - if (this.project.settings.targetDomainLocation.value === 'mii') { - - result = { }; - if (usingAuxImage()) { - const wdtHome = this.project.image.wdtHomePath.value; - const modelHome = this.project.image.modelHomePath.value; - result.targetWdtHomeDirName = 'weblogic-deploy'; - result.sourceWdtHome = window.api.path.join(wdtHome, result.targetWdtHomeDirName); - result.sourceModelHome = modelHome; - result.targetModelHomeDirName = window.api.path.basename(modelHome); - } else { - // Only set these if they are not the default - if (this.project.image.wdtHomePath.hasValue()) { - result.wdtHome = this.project.image.wdtHomePath.value; - } - if (this.project.image.modelHomePath.hasValue()) { - result.modelHome = this.project.image.modelHomePath.value; - } - } - } - return result; - } - } - - function usingAuxImage() { - return project.settings.targetDomainLocation.value === 'mii' && project.image.useAuxImage.value; - } - - function getAuxImageCopyCommand(wdtRelatedPaths) { - const auxImageInternalTarget = '$AUXILIARY_IMAGE_TARGET_PATH'; - - return `cp -R ${wdtRelatedPaths.sourceWdtHome} ${auxImageInternalTarget}; cp -R ${wdtRelatedPaths.sourceModelHome} ${auxImageInternalTarget}`; - } - - function getOperatorNameForTargetDomainLocation(targetDomainLocation) { - switch (targetDomainLocation) { - case 'mii': - return 'FromModel'; - - case 'dii': - return 'Image'; - - case 'pv': - return 'PersistentVolume'; - - default: - throw new Error(i18n.t('k8s-domain-script-generator-invalid-target-domain-location', - { targetDomainLocation: targetDomainLocation})); - } + class K8sDomainResourceGenerator { + constructor(operatorVersion = DEFAULT_OPERATOR_VERSION) { + this.project = project; + this.operatorResourceGenerator = _getOperatorResourceGenerator(operatorVersion); + this.k8sConfigMapGenerator = new K8sDomainConfigMapGenerator(); } - function getServerPodForCluster(cluster) { - const serverPod = _getServerPod(getJavaOptionsForCluster(cluster), getUserMemArgsForCluster(cluster), getKubernetesResourcesForCluster(cluster)) || {}; - - const affinity = _getAffinityForServerPod(100); - if (affinity) { - serverPod.affinity = affinity; - } - - return Object.keys(serverPod).length > 0 ? serverPod : null; - } - - function _getServerPod(javaOptions, userMemArgs, resources) { - const serverPod = {}; - const env = []; - addIfNotNull(env, 'JAVA_OPTIONS', javaOptions); - addIfNotNull(env, 'USER_MEM_ARGS', userMemArgs); - if (env.length) { - serverPod.env = env; - } - - if (resources) { - serverPod.resources = resources; - } - - return Object.keys(serverPod).length > 0 ? serverPod : null; - } - - function addIfNotNull(env, varName, varValue) { - if (varValue) { - env.push({ name: varName, value: varValue }); - } - } - - function getJavaOptions(k8sDomain) { - return _getJavaOptions(k8sDomain.disableDebugStdout.value, k8sDomain.useUrandom.value, - k8sDomain.disableFan.value, k8sDomain.additionalArguments.value); - } - - function getJavaOptionsForCluster(cluster) { - return _getJavaOptions(cluster['disableDebugStdout'], cluster['useUrandom'], cluster['disableFan'], - cluster['additionalArguments']); - } - - function _getJavaOptions(disableDebugStdout, useUrandom, disableFan, additionalArguments) { - const result = []; - if (disableDebugStdout) { - result.push('-Dweblogic.StdoutDebugEnabled=false'); - } - if (useUrandom) { - result.push('-Djava.security.egd=file:/dev/./urandom'); - } - if (disableFan) { - result.push('-Doracle.jdbc.fanEnabled=false'); - } - if (additionalArguments && additionalArguments.length > 0) { - result.push(...additionalArguments); - } - return (result.length) ? result.join(' ') : null; - } - - function getUserMemArgs(k8sDomain) { - return _getUserMemArgs(k8sDomain.minimumHeapSize.value, k8sDomain.maximumHeapSize.value); - } - - function getUserMemArgsForCluster(cluster) { - return _getUserMemArgs(cluster['minHeap'], cluster['maxHeap']); - } - - function _getUserMemArgs(minHeap, maxHeap) { - const result = []; - if (minHeap) { - result.push(`-Xms${minHeap}`); - } - if (maxHeap) { - result.push(`-Xmx${maxHeap}`); - } - return (result.length) ? result.join(' ') : null; - } - - function getKubernetesResources(k8sDomain) { - return _getKubernetesResources(k8sDomain.cpuRequest.value, k8sDomain.cpuLimit.value, - k8sDomain.memoryRequest.value, k8sDomain.memoryLimit.value); - } - - function getKubernetesResourcesForCluster(cluster) { - return _getKubernetesResources(cluster.cpuRequest, cluster.cpuLimit, cluster.memoryRequest, cluster.memoryLimit); - } - - function _getKubernetesResources(cpuRequest, cpuLimit, memoryRequest, memoryLimit) { - let foundValue = false; - const resources = { - limits: {}, - requests: {} - }; - - if (cpuRequest) { - resources.requests.cpu = cpuRequest; - foundValue = true; - } - if (cpuLimit) { - resources.limits.cpu = cpuLimit; - foundValue = true; - } - if (memoryRequest) { - resources.requests.memory = memoryRequest; - foundValue = true; - } - if (memoryLimit) { - resources.limits.memory = memoryLimit; - foundValue = true; - } - - return foundValue ? resources : null; + generate(generateYaml = true) { + return this.operatorResourceGenerator.generate(generateYaml); } + } - function _getAffinityForServerPod(weight) { - return { - podAntiAffinity: { - preferredDuringSchedulingIgnoredDuringExecution: [{ - weight: weight, - podAffinityTerm: { - topologyKey: 'kubernetes.io/hostname', - labelSelector: { - matchExpressions: [{ - key: 'weblogic.clusterName', - operator: 'In', - values: ['$(CLUSTER_NAME)'], - }], - }, - }, - }], - }, - }; + function _getOperatorResourceGenerator(operatorVersion) { + let generator; + if (window.api.utils.compareVersions(operatorVersion, V9_SWITCHOVER_VERSION) < 0) { + wktLogger.debug('Using operator version %s to create domain resource generator V8', operatorVersion); + generator = new K8sDomainV8ResourceGenerator(); + } else { + wktLogger.debug('Using operator version %s to create domain resource generator V9', operatorVersion); + generator = new K8sDomainV9ResourceGenerator(); } - - return K8sDomainResourceGenerator; + return generator; } -); + + return K8sDomainResourceGenerator; +}); diff --git a/webui/src/js/utils/k8s-domain-undeployer.js b/webui/src/js/utils/k8s-domain-undeployer.js index e9f852e04..55279870c 100644 --- a/webui/src/js/utils/k8s-domain-undeployer.js +++ b/webui/src/js/utils/k8s-domain-undeployer.js @@ -152,6 +152,9 @@ function (K8sDomainActionsBase, project, wktConsole, i18n, projectIo, dialogHelp domainNamespaces: `{${list.join(',')}}` }; + // Skip passing kubectlExe and kubectlOptions args we do not need + // the installed version of operator. + // const upgradeResults = await window.api.ipc.invoke('helm-update-wko', helmExe, operatorName, operatorNamespace, helmChartValues, helmHelper.getHelmOptions()); if (!upgradeResults.isSuccess) { @@ -219,7 +222,7 @@ function (K8sDomainActionsBase, project, wktConsole, i18n, projectIo, dialogHelp const results = { isInstalled: true }; try { const isInstalledResults = - await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorName, operatorNamespace, kubectlOptions); + await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorNamespace, kubectlOptions); if (!isInstalledResults.isInstalled) { if (isInstalledResults.reason) { // error from backend diff --git a/webui/src/js/utils/k8s-domain-v8-resource-generator.js b/webui/src/js/utils/k8s-domain-v8-resource-generator.js new file mode 100644 index 000000000..d5908fe32 --- /dev/null +++ b/webui/src/js/utils/k8s-domain-v8-resource-generator.js @@ -0,0 +1,402 @@ +/** + * @license + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. + * Licensed under The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ +'use strict'; + +define(['models/wkt-project', 'utils/k8s-domain-configmap-generator', 'js-yaml', + 'utils/i18n', 'utils/wkt-logger'], +function(project, K8sDomainConfigMapGenerator, jsYaml, i18n) { + class K8sDomainV8ResourceGenerator { + constructor() { + this.project = project; + this.k8sConfigMapGenerator = new K8sDomainConfigMapGenerator(); + } + + generate(generateYaml = true) { + const domainResource = { + apiVersion: 'weblogic.oracle/v8', + kind: 'Domain', + metadata: { + name: this.project.k8sDomain.uid.value, + namespace: this.project.k8sDomain.kubernetesNamespace.value, + labels: { + 'weblogic.domainUID': this.project.k8sDomain.uid.value + } + }, + spec: { + domainUID: this.project.k8sDomain.uid.value, + domainHomeSourceType: getOperatorNameForTargetDomainLocation(this.project.settings.targetDomainLocation.value), + image: this.project.image.imageTag.value, + imagePullPolicy: this.project.k8sDomain.imagePullPolicy.value, + introspectVersion: Date.now().toString(), + webLogicCredentialsSecret: { + name: this.project.k8sDomain.credentialsSecretName.value + } + } + }; + + if (this.project.k8sDomain.domainHome.value) { + domainResource.spec.domainHome = this.project.k8sDomain.domainHome.value; + } + + if (usingAuxImage()) { + domainResource.spec.auxiliaryImageVolumes = [ + { + name: `${domainResource.spec.domainUID}-aux-image-volume`, + mountPath: '/auxiliary' + } + ]; + } + + if (this.project.settings.targetDomainLocation.value === 'pv') { + if (this.project.k8sDomain.domainPersistentVolumeLogHomeEnabled.value) { + domainResource.spec.logHomeEnabled = true; + domainResource.spec.logHome = this.project.k8sDomain.domainPersistentVolumeLogHome.value; + } + } + + const serverPod = this._getDomainServerPod(); + if (serverPod) { + domainResource.spec.serverPod = serverPod; + } + + const wdtRelatedPaths = this._getWdtRelatedPaths(domainResource); + if (usingAuxImage()) { + const volumeName = domainResource.spec.auxiliaryImageVolumes[0].name; + const command = getAuxImageCopyCommand(wdtRelatedPaths); + const auxiliaryImage = { + image: this.project.image.auxImageTag.value, + command: command, + volume: volumeName + }; + if (this.project.k8sDomain.auxImagePullPolicy.hasValue()) { + auxiliaryImage.imagePullPolicy = this.project.k8sDomain.auxImagePullPolicy.value; + } + serverPod.auxiliaryImages = [ auxiliaryImage ]; + } + + if (this.project.k8sDomain.clusters.value.length === 0) { + domainResource.spec.replicas = this.project.k8sDomain.replicas.value; + } else { + const specClusters = []; + for (const cluster of this.project.k8sDomain.clusters.value) { + const specCluster = { clusterName: cluster.name }; + if (cluster.replicas !== undefined && cluster.replicas !== null) { + specCluster.replicas = cluster.replicas; + } + const clusterServerPod = getServerPodForCluster(cluster); + if (clusterServerPod) { + specCluster.serverPod = clusterServerPod; + } + specClusters.push(specCluster); + } + if (specClusters.length > 0) { + domainResource.spec.clusters = specClusters; + } + } + + const imagePullSecrets = []; + if (this.project.k8sDomain.imageRegistryPullRequireAuthentication.value && this.project.k8sDomain.imageRegistryPullSecretName.value) { + imagePullSecrets.push({ name: this.project.k8sDomain.imageRegistryPullSecretName.value }); + } + if (usingAuxImage()) { + if (this.project.k8sDomain.auxImageRegistryPullRequireAuthentication.value && this.project.k8sDomain.auxImageRegistryPullSecretName.value) { + const auxImagePullSecretName = this.project.k8sDomain.auxImageRegistryPullSecretName.value; + + let secretNotAlreadyAdded = true; + for (const imagePullSecret of imagePullSecrets) { + if (auxImagePullSecretName === imagePullSecret.name) { + secretNotAlreadyAdded = false; + break; + } + } + + if (secretNotAlreadyAdded) { + imagePullSecrets.push({ name: auxImagePullSecretName }); + } + } + } + if (imagePullSecrets.length > 0) { + domainResource.spec.imagePullSecrets = imagePullSecrets; + } + + if (this.project.settings.targetDomainLocation.value === 'mii') { + domainResource.spec.configuration = { }; + domainResource.spec.configuration.model = { + domainType: this.project.k8sDomain.domainType.value, + runtimeEncryptionSecret: this.project.k8sDomain.runtimeSecretName.value + }; + + if (usingAuxImage()) { + const mountPoint = domainResource.spec.auxiliaryImageVolumes[0].mountPath; + // Always set these since WKO aux image support makes these required + domainResource.spec.configuration.model.wdtInstallHome = + window.api.path.join(mountPoint, wdtRelatedPaths.targetWdtHomeDirName); + domainResource.spec.configuration.model.modelHome = + window.api.path.join(mountPoint, wdtRelatedPaths.targetModelHomeDirName); + } else { + // Only set these if they are specified; otherwise, rely on the default values + if (wdtRelatedPaths.wdtHome) { + domainResource.spec.configuration.model.wdtHome = wdtRelatedPaths.wdtHome; + } + if (wdtRelatedPaths.modelHome) { + domainResource.spec.configuration.model.modelHome = wdtRelatedPaths.modelHome; + } + } + + if (this.k8sConfigMapGenerator.shouldCreateConfigMap()) { + domainResource.spec.configuration.model.configMap = this.project.k8sDomain.modelConfigMapName.value; + } + + if (this.project.k8sDomain.secrets.value.length > 0) { + domainResource.spec.configuration.secrets = []; + for (const secret of this.project.k8sDomain.secrets.value) { + domainResource.spec.configuration.secrets.push(secret.name); + } + } + + // The operator default value is 120 but the UI sets it to 900 so + // set the value unless it is empty or 120. + // + const deadline = this.project.k8sDomain.introspectorJobActiveDeadlineSeconds.value; + if (deadline && deadline !== 120) { + domainResource.spec.configuration.introspectorJobActiveDeadlineSeconds = + this.project.k8sDomain.introspectorJobActiveDeadlineSeconds.value; + } + } + return generateYaml ? jsYaml.dump(domainResource).split('\n') : { domainResource }; + } + + _getDomainServerPod() { + let serverPod = this._getServerPod(); + + if (this.project.k8sDomain.domainNodeSelector.value.length > 0) { + if (!serverPod) { + serverPod = {}; + } + serverPod.nodeSelector = {}; + + this.project.k8sDomain.domainNodeSelector.value.forEach(selector => { + serverPod.nodeSelector[selector.name] = selector.value; + }); + } + return serverPod; + } + + _getServerPod() { + const serverPod = _getServerPod(getJavaOptions(this.project.k8sDomain), getUserMemArgs(this.project.k8sDomain), + getKubernetesResources(this.project.k8sDomain)); + + if (this.project.settings.targetDomainLocation.value === 'pv') { + const volumeName = this.project.k8sDomain.domainPersistentVolumeName.value || 'volume-name-not-set'; + serverPod.volumes = [ + { + name: volumeName, + persistentVolumeClaim: { + claimName: this.project.k8sDomain.domainPersistentVolumeClaimName.value || 'volume-claim-name-not-set' + } + } + ]; + serverPod.volumeMounts = [ + { + name: volumeName, + mountPath: this.project.k8sDomain.domainPersistentVolumeMountPath.value + } + ]; + } + return serverPod; + } + + _getWdtRelatedPaths() { + let result; + + if (this.project.settings.targetDomainLocation.value === 'mii') { + + result = { }; + if (usingAuxImage()) { + const wdtHome = this.project.image.wdtHomePath.value; + const modelHome = this.project.image.modelHomePath.value; + result.targetWdtHomeDirName = 'weblogic-deploy'; + result.sourceWdtHome = window.api.path.join(wdtHome, result.targetWdtHomeDirName); + result.sourceModelHome = modelHome; + result.targetModelHomeDirName = window.api.path.basename(modelHome); + } else { + // Only set these if they are not the default + if (this.project.image.wdtHomePath.hasValue()) { + result.wdtHome = this.project.image.wdtHomePath.value; + } + if (this.project.image.modelHomePath.hasValue()) { + result.modelHome = this.project.image.modelHomePath.value; + } + } + } + return result; + } + } + + function usingAuxImage() { + return project.settings.targetDomainLocation.value === 'mii' && project.image.useAuxImage.value; + } + + function getAuxImageCopyCommand(wdtRelatedPaths) { + const auxImageInternalTarget = '$AUXILIARY_IMAGE_TARGET_PATH'; + + return `cp -R ${wdtRelatedPaths.sourceWdtHome} ${auxImageInternalTarget}; cp -R ${wdtRelatedPaths.sourceModelHome} ${auxImageInternalTarget}`; + } + + function getOperatorNameForTargetDomainLocation(targetDomainLocation) { + switch (targetDomainLocation) { + case 'mii': + return 'FromModel'; + + case 'dii': + return 'Image'; + + case 'pv': + return 'PersistentVolume'; + + default: + throw new Error(i18n.t('k8s-domain-script-generator-invalid-target-domain-location', + { targetDomainLocation: targetDomainLocation})); + } + } + + function getServerPodForCluster(cluster) { + const serverPod = _getServerPod(getJavaOptionsForCluster(cluster), getUserMemArgsForCluster(cluster), getKubernetesResourcesForCluster(cluster)) || {}; + + const affinity = _getAffinityForServerPod(100); + if (affinity) { + serverPod.affinity = affinity; + } + + return Object.keys(serverPod).length > 0 ? serverPod : null; + } + + function _getServerPod(javaOptions, userMemArgs, resources) { + const serverPod = {}; + const env = []; + addIfNotNull(env, 'JAVA_OPTIONS', javaOptions); + addIfNotNull(env, 'USER_MEM_ARGS', userMemArgs); + if (env.length) { + serverPod.env = env; + } + + if (resources) { + serverPod.resources = resources; + } + + return Object.keys(serverPod).length > 0 ? serverPod : null; + } + + function addIfNotNull(env, varName, varValue) { + if (varValue) { + env.push({ name: varName, value: varValue }); + } + } + + function getJavaOptions(k8sDomain) { + return _getJavaOptions(k8sDomain.disableDebugStdout.value, k8sDomain.useUrandom.value, + k8sDomain.disableFan.value, k8sDomain.additionalArguments.value); + } + + function getJavaOptionsForCluster(cluster) { + return _getJavaOptions(cluster['disableDebugStdout'], cluster['useUrandom'], cluster['disableFan'], + cluster['additionalArguments']); + } + + function _getJavaOptions(disableDebugStdout, useUrandom, disableFan, additionalArguments) { + const result = []; + if (disableDebugStdout) { + result.push('-Dweblogic.StdoutDebugEnabled=false'); + } + if (useUrandom) { + result.push('-Djava.security.egd=file:/dev/./urandom'); + } + if (disableFan) { + result.push('-Doracle.jdbc.fanEnabled=false'); + } + if (additionalArguments && additionalArguments.length > 0) { + result.push(...additionalArguments); + } + return (result.length) ? result.join(' ') : null; + } + + function getUserMemArgs(k8sDomain) { + return _getUserMemArgs(k8sDomain.minimumHeapSize.value, k8sDomain.maximumHeapSize.value); + } + + function getUserMemArgsForCluster(cluster) { + return _getUserMemArgs(cluster['minHeap'], cluster['maxHeap']); + } + + function _getUserMemArgs(minHeap, maxHeap) { + const result = []; + if (minHeap) { + result.push(`-Xms${minHeap}`); + } + if (maxHeap) { + result.push(`-Xmx${maxHeap}`); + } + return (result.length) ? result.join(' ') : null; + } + + function getKubernetesResources(k8sDomain) { + return _getKubernetesResources(k8sDomain.cpuRequest.value, k8sDomain.cpuLimit.value, + k8sDomain.memoryRequest.value, k8sDomain.memoryLimit.value); + } + + function getKubernetesResourcesForCluster(cluster) { + return _getKubernetesResources(cluster.cpuRequest, cluster.cpuLimit, cluster.memoryRequest, cluster.memoryLimit); + } + + function _getKubernetesResources(cpuRequest, cpuLimit, memoryRequest, memoryLimit) { + let foundValue = false; + const resources = { + limits: {}, + requests: {} + }; + + if (cpuRequest) { + resources.requests.cpu = cpuRequest; + foundValue = true; + } + if (cpuLimit) { + resources.limits.cpu = cpuLimit; + foundValue = true; + } + if (memoryRequest) { + resources.requests.memory = memoryRequest; + foundValue = true; + } + if (memoryLimit) { + resources.limits.memory = memoryLimit; + foundValue = true; + } + + return foundValue ? resources : null; + } + + function _getAffinityForServerPod(weight) { + return { + podAntiAffinity: { + preferredDuringSchedulingIgnoredDuringExecution: [{ + weight: weight, + podAffinityTerm: { + topologyKey: 'kubernetes.io/hostname', + labelSelector: { + matchExpressions: [{ + key: 'weblogic.clusterName', + operator: 'In', + values: ['$(CLUSTER_NAME)'], + }], + }, + }, + }], + }, + }; + } + + return K8sDomainV8ResourceGenerator; +}); diff --git a/webui/src/js/utils/k8s-domain-v9-resource-generator.js b/webui/src/js/utils/k8s-domain-v9-resource-generator.js new file mode 100644 index 000000000..e8566fe58 --- /dev/null +++ b/webui/src/js/utils/k8s-domain-v9-resource-generator.js @@ -0,0 +1,427 @@ +/** + * @license + * Copyright (c) 2022, Oracle and/or its affiliates. + * Licensed under The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ +'use strict'; + +define(['models/wkt-project', 'utils/k8s-domain-configmap-generator', 'js-yaml', 'utils/i18n', 'utils/wkt-logger'], + function(project, K8sDomainConfigMapGenerator, jsYaml, i18n) { + class K8sDomainV9ResourceGenerator { + constructor() { + this.project = project; + this.k8sConfigMapGenerator = new K8sDomainConfigMapGenerator(); + } + + generate(generateYaml = true) { + const domainResource = { + apiVersion: 'weblogic.oracle/v9', + kind: 'Domain', + metadata: { + name: this.project.k8sDomain.uid.value, + namespace: this.project.k8sDomain.kubernetesNamespace.value, + labels: { + 'weblogic.domainUID': this.project.k8sDomain.uid.value + } + }, + spec: { + domainUID: this.project.k8sDomain.uid.value, + domainHomeSourceType: getOperatorNameForTargetDomainLocation(this.project.settings.targetDomainLocation.value), + image: this.project.image.imageTag.value, + imagePullPolicy: this.project.k8sDomain.imagePullPolicy.value, + introspectVersion: Date.now().toString(), + webLogicCredentialsSecret: { + name: this.project.k8sDomain.credentialsSecretName.value + } + } + }; + + if (this.project.k8sDomain.domainHome.value) { + domainResource.spec.domainHome = this.project.k8sDomain.domainHome.value; + } + + if (this.project.settings.targetDomainLocation.value === 'pv') { + if (this.project.k8sDomain.domainPersistentVolumeLogHomeEnabled.value) { + domainResource.spec.logHomeEnabled = true; + domainResource.spec.logHome = this.project.k8sDomain.domainPersistentVolumeLogHome.value; + } + } + + const serverPod = this._getDomainServerPod(); + if (serverPod) { + domainResource.spec.serverPod = serverPod; + } + + if (usingAuxImage()) { + const auxiliaryImage = { + image: this.project.image.auxImageTag.value, + }; + if (this.project.k8sDomain.auxImagePullPolicy.hasValue()) { + auxiliaryImage.imagePullPolicy = this.project.k8sDomain.auxImagePullPolicy.value; + } + if (this.project.k8sDomain.auxImageSourceWDTInstallHome.hasValue()) { + auxiliaryImage.sourceWDTInstallHome = this.project.k8sDomain.auxImageSourceWDTInstallHome.value; + } + if (this.project.k8sDomain.auxImageSourceModelHome.hasValue()) { + auxiliaryImage.sourceModelHome = this.project.k8sDomain.auxImageSourceModelHome.value; + } + + if (!domainResource.spec.configuration) { + domainResource.spec.configuration = { + model: {} + }; + } else if (!domainResource.spec.configuration.model) { + domainResource.spec.configuration.model = {}; + } + domainResource.spec.configuration.model.auxiliaryImages = [ auxiliaryImage ]; + } + + if (this.project.k8sDomain.clusters.value.length === 0) { + domainResource.spec.replicas = this.project.k8sDomain.replicas.value; + } else { + domainResource.spec.clusters = this.project.k8sDomain.clusters.value.map(cluster => { + return { name: _getClusterName(this.project.k8sDomain.uid.value, cluster.name) }; + }); + } + + const imagePullSecrets = []; + if (this.project.k8sDomain.imageRegistryPullRequireAuthentication.value && this.project.k8sDomain.imageRegistryPullSecretName.value) { + imagePullSecrets.push({ name: this.project.k8sDomain.imageRegistryPullSecretName.value }); + } + if (usingAuxImage()) { + if (this.project.k8sDomain.auxImageRegistryPullRequireAuthentication.value && this.project.k8sDomain.auxImageRegistryPullSecretName.value) { + const auxImagePullSecretName = this.project.k8sDomain.auxImageRegistryPullSecretName.value; + + let secretNotAlreadyAdded = true; + for (const imagePullSecret of imagePullSecrets) { + if (auxImagePullSecretName === imagePullSecret.name) { + secretNotAlreadyAdded = false; + break; + } + } + + if (secretNotAlreadyAdded) { + imagePullSecrets.push({ name: auxImagePullSecretName }); + } + } + } + if (imagePullSecrets.length > 0) { + domainResource.spec.imagePullSecrets = imagePullSecrets; + } + + if (this.project.settings.targetDomainLocation.value === 'mii') { + domainResource.spec.configuration.model.domainType = this.project.k8sDomain.domainType.value; + domainResource.spec.configuration.model.runtimeEncryptionSecret = this.project.k8sDomain.runtimeSecretName.value; + + if (!usingAuxImage()) { + const wdtRelatedPaths = this._getWdtRelatedPaths(domainResource); + // Only set these if they are specified; otherwise, rely on the default values + if (wdtRelatedPaths.wdtHome) { + domainResource.spec.configuration.model.wdtHome = wdtRelatedPaths.wdtHome; + } + if (wdtRelatedPaths.modelHome) { + domainResource.spec.configuration.model.modelHome = wdtRelatedPaths.modelHome; + } + } + + if (this.k8sConfigMapGenerator.shouldCreateConfigMap()) { + domainResource.spec.configuration.model.configMap = this.project.k8sDomain.modelConfigMapName.value; + } + + if (this.project.k8sDomain.secrets.value.length > 0) { + domainResource.spec.configuration.secrets = []; + for (const secret of this.project.k8sDomain.secrets.value) { + domainResource.spec.configuration.secrets.push(secret.name); + } + } + + // The operator default value is 120 but the UI sets it to 900 so + // set the value unless it is empty or 120. + // + const deadline = this.project.k8sDomain.introspectorJobActiveDeadlineSeconds.value; + if (deadline && deadline !== 120) { + domainResource.spec.configuration.introspectorJobActiveDeadlineSeconds = + this.project.k8sDomain.introspectorJobActiveDeadlineSeconds.value; + } + } + + // At this point, the domainResource is complete. Now, we need to + // generate the Cluster resource specs, as needed. + // + const clusterResources = []; + if (this.project.k8sDomain.clusters.value.length > 0) { + for (const cluster of this.project.k8sDomain.clusters.value) { + const clusterName = _getClusterName(this.project.k8sDomain.uid.value, cluster.name); + + const clusterResource = { + apiVersion: 'weblogic.oracle/v9', + kind: 'Cluster', + metadata: { + name: clusterName, + namespace: this.project.k8sDomain.kubernetesNamespace.value, + labels: { + 'weblogic.domainUID': this.project.k8sDomain.uid.value + } + }, + spec: { + clusterName, + serverPod: getServerPodForCluster(cluster), + }, + }; + + if (typeof cluster.replicas === 'number') { + clusterResource.spec.replicas = cluster.replicas; + } + + clusterResources.push(clusterResource); + } + } + + let result; + if (generateYaml) { + result = jsYaml.dump(domainResource, {}).split('\n'); + for (const clusterResource of clusterResources) { + result.push('', '---', ''); + result.push(...jsYaml.dump(clusterResource, {}).split('\n')); + } + } else { + result = { domainResource }; + + if (clusterResources.length > 0) { + result.clusters = clusterResources; + } + } + + return result; + } + + _getDomainServerPod() { + let serverPod = this._getServerPod(); + + if (this.project.k8sDomain.domainNodeSelector.value.length > 0) { + if (!serverPod) { + serverPod = {}; + } + serverPod.nodeSelector = {}; + + this.project.k8sDomain.domainNodeSelector.value.forEach(selector => { + serverPod.nodeSelector[selector.name] = selector.value; + }); + } + return serverPod; + } + + _getServerPod() { + const serverPod = _getServerPod(getJavaOptions(this.project.k8sDomain), getUserMemArgs(this.project.k8sDomain), + getKubernetesResources(this.project.k8sDomain)); + + if (this.project.settings.targetDomainLocation.value === 'pv') { + const volumeName = this.project.k8sDomain.domainPersistentVolumeName.value || 'volume-name-not-set'; + serverPod.volumes = [ + { + name: volumeName, + persistentVolumeClaim: { + claimName: this.project.k8sDomain.domainPersistentVolumeClaimName.value || 'volume-claim-name-not-set' + } + } + ]; + serverPod.volumeMounts = [ + { + name: volumeName, + mountPath: this.project.k8sDomain.domainPersistentVolumeMountPath.value + } + ]; + } + return serverPod; + } + + _getWdtRelatedPaths() { + let result; + + if (this.project.settings.targetDomainLocation.value === 'mii') { + + result = { }; + if (usingAuxImage()) { + const wdtHome = this.project.image.wdtHomePath.value; + const modelHome = this.project.image.modelHomePath.value; + result.targetWdtHomeDirName = 'weblogic-deploy'; + result.sourceWdtHome = window.api.path.join(wdtHome, result.targetWdtHomeDirName); + result.sourceModelHome = modelHome; + result.targetModelHomeDirName = window.api.path.basename(modelHome); + } else { + // Only set these if they are not the default + if (this.project.image.wdtHomePath.hasValue()) { + result.wdtHome = this.project.image.wdtHomePath.value; + } + if (this.project.image.modelHomePath.hasValue()) { + result.modelHome = this.project.image.modelHomePath.value; + } + } + } + return result; + } + } + + function usingAuxImage() { + return project.settings.targetDomainLocation.value === 'mii' && project.image.useAuxImage.value; + } + + function getOperatorNameForTargetDomainLocation(targetDomainLocation) { + switch (targetDomainLocation) { + case 'mii': + return 'FromModel'; + + case 'dii': + return 'Image'; + + case 'pv': + return 'PersistentVolume'; + + default: + throw new Error(i18n.t('k8s-domain-script-generator-invalid-target-domain-location', + { targetDomainLocation: targetDomainLocation})); + } + } + + function getServerPodForCluster(cluster) { + const serverPod = _getServerPod(getJavaOptionsForCluster(cluster), getUserMemArgsForCluster(cluster), getKubernetesResourcesForCluster(cluster)) || {}; + + const affinity = _getAffinityForServerPod(100); + if (affinity) { + serverPod.affinity = affinity; + } + + return Object.keys(serverPod).length > 0 ? serverPod : null; + } + + function _getServerPod(javaOptions, userMemArgs, resources) { + const serverPod = {}; + const env = []; + addIfNotNull(env, 'JAVA_OPTIONS', javaOptions); + addIfNotNull(env, 'USER_MEM_ARGS', userMemArgs); + if (env.length) { + serverPod.env = env; + } + + if (resources) { + serverPod.resources = resources; + } + + return Object.keys(serverPod).length > 0 ? serverPod : null; + } + + function addIfNotNull(env, varName, varValue) { + if (varValue) { + env.push({ name: varName, value: varValue }); + } + } + + function getJavaOptions(k8sDomain) { + return _getJavaOptions(k8sDomain.disableDebugStdout.value, k8sDomain.useUrandom.value, + k8sDomain.disableFan.value, k8sDomain.additionalArguments.value); + } + + function getJavaOptionsForCluster(cluster) { + return _getJavaOptions(cluster['disableDebugStdout'], cluster['useUrandom'], cluster['disableFan'], + cluster['additionalArguments']); + } + + function _getJavaOptions(disableDebugStdout, useUrandom, disableFan, additionalArguments) { + const result = []; + if (disableDebugStdout) { + result.push('-Dweblogic.StdoutDebugEnabled=false'); + } + if (useUrandom) { + result.push('-Djava.security.egd=file:/dev/./urandom'); + } + if (disableFan) { + result.push('-Doracle.jdbc.fanEnabled=false'); + } + if (additionalArguments && additionalArguments.length > 0) { + result.push(...additionalArguments); + } + return (result.length) ? result.join(' ') : null; + } + + function getUserMemArgs(k8sDomain) { + return _getUserMemArgs(k8sDomain.minimumHeapSize.value, k8sDomain.maximumHeapSize.value); + } + + function getUserMemArgsForCluster(cluster) { + return _getUserMemArgs(cluster['minHeap'], cluster['maxHeap']); + } + + function _getUserMemArgs(minHeap, maxHeap) { + const result = []; + if (minHeap) { + result.push(`-Xms${minHeap}`); + } + if (maxHeap) { + result.push(`-Xmx${maxHeap}`); + } + return (result.length) ? result.join(' ') : null; + } + + function getKubernetesResources(k8sDomain) { + return _getKubernetesResources(k8sDomain.cpuRequest.value, k8sDomain.cpuLimit.value, + k8sDomain.memoryRequest.value, k8sDomain.memoryLimit.value); + } + + function getKubernetesResourcesForCluster(cluster) { + return _getKubernetesResources(cluster.cpuRequest, cluster.cpuLimit, cluster.memoryRequest, cluster.memoryLimit); + } + + function _getKubernetesResources(cpuRequest, cpuLimit, memoryRequest, memoryLimit) { + let foundValue = false; + const resources = { + limits: {}, + requests: {} + }; + + if (cpuRequest) { + resources.requests.cpu = cpuRequest; + foundValue = true; + } + if (cpuLimit) { + resources.limits.cpu = cpuLimit; + foundValue = true; + } + if (memoryRequest) { + resources.requests.memory = memoryRequest; + foundValue = true; + } + if (memoryLimit) { + resources.limits.memory = memoryLimit; + foundValue = true; + } + + return foundValue ? resources : null; + } + + function _getAffinityForServerPod(weight) { + return { + podAntiAffinity: { + preferredDuringSchedulingIgnoredDuringExecution: [{ + weight: weight, + podAffinityTerm: { + topologyKey: 'kubernetes.io/hostname', + labelSelector: { + matchExpressions: [{ + key: 'weblogic.clusterName', + operator: 'In', + values: ['$(CLUSTER_NAME)'], + }], + }, + }, + }], + }, + }; + } + + function _getClusterName(domainUid, clusterName) { + return `${domainUid}-${clusterName.replaceAll('_', '-')}`; + } + + return K8sDomainV9ResourceGenerator; + } +); diff --git a/webui/src/js/utils/vz-component-resource-generator.js b/webui/src/js/utils/vz-component-resource-generator.js index 755c42b50..8e26d3873 100644 --- a/webui/src/js/utils/vz-component-resource-generator.js +++ b/webui/src/js/utils/vz-component-resource-generator.js @@ -8,15 +8,21 @@ define(['models/wkt-project', 'utils/k8s-domain-resource-generator', 'utils/vz-helper', 'js-yaml', 'utils/i18n', 'utils/wkt-logger'], function(project, K8sDomainResourceGenerator, VerrazzanoHelper, jsYaml) { + + // Note that the specific version number doesn't really matter. What is important is until Verrazzano + // starts distributing WKO 4.x, we use a 3.x version number to get the right Domain resource spec. + // + const WKO3_VERSION = '3.4.3'; + class VerrazzanoComponentResourceGenerator { constructor() { this.project = project; - this.k8sDomainResourceGenerator = new K8sDomainResourceGenerator(); + this.k8sDomainResourceGenerator = new K8sDomainResourceGenerator(WKO3_VERSION); this._vzHelper = undefined; } generate() { - const workloadTemplate = this.k8sDomainResourceGenerator.generate(false); + const { domainResource } = this.k8sDomainResourceGenerator.generate(false); const component = { apiVersion: this._getComponentApiVersion(), @@ -30,7 +36,7 @@ function(project, K8sDomainResourceGenerator, VerrazzanoHelper, jsYaml) { apiVersion: this._getWorkloadApiVersion(), kind: 'VerrazzanoWebLogicWorkload', spec: { - template: workloadTemplate + template: domainResource } } } diff --git a/webui/src/js/utils/vz-get-installed-version.js b/webui/src/js/utils/vz-get-installed-version.js index 085ee8581..246a7e25d 100644 --- a/webui/src/js/utils/vz-get-installed-version.js +++ b/webui/src/js/utils/vz-get-installed-version.js @@ -13,7 +13,7 @@ function(VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper) { super(); } - async startVerrazzanoInstallStatusCheck() { + async startVerrazzanoInstallVersionCheck() { await this.executeAction(this.callVerrazzanoInstallVersionCheck); } diff --git a/webui/src/js/utils/wko-get-installed-version.js b/webui/src/js/utils/wko-get-installed-version.js new file mode 100644 index 000000000..1ad8f8209 --- /dev/null +++ b/webui/src/js/utils/wko-get-installed-version.js @@ -0,0 +1,150 @@ +/** + * @license + * Copyright (c) 2022, Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + */ +'use strict'; + +define(['utils/wko-actions-base', 'models/wkt-project', 'models/wkt-console', 'utils/i18n', 'utils/project-io', + 'utils/dialog-helper', 'utils/validation-helper', 'utils/wkt-logger'], +function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, validationHelper) { + class WkoInstallVersionChecker extends WkoActionsBase { + constructor() { + super(); + } + + async startOperatorInstallVersionCheck() { + await this.executeAction(this.callOperatorInstallVersionCheck); + } + + async callOperatorInstallVersionCheck(options) { + if (!options) { + options = {}; + } + + let errTitle = i18n.t('wko-get-install-version-aborted-error-title'); + const errPrefix = 'wko-get-install-version'; + const validatableObject = this.getValidatableObject('flow-wko-get-install-version-name'); + if (validatableObject.hasValidationErrors()) { + const validationErrorDialogConfig = validatableObject.getValidationErrorDialogConfig(errTitle); + dialogHelper.openDialog('validation-error-dialog', validationErrorDialogConfig); + return Promise.resolve(false); + } + + const totalSteps = 5.0; + try { + const operatorNamespace = this.project.wko.k8sNamespace.value; + let busyDialogMessage = i18n.t('flow-validate-kubectl-exe-in-progress'); + dialogHelper.openBusyDialog(busyDialogMessage, 'bar'); + dialogHelper.updateBusyDialog(busyDialogMessage, 0/totalSteps); + + const kubectlExe = this.getKubectlExe(); + if (!options.skipKubectlExeValidation) { + if (! await this.validateKubectlExe(kubectlExe, errTitle, errPrefix)) { + return Promise.resolve(false); + } + } + + // While technically not required, we force saving the project for Go Menu item behavior consistency. + // + busyDialogMessage = i18n.t('flow-save-project-in-progress'); + dialogHelper.updateBusyDialog(busyDialogMessage, 1 / totalSteps); + if (!options.skipProjectSave) { + if (! await this.saveProject(errTitle, errPrefix)) { + return Promise.resolve(false); + } + } + + busyDialogMessage = i18n.t('flow-kubectl-use-context-in-progress'); + dialogHelper.updateBusyDialog(busyDialogMessage, 2 / totalSteps); + const kubectlContext = this.getKubectlContext(); + const kubectlOptions = this.getKubectlOptions(); + if (!options.skipKubectlSetContext) { + const status = await this.useKubectlContext(kubectlExe, kubectlOptions, kubectlContext, errTitle, errPrefix); + if (!status) { + return Promise.resolve(false); + } + } + + busyDialogMessage = i18n.t('wko-get-install-version-checking-installed-in-progress', + {operatorNamespace: operatorNamespace}); + dialogHelper.updateBusyDialog(busyDialogMessage, 3 / totalSteps); + if (!options.skipCheckOperatorAlreadyInstalled) { + const status = await this.checkOperatorIsInstalled(kubectlExe, kubectlOptions, operatorNamespace, errTitle, errPrefix); + if (!status) { + return Promise.resolve(false); + } + } + + busyDialogMessage = i18n.t('wko-get-install-version-get-in-progress'); + dialogHelper.updateBusyDialog(busyDialogMessage, 4 / totalSteps); + const versionResults = await window.api.ipc.invoke('k8s-get-operator-version', kubectlExe, operatorNamespace, kubectlOptions); + if (versionResults.isSuccess) { + this.project.wko.installedVersion.value = versionResults.version; + return Promise.resolve(true); + } else { + errTitle = i18n.t('wko-get-install-version-get-failed-title'); + const errMessage = i18n.t('wko-get-install-version-get-failed-error-message', + { operatorNamespace: operatorNamespace, error: versionResults.reason }); + await window.api.ipc.invoke('show-error-message', errTitle, errMessage); + return Promise.resolve(false); + } + } catch(err) { + dialogHelper.closeBusyDialog(); + throw err; + } finally { + dialogHelper.closeBusyDialog(); + } + } + + getValidatableObject(flowNameKey) { + const validationObject = validationHelper.createValidatableObject(flowNameKey); + const kubectlFormConfig = validationObject.getDefaultConfigObject(); + kubectlFormConfig.formName = 'kubectl-form-name'; + + validationObject.addField('kubectl-exe-file-path-label', + validationHelper.validateRequiredField(this.project.kubectl.executableFilePath.value), kubectlFormConfig); + + const wkoFormConfig = validationObject.getDefaultConfigObject(); + wkoFormConfig.formName = 'wko-design-form-name'; + validationObject.addField('wko-design-k8s-namespace-label', + validationHelper.validateRequiredField(this.project.wko.k8sNamespace.value), wkoFormConfig); + + return validationObject; + } + + async checkOperatorIsInstalled(kubectlExe, kubectlOptions, operatorNamespace, errTitle, errPrefix) { + try { + const isInstalledResults = + await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorNamespace, kubectlOptions); + + if (!isInstalledResults.isInstalled) { + // There should only be a reason if the backend error didn't match the expected "not found" error condition! + let errMessage; + if (isInstalledResults.reason) { + errMessage = i18n.t('wko-get-install-version-installed-check-failed-error-message', + {operatorNamespace: operatorNamespace, error: isInstalledResults.reason}); + } else { + errMessage = i18n.t('wko-get-install-version-not-installed-error-message', { operatorNamespace: operatorNamespace }); + } + dialogHelper.closeBusyDialog(); + await window.api.ipc.invoke('show-error-message', errTitle, errMessage); + return Promise.resolve(false); + } else { + // Best effort attempt to prevent users from trying to use pre-3.0 versions of operator with the UI. + // + if (! await this.isWkoInstalledVersionSupported(isInstalledResults, operatorNamespace, errTitle, errPrefix)) { + return Promise.resolve(false); + } + if (! await this.isWkoImageVersionSupported(operatorNamespace, errTitle, errPrefix)) { + return Promise.resolve(false); + } + } + } catch (err) { + return Promise.reject(err); + } + return Promise.resolve(true); + } + } + return new WkoInstallVersionChecker(); +}); diff --git a/webui/src/js/utils/wko-installer.js b/webui/src/js/utils/wko-installer.js index 73f4a23c2..fa81ff036 100644 --- a/webui/src/js/utils/wko-installer.js +++ b/webui/src/js/utils/wko-installer.js @@ -141,7 +141,7 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val wktLogger.debug('helmChartValues = %s', JSON.stringify(helmChartValues, null, 2)); const installResults = await window.api.ipc.invoke('helm-install-wko', helmExe, helmReleaseName, - operatorNamespace, helmChartValues, helmOptions); + operatorNamespace, helmChartValues, helmOptions, kubectlExe, kubectlOptions); dialogHelper.closeBusyDialog(); @@ -149,6 +149,7 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val const title = i18n.t('wko-installer-install-complete-title'); const message = i18n.t('wko-installer-install-complete-message', { operatorName: helmReleaseName, operatorNamespace: operatorNamespace }); + this.project.wko.installedVersion.value = installResults.version; await window.api.ipc.invoke('show-info-message', title, message); return Promise.resolve(true); } else { diff --git a/webui/src/js/utils/wko-uninstaller.js b/webui/src/js/utils/wko-uninstaller.js index 6419a0810..336963f78 100644 --- a/webui/src/js/utils/wko-uninstaller.js +++ b/webui/src/js/utils/wko-uninstaller.js @@ -174,7 +174,7 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val async checkOperatorIsInstalled(kubectlExe, kubectlOptions, helmReleaseName, operatorNamespace, errTitle, errPrefix) { try { const isInstalledResults = - await window.api.ipc.invoke('is-wko-installed', kubectlExe, helmReleaseName, operatorNamespace, kubectlOptions); + await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorNamespace, kubectlOptions); if (!isInstalledResults.isInstalled) { // There should only be a reason if the backend error didn't match the expected "not found" error condition! @@ -205,4 +205,4 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val } return new WkoUninstaller(); -}); \ No newline at end of file +}); diff --git a/webui/src/js/utils/wko-updater.js b/webui/src/js/utils/wko-updater.js index ee93f3721..f390e02f5 100644 --- a/webui/src/js/utils/wko-updater.js +++ b/webui/src/js/utils/wko-updater.js @@ -140,13 +140,14 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val wktLogger.debug('helmChartValues = %s', JSON.stringify(helmChartValues, null, 2)); const updateResults = await window.api.ipc.invoke('helm-update-wko', helmExe, helmReleaseName, - operatorNamespace, helmChartValues, helmOptions); + operatorNamespace, helmChartValues, helmOptions, kubectlExe, kubectlOptions); dialogHelper.closeBusyDialog(); if (updateResults.isSuccess) { const title = i18n.t('wko-updater-update-complete-title'); const message = i18n.t('wko-updater-update-complete-message', { operatorName: helmReleaseName, operatorNamespace: operatorNamespace }); + this.project.wko.installedVersion.value = updateResults.version; await window.api.ipc.invoke('show-info-message', title, message); return Promise.resolve(true); } else { @@ -167,7 +168,7 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val async checkOperatorIsInstalled(kubectlExe, kubectlOptions, helmReleaseName, operatorNamespace, errTitle, errPrefix) { try { const isInstalledResults = - await window.api.ipc.invoke('is-wko-installed', kubectlExe, helmReleaseName, operatorNamespace, kubectlOptions); + await window.api.ipc.invoke('is-wko-installed', kubectlExe, operatorNamespace, kubectlOptions); if (!isInstalledResults.isInstalled) { // There should only be a reason if the backend error didn't match the expected "not found" error condition! @@ -199,4 +200,4 @@ function(WkoActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val } return new WkoUpdater(); -}); \ No newline at end of file +}); diff --git a/webui/src/js/viewModels/domain-code-view.js b/webui/src/js/viewModels/domain-code-view.js index 72b02242d..6db7701a6 100644 --- a/webui/src/js/viewModels/domain-code-view.js +++ b/webui/src/js/viewModels/domain-code-view.js @@ -77,7 +77,6 @@ function (accUtils, ko, project, K8sDomainScriptGenerator, K8sDomainConfigMapGen this.configMapText = ko.observable(); this.k8sConfigMapGenerator = new K8sDomainConfigMapGenerator(); - this.k8sDomainResourceGenerator = new K8sDomainResourceGenerator(); this.renderScript = (selectedSubview) => { switch (selectedSubview) { @@ -103,8 +102,16 @@ function (accUtils, ko, project, K8sDomainScriptGenerator, K8sDomainConfigMapGen this.configMapText(this.k8sConfigMapGenerator.generate().join('\n')); }; + this.getDomainResourceGenerator = () => { + if (this.project.wko.installedVersion.hasValue()) { + return new K8sDomainResourceGenerator(this.project.wko.installedVersion.value); + } else { + return new K8sDomainResourceGenerator(); + } + }; + this.renderDomainResource = () => { - this.domainText(this.k8sDomainResourceGenerator.generate().join('\n')); + this.domainText(this.getDomainResourceGenerator().generate().join('\n')); }; this.renderScript(this.selectedSubview()); diff --git a/webui/src/js/viewModels/domain-design-view.js b/webui/src/js/viewModels/domain-design-view.js index 6e93d2a0f..2d063c9f6 100644 --- a/webui/src/js/viewModels/domain-design-view.js +++ b/webui/src/js/viewModels/domain-design-view.js @@ -5,11 +5,11 @@ */ define(['models/wkt-project', 'accUtils', 'utils/common-utilities', 'knockout', 'utils/i18n', 'utils/screen-utils', 'ojs/ojbufferingdataprovider', 'ojs/ojarraydataprovider', 'ojs/ojconverter-number', 'utils/dialog-helper', - 'utils/view-helper', 'utils/wkt-logger', 'ojs/ojmessaging', 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', - 'ojs/ojformlayout', 'ojs/ojcollapsible', 'ojs/ojselectsingle', 'ojs/ojlistview', 'ojs/ojtable', 'ojs/ojswitch', - 'ojs/ojinputnumber', 'ojs/ojradioset'], + 'utils/view-helper', 'utils/wko-get-installed-version', 'utils/wkt-logger', 'ojs/ojmessaging', 'ojs/ojinputtext', + 'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojformlayout', 'ojs/ojcollapsible', 'ojs/ojselectsingle', 'ojs/ojlistview', + 'ojs/ojtable', 'ojs/ojswitch', 'ojs/ojinputnumber', 'ojs/ojradioset'], function (project, accUtils, utils, ko, i18n, screenUtils, BufferingDataProvider, ArrayDataProvider, - ojConverterNumber, dialogHelper, viewHelper) { + ojConverterNumber, dialogHelper, viewHelper, wkoInstalledVersionChecker) { function DomainDesignViewModel() { let subscriptions = []; @@ -69,6 +69,10 @@ function (project, accUtils, utils, ko, i18n, screenUtils, BufferingDataProvider this.project = project; this.i18n = i18n; + this.getWkoInstalledVersion = () => { + wkoInstalledVersionChecker.startOperatorInstallVersionCheck().then(); + }; + this.mainCreateImageSwitchHelp = ko.computed(() => { if (this.project.image.useAuxImage.value) { return this.imageLabelMapper('create-image-aux-help'); diff --git a/webui/src/js/viewModels/vz-application-design-view.js b/webui/src/js/viewModels/vz-application-design-view.js index f6c937ee6..b5e3fd20b 100644 --- a/webui/src/js/viewModels/vz-application-design-view.js +++ b/webui/src/js/viewModels/vz-application-design-view.js @@ -31,7 +31,7 @@ function (project, accUtils, utils, ko, i18n, BufferingDataProvider, ArrayDataPr this.i18n = i18n; this.getInstalledVersionNumber = () => { - verrazzanoInstallVersionChecker.startVerrazzanoInstallStatusCheck().then(); + verrazzanoInstallVersionChecker.startVerrazzanoInstallVersionCheck().then(); }; this.components = this.project.vzApplication.components; diff --git a/webui/src/js/viewModels/vz-component-design-view.js b/webui/src/js/viewModels/vz-component-design-view.js index 4df4986ae..954e80e71 100644 --- a/webui/src/js/viewModels/vz-component-design-view.js +++ b/webui/src/js/viewModels/vz-component-design-view.js @@ -82,7 +82,7 @@ function (project, accUtils, utils, ko, i18n, screenUtils, BufferingDataProvider this.i18n = i18n; this.getInstalledVersionNumber = () => { - verrazzanoInstallVersionChecker.startVerrazzanoInstallStatusCheck().then(); + verrazzanoInstallVersionChecker.startVerrazzanoInstallVersionCheck().then(); }; this.mainCreateImageSwitchHelp = ko.computed(() => { diff --git a/webui/src/js/views/domain-design-view.html b/webui/src/js/views/domain-design-view.html index 0d0e775c8..3ae9ddce3 100644 --- a/webui/src/js/views/domain-design-view.html +++ b/webui/src/js/views/domain-design-view.html @@ -57,6 +57,14 @@