diff --git a/src/commands/server/update.ts b/src/commands/server/update.ts index ccdfbad98..e5c262edf 100644 --- a/src/commands/server/update.ts +++ b/src/commands/server/update.ts @@ -13,6 +13,7 @@ import { string } from '@oclif/parser/lib/flags' import { cli } from 'cli-ux' import * as fs from 'fs-extra' import * as Listr from 'listr' +import { merge } from 'lodash' import * as path from 'path' import { ChectlContext } from '../../api/context' @@ -23,11 +24,20 @@ import { getPrintHighlightedMessagesTask } from '../../tasks/installers/common-t import { InstallerTasks } from '../../tasks/installers/installer' import { ApiTasks } from '../../tasks/platforms/api' import { CommonPlatformTasks } from '../../tasks/platforms/common-platform-tasks' -import { getCommandErrorMessage, getCommandSuccessMessage, getImageTag, notifyCommandCompletedSuccessfully } from '../../util' +import { getCommandErrorMessage, getCommandSuccessMessage, getCurrentChectlName, getCurrentChectlVersion, getImageTag, getLatestChectlVersion, notifyCommandCompletedSuccessfully } from '../../util' export default class Update extends Command { static description = 'Update Eclipse Che server.' + static examples = [ + '# Update Eclipse Che:\n' + + 'chectl server:update', + '\n\n# Update Eclipse Che in \'eclipse-che\' namespace:' + + 'chectl server:update -n eclipse-che', + '\n\n# Update Eclipse Che and update its configuration in the custom resource:' + + `chectl server:update --${CHE_OPERATOR_CR_PATCH_YAML_KEY} patch.yaml`, + ] + static flags: flags.Input = { installer: string({ char: 'a', @@ -110,40 +120,116 @@ export default class Update extends Command { try { await preUpdateTasks.run(ctx) + } catch (err) { + this.error(getCommandErrorMessage(err)) + } - if (!flags.yes) { - cli.info(`Existed Eclipse Che operator: ${ctx.deployedCheOperatorImage}:${ctx.deployedCheOperatorTag}.`) - cli.info(`New Eclipse Che operator : ${ctx.newCheOperatorImage}:${ctx.newCheOperatorTag}.`) + if (flags.installer === 'operator') { + const existedOperatorImage = `${ctx.deployedCheOperatorImage}:${ctx.deployedCheOperatorTag}` + const newOperatorImage = `${ctx.newCheOperatorImage}:${ctx.newCheOperatorTag}` + cli.info(`Existed Eclipse Che operator: ${existedOperatorImage}.`) + cli.info(`New Eclipse Che operator : ${newOperatorImage}.`) - if (flags['che-operator-image'] !== DEFAULT_CHE_OPERATOR_IMAGE) { - cli.warn(`This command updates Eclipse Che to ${getImageTag(DEFAULT_CHE_OPERATOR_IMAGE)} version, but custom operator image is specified.`) - cli.warn('Make sure that the new version of the Eclipse Che is corresponding to the version of the tool you use.') - cli.warn('Consider using \'chectl update [stable|next]\' to update to the latest version of chectl.') - } + const defaultOperatorImageTag = getImageTag(DEFAULT_CHE_OPERATOR_IMAGE) + const chectlChannel = defaultOperatorImageTag === 'nightly' ? 'next' : 'stable' + const currentChectlVersion = getCurrentChectlVersion() + const latestChectlVersion = await getLatestChectlVersion(chectlChannel) + const chectlName = getCurrentChectlName() - const cheCluster = await kubeHelper.getCheCluster(flags.chenamespace) - if (cheCluster.spec.server.cheImage - || cheCluster.spec.server.cheImageTag - || cheCluster.spec.server.devfileRegistryImage - || cheCluster.spec.database.postgresImage - || cheCluster.spec.server.pluginRegistryImage - || cheCluster.spec.auth.identityProviderImage) { - cli.warn(`In order to update Eclipse Che the images defined in the '${cheCluster.metadata.name}' - Custom Resource of the namespace '${flags.chenamespace}' will be cleaned up:`) - cheCluster.spec.server.cheImageTag && cli.warn(`Eclipse Che server image tag [${cheCluster.spec.server.cheImageTag}]`) - cheCluster.spec.server.cheImage && cli.warn(`Eclipse Che server [${cheCluster.spec.server.cheImage}]`) - cheCluster.spec.database.postgresImage && cli.warn(`Database [${cheCluster.spec.database.postgresImage}]`) - cheCluster.spec.server.devfileRegistryImage && cli.warn(`Devfile registry [${cheCluster.spec.server.devfileRegistryImage}]`) - cheCluster.spec.server.pluginRegistryImage && cli.warn(`Plugin registry [${cheCluster.spec.server.pluginRegistryImage}]`) - cheCluster.spec.auth.identityProviderImage && cli.warn(`Identity provider [${cheCluster.spec.auth.identityProviderImage}]`) + // the same version is already installed + if (newOperatorImage === existedOperatorImage) { + if (chectlName === 'chectl' && latestChectlVersion) { + // suggest update chectl first + if (currentChectlVersion !== latestChectlVersion) { + cli.warn(`It is not possible to update Eclipse Che to a newer version +using the current '${currentChectlVersion}' version of chectl. Please, update 'chectl' +to a newer version '${latestChectlVersion}' with the command 'chectl update ${chectlChannel}' +and then try again.`) + } else if (!flags[CHE_OPERATOR_CR_PATCH_YAML_KEY]) { + // same version, no patch then nothing to update + cli.info('Eclipse Che is already up to date.') + this.exit(0) + } + } else { + // unknown project, no patch file then suggest to update + if (!flags[CHE_OPERATOR_CR_PATCH_YAML_KEY]) { + cli.warn(`It is not possible to update Eclipse Che to a newer version +using the current '${currentChectlVersion}' version of '${getCurrentChectlName()}'. +Please, update '${getCurrentChectlName()}' and then try again.`) + this.exit(0) + } } + // custom operator image is used + } else if (newOperatorImage !== DEFAULT_CHE_OPERATOR_IMAGE) { + cli.warn(`Eclipse Che operator deployment will be updated with the provided image, +but other Eclipse Che components will be updated to the ${defaultOperatorImageTag} version. +Consider removing '--che-operator-image' to update Eclipse Che operator to the same version.`) + } + + if (!flags.yes && !await cli.confirm('If you want to continue - press Y')) { + cli.info('Update cancelled by user.') + this.exit(0) + } + } + + const cheCluster = await kubeHelper.getCheCluster(flags.chenamespace) + if (cheCluster.spec.server.cheImage + || cheCluster.spec.server.cheImageTag + || cheCluster.spec.server.devfileRegistryImage + || cheCluster.spec.database.postgresImage + || cheCluster.spec.server.pluginRegistryImage + || cheCluster.spec.auth.identityProviderImage) { + let imagesListMsg = '' + + const crPatch = ctx[ChectlContext.CR_PATCH] || {} + if (cheCluster.spec.server.pluginRegistryImage + && (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.pluginRegistryImage)) { + imagesListMsg += `\n - Plugin registry image: ${cheCluster.spec.server.pluginRegistryImage}` + merge(crPatch, { spec: { server: { pluginRegistryImage: '' } } }) + } - const confirmed = await cli.confirm('If you want to continue - press Y') - if (!confirmed) { + if (cheCluster.spec.server.devfileRegistryImage + && (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.devfileRegistryImage)) { + imagesListMsg += `\n - Devfile registry image: ${cheCluster.spec.server.devfileRegistryImage}` + merge(crPatch, { spec: { server: { devfileRegistryImage: '' } } }) + } + + if (cheCluster.spec.server.postgresImage + && (!crPatch.spec || !crPatch.spec.database || !crPatch.spec.database.postgresImage)) { + imagesListMsg += `\n - Postgres image: ${cheCluster.spec.database.postgresImage}` + merge(crPatch, { spec: { database: { postgresImage: '' } } }) + } + + if (cheCluster.spec.server.identityProviderImage + && (!crPatch.spec || !crPatch.spec.auth || !crPatch.spec.auth.identityProviderImage)) { + imagesListMsg += `\n - Identity provider image: ${cheCluster.spec.auth.identityProviderImage}` + merge(crPatch, { spec: { auth: { identityProviderImage: '' } } }) + } + + if (cheCluster.spec.server.cheImage + && (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.cheImage)) { + imagesListMsg += `\n - Eclipse Che server image name: ${cheCluster.spec.server.cheImage}` + merge(crPatch, { spec: { server: { cheImage: '' } } }) + } + + if (cheCluster.spec.server.cheImageTag + && (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.cheImageTag)) { + imagesListMsg += `\n - Eclipse Che server image tag: ${cheCluster.spec.server.cheImageTag}` + merge(crPatch, { spec: { server: { cheImageTag: '' } } }) + } + ctx[ChectlContext.CR_PATCH] = crPatch + + if (imagesListMsg) { + cli.warn(`In order to update Eclipse Che to a newer version the fields defining the images in the '${cheCluster.metadata.name}' +Custom Resource in the '${flags.chenamespace}' namespace will be cleaned up:${imagesListMsg}`) + if (!flags.yes && !await cli.confirm('If you want to continue - press Y')) { + cli.info('Update cancelled by user.') this.exit(0) } } + } + try { await updateTasks.run(ctx) await postUpdateTasks.run(ctx) diff --git a/src/tasks/installers/common-tasks.ts b/src/tasks/installers/common-tasks.ts index c9a566726..552567623 100644 --- a/src/tasks/installers/common-tasks.ts +++ b/src/tasks/installers/common-tasks.ts @@ -12,10 +12,11 @@ import Command from '@oclif/command' import ansi = require('ansi-colors') import { copy, mkdirp, remove } from 'fs-extra' import * as Listr from 'listr' -import { merge } from 'lodash' +import { isEmpty } from 'lodash' import * as path from 'path' import { CheHelper } from '../../api/che' +import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' import { CHE_CLUSTER_CRD, DOCS_LINK_IMPORT_CA_CERT_INTO_BROWSER } from '../../constants' @@ -97,34 +98,16 @@ export function createEclipseCheCluster(flags: any, kube: KubeHelper): Listr.Lis * @param kube - kubeHelper util * @param command - parent command */ -export function updateEclipseCheCluster(flags: any, kube: KubeHelper, command: Command): Listr.ListrTask { +export function patchingEclipseCheCluster(flags: any, kube: KubeHelper, command: Command): Listr.ListrTask { return { - title: `Update the Custom Resource of type ${CHE_CLUSTER_CRD} in the namespace ${flags.chenamespace}`, + title: `Patching the Custom Resource of type '${CHE_CLUSTER_CRD}' in the namespace '${flags.chenamespace}'`, + skip: (ctx: any) => isEmpty(ctx[ChectlContext.CR_PATCH]), task: async (ctx: any, task: any) => { - let crPatch: any = ctx.crPatch || {} - const cheCluster = await kube.getCheCluster(flags.chenamespace) if (!cheCluster) { - command.error(`Eclipse Che cluster CR was not found in the namespace ${flags.chenamespace}`) - } - - if (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.pluginRegistryImage) { - merge(crPatch, { spec: { server: { pluginRegistryImage: '' } } }) - } - if (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.devfileRegistryImage) { - merge(crPatch, { spec: { server: { devfileRegistryImage: '' } } }) + command.error(`Eclipse Che cluster CR is not found in the namespace '${flags.chenamespace}'`) } - if (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.identityProviderImage) { - merge(crPatch, { spec: { server: { identityProviderImage: '' } } }) - } - if (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.cheImage) { - merge(crPatch, { spec: { server: { cheImage: '' } } }) - } - if (!crPatch.spec || !crPatch.spec.server || !crPatch.spec.server.cheImageTag) { - merge(crPatch, { spec: { server: { cheImageTag: '' } } }) - } - - await kube.patchCheCluster(cheCluster.metadata.name, flags.chenamespace, crPatch) + await kube.patchCheCluster(cheCluster.metadata.name, flags.chenamespace, ctx[ChectlContext.CR_PATCH]) task.title = `${task.title}...done.` } } diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index 954e0c89a..43114eb28 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -19,7 +19,7 @@ import { CatalogSource, Subscription } from '../../api/typings/olm' import { CUSTOM_CATALOG_SOURCE_NAME, CVS_PREFIX, DEFAULT_CHE_OLM_PACKAGE_NAME, DEFAULT_OLM_KUBERNETES_NAMESPACE, DEFAULT_OPENSHIFT_MARKET_PLACE_NAMESPACE, KUBERNETES_OLM_CATALOG, NIGHTLY_CATALOG_SOURCE_NAME, OLM_NIGHTLY_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPENSHIFT_OLM_CATALOG, OPERATOR_GROUP_NAME, SUBSCRIPTION_NAME } from '../../constants' import { isKubernetesPlatformFamily, isStableVersion } from '../../util' -import { createEclipseCheCluster, createNamespaceTask, updateEclipseCheCluster } from './common-tasks' +import { createEclipseCheCluster, createNamespaceTask, patchingEclipseCheCluster } from './common-tasks' export class OLMTasks { prometheusRoleName = 'prometheus-k8s' @@ -269,7 +269,7 @@ export class OLMTasks { task.title = `${task.title}...done.` } }, - updateEclipseCheCluster(flags, kube, command) + patchingEclipseCheCluster(flags, kube, command) ], { renderer: flags['listr-renderer'] as any }) } diff --git a/src/tasks/installers/operator.ts b/src/tasks/installers/operator.ts index 2f1a1ec68..f436e2dd0 100644 --- a/src/tasks/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -19,7 +19,7 @@ import { CHE_CLUSTER_CRD, CHE_OPERATOR_SELECTOR, OPERATOR_DEPLOYMENT_NAME } from import { isStableVersion } from '../../util' import { KubeTasks } from '../kube' -import { copyOperatorResources, createEclipseCheCluster, createNamespaceTask, updateEclipseCheCluster } from './common-tasks' +import { copyOperatorResources, createEclipseCheCluster, createNamespaceTask, patchingEclipseCheCluster } from './common-tasks' export class OperatorTasks { operatorServiceAccount = 'che-operator' @@ -380,7 +380,7 @@ export class OperatorTasks { await kube.waitLatestReplica(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) } }, - updateEclipseCheCluster(flags, kube, command), + patchingEclipseCheCluster(flags, kube, command), ], { renderer: flags['listr-renderer'] as any }) } diff --git a/src/util.ts b/src/util.ts index 3e3a9a578..b178e0a85 100644 --- a/src/util.ts +++ b/src/util.ts @@ -8,11 +8,15 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import axios from 'axios' import * as commandExists from 'command-exists' import * as fs from 'fs-extra' +import * as https from 'https' import * as yaml from 'js-yaml' import * as notifier from 'node-notifier' +import * as pjson from '../package.json' + import { ChectlContext } from './api/context' import { DEFAULT_CHE_OPERATOR_IMAGE } from './constants' @@ -174,3 +178,37 @@ export function getCommandErrorMessage(err: Error): string { return message } + +/** + * Returns current chectl version defined in package.json. + */ +export function getCurrentChectlVersion(): string { + return pjson.version +} + +/** + * Returns current chectl version defined in package.json. + */ +export function getCurrentChectlName(): string { + return pjson.name +} + +/** + * Returns latest chectl version for the given channel. + */ +export async function getLatestChectlVersion(channel: string): Promise { + if (getCurrentChectlName() !== 'chectl') { + return + } + + const axiosInstance = axios.create({ + httpsAgent: new https.Agent({}) + }) + + try { + const { data } = await axiosInstance.get(`https://che-incubator.github.io/chectl/channels/${channel}/linux-x64`) + return data.version + } catch { + return + } +} diff --git a/tsconfig.json b/tsconfig.json index cb30b69c9..cf53888a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "module": "commonjs", "moduleResolution": "node", "target": "es6", - "sourceMap": true + "sourceMap": true, + "resolveJsonModule": true }, "include": [ "src" diff --git a/yarn.lock b/yarn.lock index ce31a2046..aab2b4144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2126,7 +2126,7 @@ ecc-jsbn@~0.1.1: "eclipse-che-devfile-workspace-operator@git://github.com/devfile/devworkspace-operator#master": version "0.0.0" - resolved "git://github.com/devfile/devworkspace-operator#55769df6e901c37b38384dc231e4abc5fbcdb96e" + resolved "git://github.com/devfile/devworkspace-operator#488c2a00590dab1378e4eb498ff889021c4268ba" "eclipse-che-minishift@git://github.com/minishift/minishift#master": version "0.0.0" @@ -2134,11 +2134,11 @@ ecc-jsbn@~0.1.1: "eclipse-che-operator@git://github.com/eclipse/che-operator#master": version "0.0.0" - resolved "git://github.com/eclipse/che-operator#ed3df35359b648d19a5a775f3a2e140f5da96933" + resolved "git://github.com/eclipse/che-operator#9eee2bfc6f7791e85675bc95d76d556025132cb5" "eclipse-che@git://github.com/eclipse/che#master": version "0.0.0" - resolved "git://github.com/eclipse/che#6e6b773e060bd04d876b150fa7470637496e675f" + resolved "git://github.com/eclipse/che#7061f06ab1e13b947967878b0a96391ad29d7e59" editorconfig@^0.15.0: version "0.15.3"