From cbfa9411d0509fd29a2c1b3acd63b7eeca7b08e4 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Mon, 2 Nov 2020 08:43:48 +0200 Subject: [PATCH] Improvements Signed-off-by: Anatolii Bazko --- README.md | 101 ++++-------- src/api/kube.ts | 75 ++++++--- src/api/openshift.ts | 11 +- src/api/version.ts | 5 +- src/commands/server/debug.ts | 3 +- src/commands/server/delete.ts | 56 ++++--- src/commands/server/deploy.ts | 42 +++-- src/commands/server/logs.ts | 3 +- src/commands/server/status.ts | 8 +- src/commands/server/stop.ts | 2 +- src/commands/server/update.ts | 153 ++++++++---------- src/common-flags.ts | 12 +- src/constants.ts | 8 +- src/tasks/che.ts | 13 +- .../devfile-workspace-operator-installer.ts | 10 +- src/tasks/installers/common-tasks.ts | 2 +- src/tasks/installers/minishift-addon.ts | 2 +- src/tasks/installers/operator.ts | 27 ++-- src/tasks/kube.ts | 8 +- src/tasks/platforms/api.ts | 5 +- src/tasks/platforms/common-platform-tasks.ts | 9 +- src/tasks/platforms/openshift.ts | 14 +- src/util.ts | 23 ++- test/e2e/minikube.test.ts | 2 +- test/e2e/minishift.test.ts | 2 +- test/e2e/openshift.test.ts | 2 +- test/tasks/platforms/openshift.test.ts | 8 +- 27 files changed, 300 insertions(+), 306 deletions(-) diff --git a/README.md b/README.md index ba6d5cb10..ed1db560c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ If you're using linux or macOS, here is how to install chectl by using one singl ``` $ bash <(curl -sL https://www.eclipse.org/che/chectl/) ``` - + - For `next` channel: ``` $ bash <(curl -sL https://www.eclipse.org/che/chectl/) --channel=next @@ -372,16 +372,14 @@ USAGE $ chectl server:debug OPTIONS - -h, --help show CLI help - - -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server is supposed to - be deployed + -h, --help show CLI help - --debug-port=debug-port [default: 8000] Eclipse Che server debug port + -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server is supposed to be + deployed - --listr-renderer=default|silent|verbose [default: default] Listr renderer + --debug-port=debug-port [default: 8000] Eclipse Che server debug port - --skip-kubernetes-health-check Skip Kubernetes health check + --skip-kubernetes-health-check Skip Kubernetes health check ``` _See code: [src/commands/server/debug.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/server/debug.ts)_ @@ -400,6 +398,10 @@ OPTIONS -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server is supposed to be deployed + -y, --yes Automatic yes to prompts; assume "yes" as + answer to all prompts and run + non-interactively + --delete-namespace Indicates that a Eclipse Che namespace will be deleted as well @@ -410,10 +412,6 @@ OPTIONS parameter is used only when the workspace engine is the DevWorkspace - --listr-renderer=default|silent|verbose [default: default] Listr renderer - - --skip-deletion-check Skip user confirmation on deletion check - --skip-kubernetes-health-check Skip Kubernetes health check ``` @@ -462,17 +460,6 @@ OPTIONS Type of Kubernetes platform. Valid values are "minikube", "minishift", "k8s (for kubernetes)", "openshift", "crc (for CodeReady Containers)", "microk8s". - -s, --tls - Deprecated. Enable TLS encryption. - Note, this option is turned on by default. - To provide own certificate for Kubernetes infrastructure, 'che-tls' secret with TLS certificate - must be pre-created in the configured namespace. - In case of providing own self-signed certificate 'self-signed-certificate' secret should be - also created. - For OpenShift, router will use default cluster certificates. - Please see the docs how to deploy Eclipse Che on different infrastructures: - https://www.eclipse.org/che/docs/che-7/overview/running-che-locally/ - -t, --templates=templates Path to the templates folder @@ -534,9 +521,6 @@ OPTIONS --k8spodwaittimeout=k8spodwaittimeout [default: 300000] Waiting time for Pod Wait Timeout Kubernetes (in milliseconds) - --listr-renderer=default|silent|verbose - [default: default] Listr renderer - --olm-channel=olm-channel Olm channel to install Eclipse Che, f.e. stable. If options was not set, will be used default version for package manifest. @@ -552,9 +536,6 @@ OPTIONS --postgres-pvc-storage-class-name=postgres-pvc-storage-class-name persistent volume storage class name to use to store Eclipse Che postgres database - --self-signed-cert - Deprecated. The flag is ignored. Self signed certificates usage is autodetected now. - --skip-cluster-availability-check Skip cluster availability check. The check is a simple request to ensure the cluster is reachable. @@ -594,17 +575,15 @@ USAGE $ chectl server:logs OPTIONS - -d, --directory=directory Directory to store logs into - -h, --help show CLI help - - -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server is supposed to - be deployed + -d, --directory=directory Directory to store logs into + -h, --help show CLI help - --deployment-name=deployment-name [default: che] Eclipse Che deployment name + -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server is supposed to be + deployed - --listr-renderer=default|silent|verbose [default: default] Listr renderer + --deployment-name=deployment-name [default: che] Eclipse Che deployment name - --skip-kubernetes-health-check Skip Kubernetes health check + --skip-kubernetes-health-check Skip Kubernetes health check ``` _See code: [src/commands/server/logs.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/server/logs.ts)_ @@ -658,9 +637,6 @@ OPTIONS [default: devworkspace-controller] Namespace for the DevWorkspace controller. This parameter is used only when the workspace engine is the DevWorkspace - --listr-renderer=default|silent|verbose - [default: default] Listr renderer - --skip-kubernetes-health-check Skip Kubernetes health check ``` @@ -676,44 +652,27 @@ USAGE $ chectl server:update OPTIONS - -a, --installer=operator|olm Installer type. If not set, default is - autodetected depending on previous - installation. - - -h, --help show CLI help - - -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where - Eclipse Che server is supposed to be - deployed - - -p, --platform=minikube|minishift|k8s|openshift|microk8s|docker-desktop|crc Type of Kubernetes platform. Valid values - are "minikube", "minishift", "k8s (for - kubernetes)", "openshift", "crc (for - CodeReady Containers)", "microk8s". + -h, --help show CLI help - -t, --templates=templates [default: templates] Path to the - templates folder + -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server + is supposed to be deployed - --che-operator-cr-patch-yaml=che-operator-cr-patch-yaml Path to a yaml file that overrides the - default values in CheCluster CR used by - the operator. This parameter is used only - when the installer is the 'operator' or - the 'olm'. + -t, --templates=templates [default: templates] Path to the templates folder - --che-operator-image=che-operator-image [default: - quay.io/eclipse/che-operator:nightly] - Container image of the operator. This - parameter is used only when the installer - is the operator + -y, --yes Automatic yes to prompts; assume "yes" as answer to all + prompts and run non-interactively - --deployment-name=deployment-name [default: che] Eclipse Che deployment - name + --che-operator-cr-patch-yaml=che-operator-cr-patch-yaml Path to a yaml file that overrides the default values in + CheCluster CR used by the operator. This parameter is used + only when the installer is the 'operator' or the 'olm'. - --listr-renderer=default|silent|verbose [default: default] Listr renderer + --che-operator-image=che-operator-image [default: quay.io/eclipse/che-operator:nightly] Container + image of the operator. This parameter is used only when the + installer is the operator - --skip-kubernetes-health-check Skip Kubernetes health check + --deployment-name=deployment-name [default: che] Eclipse Che deployment name - --skip-version-check Skip user confirmation on version check + --skip-kubernetes-health-check Skip Kubernetes health check ``` _See code: [src/commands/server/update.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/server/update.ts)_ diff --git a/src/api/kube.ts b/src/api/kube.ts index 05b2bab0e..0471c1ff0 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { ApiextensionsV1beta1Api, ApisApi, AppsV1Api, AuthorizationV1Api, BatchV1Api, CoreV1Api, CustomObjectsApi, ExtensionsV1beta1Api, ExtensionsV1beta1IngressList, KubeConfig, Log, PortForward, RbacAuthorizationV1Api, V1beta1CustomResourceDefinition, V1ClusterRole, V1ClusterRoleBinding, V1ConfigMap, V1ConfigMapEnvSource, V1Container, V1ContainerStateWaiting, V1Deployment, V1DeploymentList, V1DeploymentSpec, V1EnvFromSource, V1Job, V1JobSpec, V1LabelSelector, V1NamespaceList, V1ObjectMeta, V1PersistentVolumeClaimList, V1Pod, V1PodCondition, V1PodList, V1PodSpec, V1PodTemplateSpec, V1PolicyRule, V1Role, V1RoleBinding, V1RoleRef, V1Secret, V1SelfSubjectAccessReview, V1SelfSubjectAccessReviewSpec, V1Service, V1ServiceAccount, V1ServiceList, V1Subject, Watch } from '@kubernetes/client-node' +import { ApiextensionsV1beta1Api, ApisApi, AppsV1Api, AuthorizationV1Api, BatchV1Api, CoreV1Api, CustomObjectsApi, ExtensionsV1beta1Api, ExtensionsV1beta1IngressList, KubeConfig, Log, PortForward, RbacAuthorizationV1Api, V1beta1CustomResourceDefinition, V1ClusterRole, V1ClusterRoleBinding, V1ConfigMap, V1ConfigMapEnvSource, V1Container, V1ContainerStateTerminated, V1ContainerStateWaiting, V1Deployment, V1DeploymentList, V1DeploymentSpec, V1EnvFromSource, V1Job, V1JobSpec, V1LabelSelector, V1NamespaceList, V1ObjectMeta, V1PersistentVolumeClaimList, V1Pod, V1PodCondition, V1PodList, V1PodSpec, V1PodTemplateSpec, V1PolicyRule, V1Role, V1RoleBinding, V1RoleRef, V1Secret, V1SelfSubjectAccessReview, V1SelfSubjectAccessReviewSpec, V1Service, V1ServiceAccount, V1ServiceList, V1Subject, Watch } from '@kubernetes/client-node' import { Cluster, Context } from '@kubernetes/client-node/dist/config_types' import axios, { AxiosRequestConfig } from 'axios' import { cli } from 'cli-ux' @@ -635,6 +635,26 @@ export class KubeHelper { } } + async patchCheClusterCustomResource(name: string, namespace: string, patch: any): Promise { + const k8sCoreApi = KubeHelper.KUBE_CONFIG.makeApiClient(CustomObjectsApi) + + // It is required to patch content-type, otherwise request will be rejected with 415 (Unsupported media type) error. + const requestOptions = { + headers: { + 'content-type': 'application/merge-patch+json' + } + } + + try { + const res = await k8sCoreApi.patchNamespacedCustomObject('org.eclipse.che', 'v1', namespace, 'checlusters', name, patch, undefined, undefined, undefined, requestOptions) + if (res && res.body) { + return res.body + } + } catch (e) { + throw this.wrapK8sClientError(e) + } + } + async patchNamespacedPod(name: string, namespace: string, patch: any): Promise { const k8sCoreApi = KubeHelper.KUBE_CONFIG.makeApiClient(CoreV1Api) @@ -691,6 +711,26 @@ export class KubeHelper { } } + /** + * Returns pod last terminated state. + */ + async getPodLastTerminatedState(namespace: string, selector: string): Promise { + const pods = await this.getPodListByLabel(namespace, selector) + if (!pods.length) { + return + } + + for (const pod of pods) { + if (pod.status && pod.status.containerStatuses) { + for (const status of pod.status.containerStatuses) { + if (status.lastState) { + return status.lastState.terminated + } + } + } + } + } + async getPodCondition(namespace: string, selector: string, conditionType: string): Promise { const k8sCoreApi = KubeHelper.KUBE_CONFIG.makeApiClient(CoreV1Api) let res @@ -1336,8 +1376,8 @@ export class KubeHelper { } // override default values - if (ctx.CRPatch) { - merge(cheClusterCR, ctx.CRPatch) + if (ctx.crPatch) { + merge(cheClusterCR, ctx.crPatch) } // Back off some configuration properties(chectl estimated them like not working or not desired) @@ -1443,7 +1483,7 @@ export class KubeHelper { } } - async getAmoutUsers(): Promise { + async getUsersNumber(): Promise { const customObjectsApi = KubeHelper.KUBE_CONFIG.makeApiClient(CustomObjectsApi) let amountOfUsers: number try { @@ -1956,23 +1996,15 @@ export class KubeHelper { } async isOpenShift(): Promise { - const k8sApiApi = KubeHelper.KUBE_CONFIG.makeApiClient(ApisApi) - let res + const k8sCoreApi = KubeHelper.KUBE_CONFIG.makeApiClient(ApisApi) + try { - res = await k8sApiApi.getAPIVersions() + const res = await k8sCoreApi.getAPIVersions() + return res && res.body && res.body.groups && + res.body.groups.some(group => group.name === 'apps.openshift.io') } catch (e) { throw this.wrapK8sClientError(e) } - if (!res || !res.body) { - throw new Error('Get API versions returned an invalid response') - } - const v1APIGroupList = res.body - for (const v1APIGroup of v1APIGroupList.groups) { - if (v1APIGroup.name === 'apps.openshift.io') { - return true - } - } - return false } async getIngressHost(name = '', namespace = ''): Promise { @@ -2026,12 +2058,9 @@ export class KubeHelper { try { const res = await k8sCoreApi.getAPIVersions() - if (res && res.body && res.body.groups) { - return res.body.groups.some(group => group.name === 'route.openshift.io') - && res.body.groups.some(group => group.name === 'config.openshift.io') - } else { - return false - } + return res && res.body && res.body.groups && + res.body.groups.some(group => group.name === 'route.openshift.io') && + res.body.groups.some(group => group.name === 'config.openshift.io') } catch (e) { throw this.wrapK8sClientError(e) } diff --git a/src/api/openshift.ts b/src/api/openshift.ts index 7a61edd12..621b705a7 100644 --- a/src/api/openshift.ts +++ b/src/api/openshift.ts @@ -11,11 +11,12 @@ import execa = require('execa') export class OpenShiftHelper { - async status(): Promise { - const command = 'oc' - const args = ['status'] - const { exitCode } = await execa(command, args, { timeout: 60000, reject: false }) - if (exitCode === 0) { return true } else { return false } + /** + * Check status on existed `default` namespace. + */ + async isOpenShiftRunning(): Promise { + const { exitCode } = await execa('oc', ['status', '--namespace', 'default'], { timeout: 60000, reject: false }) + return exitCode === 0 } async getRouteHost(name: string, namespace = ''): Promise { const command = 'oc' diff --git a/src/api/version.ts b/src/api/version.ts index 882cb55cf..39fca262b 100644 --- a/src/api/version.ts +++ b/src/api/version.ts @@ -26,12 +26,11 @@ export namespace VersionHelper { export function getOpenShiftCheckVersionTask(flags: any): Listr.ListrTask { return { title: 'Check OpenShift version', - task: async (_ctx: any, task: any) => { - const kubeHelper = new KubeHelper(flags) + task: async (ctx: any, task: any) => { const actualVersion = await getOpenShiftVersion() if (actualVersion) { task.title = `${task.title}: ${actualVersion}.` - } else if (await kubeHelper.isOpenShift4()) { + } else if (ctx.isOpenShift4) { task.title = `${task.title}: 4.x` } else { task.title = `${task.title}: Unknown` diff --git a/src/commands/server/debug.ts b/src/commands/server/debug.ts index 81e3293d0..4d899e37f 100644 --- a/src/commands/server/debug.ts +++ b/src/commands/server/debug.ts @@ -15,6 +15,7 @@ import * as Listr from 'listr' import { cheNamespace, listrRenderer, skipKubeHealthzCheck } from '../../common-flags' import { CheTasks } from '../../tasks/che' import { ApiTasks } from '../../tasks/platforms/api' +import { initializeContext } from '../../util' export default class Debug extends Command { static description = 'Enable local debug of Eclipse Che server' @@ -32,7 +33,7 @@ export default class Debug extends Command { async run() { const { flags } = this.parse(Debug) - const ctx: any = {} + const ctx = await initializeContext(flags) const cheTasks = new CheTasks(flags) const apiTasks = new ApiTasks() diff --git a/src/commands/server/delete.ts b/src/commands/server/delete.ts index 879f7b341..a28c07c39 100644 --- a/src/commands/server/delete.ts +++ b/src/commands/server/delete.ts @@ -14,7 +14,7 @@ import { cli } from 'cli-ux' import * as Listrq from 'listr' import { KubeHelper } from '../../api/kube' -import { cheDeployment, cheNamespace, devWorkspaceControllerNamespace, listrRenderer, skipKubeHealthzCheck } from '../../common-flags' +import { assumeYes, cheDeployment, cheNamespace, devWorkspaceControllerNamespace, listrRenderer, skipKubeHealthzCheck } from '../../common-flags' import { CheTasks } from '../../tasks/che' import { DevWorkspaceTasks } from '../../tasks/component-installers/devfile-workspace-operator-installer' import { HelmTasks } from '../../tasks/installers/helm' @@ -39,17 +39,22 @@ export default class Delete extends Command { 'listr-renderer': listrRenderer, 'skip-deletion-check': boolean({ description: 'Skip user confirmation on deletion check', - default: false + default: false, + hidden: true, }), - 'skip-kubernetes-health-check': skipKubeHealthzCheck + 'skip-kubernetes-health-check': skipKubeHealthzCheck, + yes: assumeYes } async run() { - const ctx = initializeContext() const { flags } = this.parse(Delete) + const ctx = await initializeContext(flags) + if (flags['skip-deletion-check']) { + this.warn('\'--skip-deletion-check\' flag is deprecated, use \'--yes\' instead.') + flags.yes = flags['skip-deletion-check'] + } const notifier = require('node-notifier') - const apiTasks = new ApiTasks() const helmTasks = new HelmTasks(flags) const minishiftAddonTasks = new MinishiftAddonTasks() @@ -58,9 +63,7 @@ export default class Delete extends Command { const cheTasks = new CheTasks(flags) const devWorkspaceTasks = new DevWorkspaceTasks(flags) - let tasks = new Listrq(undefined, - { renderer: flags['listr-renderer'] as any } - ) + const tasks = new Listrq([], ctx.listrOptions) tasks.add(apiTasks.testApiTasks(flags, this)) tasks.add(operatorTasks.deleteTasks(flags)) @@ -74,23 +77,15 @@ export default class Delete extends Command { tasks.add(cheTasks.deleteNamespace(flags)) } - const cluster = KubeHelper.KUBE_CONFIG.getCurrentCluster() - if (!cluster) { - throw new Error('Failed to get current Kubernetes cluster. Check if the current context is set via kubect/oc') - } - - if (!flags['skip-deletion-check']) { - const confirmed = await cli.confirm(`You're going to remove Eclipse Che server in namespace '${flags.chenamespace}' on server '${cluster ? cluster.server : ''}'. If you want to continue - press Y`) - if (!confirmed) { - this.exit(0) + if (await this.isDeletionConfirmed(flags)) { + try { + await tasks.run() + cli.log(getCommandSuccessMessage(this, ctx)) + } catch (error) { + cli.error(error) } - } - - try { - await tasks.run() - cli.log(getCommandSuccessMessage(this, ctx)) - } catch (error) { - cli.error(error) + } else { + this.exit(0) } notifier.notify({ @@ -100,4 +95,17 @@ export default class Delete extends Command { this.exit(0) } + + async isDeletionConfirmed(flags: any): Promise { + const cluster = KubeHelper.KUBE_CONFIG.getCurrentCluster() + if (!cluster) { + throw new Error('Failed to get current Kubernetes cluster. Check if the current context is set via kubectl/oc') + } + + if (!flags.yes) { + return cli.confirm(`You're going to remove Eclipse Che server in namespace '${flags.chenamespace}' on server '${cluster ? cluster.server : ''}'. If you want to continue - press Y`) + } + + return true + } } diff --git a/src/commands/server/deploy.ts b/src/commands/server/deploy.ts index bb838f7a3..3745635a0 100644 --- a/src/commands/server/deploy.ts +++ b/src/commands/server/deploy.ts @@ -27,7 +27,7 @@ import { InstallerTasks } from '../../tasks/installers/installer' import { ApiTasks } from '../../tasks/platforms/api' import { CommonPlatformTasks } from '../../tasks/platforms/common-platform-tasks' import { PlatformTasks } from '../../tasks/platforms/platform' -import { getCommandSuccessMessage, initializeContext, isOpenshiftPlatformFamily, readCRFile } from '../../util' +import { getCommandSuccessMessage, initializeContext, isOpenshiftPlatformFamily } from '../../util' export default class Deploy extends Command { static description = 'start Eclipse Che server' @@ -83,11 +83,13 @@ export default class Deploy extends Command { To provide own certificate for Kubernetes infrastructure, 'che-tls' secret with TLS certificate must be pre-created in the configured namespace. In case of providing own self-signed certificate 'self-signed-certificate' secret should be also created. For OpenShift, router will use default cluster certificates. - Please see the docs how to deploy Eclipse Che on different infrastructures: ${DOCS_LINK_INSTALL_RUNNING_CHE_LOCALLY}` + Please see the docs how to deploy Eclipse Che on different infrastructures: ${DOCS_LINK_INSTALL_RUNNING_CHE_LOCALLY}`, + hidden: true }), 'self-signed-cert': flags.boolean({ description: 'Deprecated. The flag is ignored. Self signed certificates usage is autodetected now.', - default: false + default: false, + hidden: true }), platform: string({ char: 'p', @@ -192,14 +194,14 @@ export default class Deploy extends Command { default: DEFAULT_DEV_WORKSPACE_CONTROLLER_IMAGE, env: 'DEV_WORKSPACE_OPERATOR_IMAGE', }), - 'dev-workspace-controller-namespace': devWorkspaceControllerNamespace + 'dev-workspace-controller-namespace': devWorkspaceControllerNamespace, } async setPlaformDefaults(flags: any, ctx: any): Promise { flags.tls = await this.checkTlsMode(ctx) if (!flags.installer) { - await this.setDefaultInstaller(flags) + await this.setDefaultInstaller(flags, ctx) cli.info(`› Installer type is set to: '${flags.installer}'`) } @@ -240,7 +242,7 @@ export default class Deploy extends Command { * Returns true if TLS is enabled (or omitted) and false if it is explicitly disabled. */ async checkTlsMode(ctx: any): Promise { - const crPatch = ctx.CRPatch + const crPatch = ctx.crPatch if (crPatch && crPatch.spec && crPatch.spec.server && crPatch.spec.server.tlsSupport === false) { return false } @@ -330,12 +332,8 @@ export default class Deploy extends Command { } const { flags } = this.parse(Deploy) - const ctx = initializeContext() + const ctx = await initializeContext(flags) ctx.directory = path.resolve(flags.directory ? flags.directory : path.resolve(os.tmpdir(), 'chectl-logs', Date.now().toString())) - const listrOptions: Listr.ListrOptions = { renderer: (flags['listr-renderer'] as any), collapse: false, showSubtasks: true } as Listr.ListrOptions - ctx.listrOptions = listrOptions - ctx.customCR = readCRFile(flags, CHE_OPERATOR_CR_YAML_KEY , this) - ctx.CRPatch = readCRFile(flags, CHE_OPERATOR_CR_PATCH_YAML_KEY, this) if (flags['self-signed-cert']) { this.warn('"self-signed-cert" flag is deprecated and has no effect. Autodetection is used instead.') @@ -348,11 +346,11 @@ export default class Deploy extends Command { const devWorkspaceTasks = new DevWorkspaceTasks(flags) // Platform Checks - let platformCheckTasks = new Listr(platformTasks.preflightCheckTasks(flags, this), listrOptions) + let platformCheckTasks = new Listr(platformTasks.preflightCheckTasks(flags, this), ctx.listrOptions) platformCheckTasks.add(CommonPlatformTasks.oAuthProvidersExists(flags)) // Checks if Eclipse Che is already deployed - let preInstallTasks = new Listr(undefined, listrOptions) + let preInstallTasks = new Listr(undefined, ctx.listrOptions) preInstallTasks.add(apiTasks.testApiTasks(flags, this)) preInstallTasks.add({ title: '👀 Looking for an already existing Eclipse Che instance', @@ -360,12 +358,12 @@ export default class Deploy extends Command { }) await this.setPlaformDefaults(flags, ctx) - let installTasks = new Listr(installerTasks.installTasks(flags, this), listrOptions) + let installTasks = new Listr(installerTasks.installTasks(flags, this), ctx.listrOptions) const startDeployedCheTasks = new Listr([{ title: '👀 Starting already deployed Eclipse Che', task: () => new Listr(cheTasks.scaleCheUpTasks(this)) - }], listrOptions) + }], ctx.listrOptions) // Post Install Checks const postInstallTasks = new Listr([ @@ -383,17 +381,17 @@ export default class Deploy extends Command { retrieveCheCaCertificateTask(flags), ...cheTasks.preparePostInstallationOutput(flags), getPrintHighlightedMessagesTask(), - ], listrOptions) + ], ctx.listrOptions) const logsTasks = new Listr([{ title: 'Start following logs', task: () => new Listr(cheTasks.serverLogsTasks(flags, true)) - }], listrOptions) + }], ctx.listrOptions) const eventTasks = new Listr([{ title: 'Start following events', task: () => new Listr(cheTasks.namespaceEventsTask(flags.chenamespace, this, true)) - }], listrOptions) + }], ctx.listrOptions) try { await preInstallTasks.run(ctx) @@ -421,9 +419,9 @@ export default class Deploy extends Command { } catch (err) { const isDirEmpty = await this.isDirEmpty(ctx.directory) if (isDirEmpty) { - this.error(`${err}\nInstallation failed. There are no available logs.`) + this.error(`${err}\nInstallation failed. Error log: ${this.config.errlog}`) } - this.error(`${err}\nInstallation failed, check logs in '${ctx.directory}'`) + this.error(`${err}\nInstallation failed. Error log: ${this.config.errlog}. Eclipse Che logs: ${ctx.directory}`) } notifier.notify({ @@ -438,7 +436,7 @@ export default class Deploy extends Command { * Sets default installer which is `olm` for OpenShift 4 with stable version of chectl * and `operator` for other cases. */ - async setDefaultInstaller(flags: any): Promise { + async setDefaultInstaller(flags: any, ctx: any): Promise { const kubeHelper = new KubeHelper(flags) const isOlmPreinstalled = await kubeHelper.isPreInstalledOLM() @@ -447,7 +445,7 @@ export default class Deploy extends Command { return } - if (flags.platform === 'openshift' && await kubeHelper.isOpenShift4() && isOlmPreinstalled) { + if (flags.platform === 'openshift' && ctx.isOpenShift4 && isOlmPreinstalled) { flags.installer = 'olm' } else { flags.installer = 'operator' diff --git a/src/commands/server/logs.ts b/src/commands/server/logs.ts index 83db2f1fb..b4c3e1b12 100644 --- a/src/commands/server/logs.ts +++ b/src/commands/server/logs.ts @@ -18,6 +18,7 @@ import * as path from 'path' import { cheDeployment, cheNamespace, listrRenderer, skipKubeHealthzCheck } from '../../common-flags' import { CheTasks } from '../../tasks/che' import { ApiTasks } from '../../tasks/platforms/api' +import { initializeContext } from '../../util' export default class Logs extends Command { static description = 'Collect Eclipse Che logs' @@ -37,7 +38,7 @@ export default class Logs extends Command { async run() { const { flags } = this.parse(Logs) - const ctx: any = {} + const ctx = await initializeContext(flags) ctx.directory = path.resolve(flags.directory ? flags.directory : path.resolve(os.tmpdir(), 'chectl-logs', Date.now().toString())) const cheTasks = new CheTasks(flags) const apiTasks = new ApiTasks() diff --git a/src/commands/server/status.ts b/src/commands/server/status.ts index 41150c6ea..fb39bd15d 100644 --- a/src/commands/server/status.ts +++ b/src/commands/server/status.ts @@ -16,8 +16,9 @@ import { CheHelper } from '../../api/che' import { KubeHelper } from '../../api/kube' import { VersionHelper } from '../../api/version' import { cheNamespace } from '../../common-flags' +import { initializeContext } from '../../util' -export default class List extends Command { +export default class Status extends Command { // Implementation-Version it is a property from Manifest.ml inside of che server pod which indicate Eclipse Che build version. static description = 'status Eclipse Che server' @@ -27,14 +28,15 @@ export default class List extends Command { } async run() { - const { flags } = this.parse(List) + const { flags } = this.parse(Status) + const ctx = await initializeContext(flags) const kube = new KubeHelper(flags) const che = new CheHelper(flags) let openshiftOauth = 'No' const cr = await kube.getCheCluster(flags.chenamespace) - if (cr && cr.spec && cr.spec.auth && cr.spec.auth.openShiftoAuth) { + if (ctx.isOpenShift && cr && cr.spec && cr.spec.auth && cr.spec.auth.openShiftoAuth) { openshiftOauth = 'Yes' } diff --git a/src/commands/server/stop.ts b/src/commands/server/stop.ts index 115d31436..6d71aab18 100644 --- a/src/commands/server/stop.ts +++ b/src/commands/server/stop.ts @@ -36,8 +36,8 @@ export default class Stop extends Command { } async run() { - const ctx = initializeContext() const { flags } = this.parse(Stop) + const ctx = await initializeContext(flags) const Listr = require('listr') const notifier = require('node-notifier') const cheTasks = new CheTasks(flags) diff --git a/src/commands/server/update.ts b/src/commands/server/update.ts index 3c641e86b..6cd71c206 100644 --- a/src/commands/server/update.ts +++ b/src/commands/server/update.ts @@ -9,7 +9,7 @@ **********************************************************************/ import { Command, flags } from '@oclif/command' -import { boolean, string } from '@oclif/parser/lib/flags' +import { string } from '@oclif/parser/lib/flags' import { cli } from 'cli-ux' import * as fs from 'fs-extra' import * as Listr from 'listr' @@ -17,15 +17,13 @@ import * as notifier from 'node-notifier' import * as path from 'path' import { KubeHelper } from '../../api/kube' -import { cheDeployment, cheNamespace, cheOperatorCRPatchYaml, CHE_OPERATOR_CR_PATCH_YAML_KEY, listrRenderer, skipKubeHealthzCheck } from '../../common-flags' +import { assumeYes, cheDeployment, cheNamespace, cheOperatorCRPatchYaml, CHE_OPERATOR_CR_PATCH_YAML_KEY, listrRenderer, skipKubeHealthzCheck } from '../../common-flags' import { DEFAULT_CHE_OPERATOR_IMAGE, SUBSCRIPTION_NAME } from '../../constants' -import { CheTasks } from '../../tasks/che' import { getPrintHighlightedMessagesTask } from '../../tasks/installers/common-tasks' import { InstallerTasks } from '../../tasks/installers/installer' import { ApiTasks } from '../../tasks/platforms/api' import { CommonPlatformTasks } from '../../tasks/platforms/common-platform-tasks' -import { PlatformTasks } from '../../tasks/platforms/platform' -import { getCommandSuccessMessage, getImageTag, initializeContext, isKubernetesPlatformFamily, readCRFile } from '../../util' +import { getCommandSuccessMessage, getImageTag, initializeContext } from '../../util' export default class Update extends Command { static description = 'Update Eclipse Che server.' @@ -35,11 +33,13 @@ export default class Update extends Command { char: 'a', description: 'Installer type. If not set, default is autodetected depending on previous installation.', options: ['operator', 'olm'], + hidden: true, }), platform: string({ char: 'p', description: 'Type of Kubernetes platform. Valid values are \"minikube\", \"minishift\", \"k8s (for kubernetes)\", \"openshift\", \"crc (for CodeReady Containers)\", \"microk8s\".', options: ['minikube', 'minishift', 'k8s', 'openshift', 'microk8s', 'docker-desktop', 'crc'], + hidden: true, }), chenamespace: cheNamespace, templates: string({ @@ -52,13 +52,15 @@ export default class Update extends Command { description: 'Container image of the operator. This parameter is used only when the installer is the operator', default: DEFAULT_CHE_OPERATOR_IMAGE }), - 'skip-version-check': boolean({ - description: 'Skip user confirmation on version check', - default: false + 'skip-version-check': flags.boolean({ + description: 'Skip minimal versions check.', + default: false, + hidden: true, }), 'deployment-name': cheDeployment, 'listr-renderer': listrRenderer, 'skip-kubernetes-health-check': skipKubeHealthzCheck, + yes: assumeYes, help: flags.help({ char: 'h' }), [CHE_OPERATOR_CR_PATCH_YAML_KEY]: cheOperatorCRPatchYaml, } @@ -75,108 +77,75 @@ export default class Update extends Command { return path.join(__dirname, '../../../templates') } - async checkIfInstallerSupportUpdating(flags: any) { + async run() { + const { flags } = this.parse(Update) + const ctx = await initializeContext(flags) + await this.setDomainFlag(flags) if (!flags.installer) { await this.setDefaultInstaller(flags) cli.info(`› Installer type is set to: '${flags.installer}'`) } - if (flags.installer === 'operator' || flags.installer === 'olm') { - // operator already supports updating - return - } - - if (flags.installer === 'olm' && flags.platform === 'minishift') { - this.error(`🛑 The specified installer ${flags.installer} does not support Minishift`) - } - } - - async run() { - const { flags } = this.parse(Update) - const ctx = initializeContext() - const listrOptions: Listr.ListrOptions = { renderer: (flags['listr-renderer'] as any), collapse: false } as Listr.ListrOptions - ctx.listrOptions = listrOptions - ctx.CRPatch = readCRFile(flags, CHE_OPERATOR_CR_PATCH_YAML_KEY, this) - - const cheTasks = new CheTasks(flags) const kubeHelper = new KubeHelper(flags) - const platformTasks = new PlatformTasks() const installerTasks = new InstallerTasks() - const apiTasks = new ApiTasks() - // Platform Checks - const platformCheckTasks = new Listr(platformTasks.preflightCheckTasks(flags, this), listrOptions) - platformCheckTasks.add(CommonPlatformTasks.oAuthProvidersExists(flags)) - - await this.checkIfInstallerSupportUpdating(flags) - - // Checks if Eclipse Che is already deployed - let preInstallTasks = new Listr(undefined, listrOptions) - preInstallTasks.add(apiTasks.testApiTasks(flags, this)) - preInstallTasks.add({ - title: '👀 Looking for an already existing Eclipse Che instance', - task: () => new Listr(cheTasks.checkIfCheIsInstalledTasks(flags, this)) - }) - - const preUpdateTasks = new Listr(installerTasks.preUpdateTasks(flags, this), listrOptions) + // pre update tasks + const apiTasks = new ApiTasks() + const preUpdateTasks = new Listr([], ctx.listrOptions) + preUpdateTasks.add(apiTasks.testApiTasks(flags, this)) + preUpdateTasks.add(CommonPlatformTasks.oAuthProvidersExists(flags)) + preUpdateTasks.add(installerTasks.preUpdateTasks(flags, this)) - const updateTasks = new Listr(undefined, listrOptions) + // update tasks + const updateTasks = new Listr([], ctx.listrOptions) updateTasks.add({ title: '↺ Updating...', task: () => new Listr(installerTasks.updateTasks(flags, this)) }) - const postUpdateTasks = new Listr(undefined, listrOptions) + // post update tasks + const postUpdateTasks = new Listr([], ctx.listrOptions) postUpdateTasks.add(getPrintHighlightedMessagesTask()) try { - await preInstallTasks.run(ctx) + await preUpdateTasks.run(ctx) - if (!ctx.isCheDeployed) { - this.error('Eclipse Che deployment is not found. Use `chectl server:deploy` to initiate a new deployment.') - } else { - if (isKubernetesPlatformFamily(flags.platform!)) { - await this.setDomainFlag(flags) + if (!flags.assumeYes) { + cli.info(`Existed Eclipse Che operator: ${ctx.deployedCheOperatorImage}:${ctx.deployedCheOperatorTag}.`) + cli.info(`New Eclipse Che operator : ${ctx.newCheOperatorImage}:${ctx.newCheOperatorTag}.`) + + 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.') } - await platformCheckTasks.run(ctx) - await preUpdateTasks.run(ctx) - - if (!flags['skip-version-check']) { - cli.info(`Existed Eclipse Che operator: ${ctx.deployedCheOperatorImage}:${ctx.deployedCheOperatorTag}.`) - cli.info(`New Eclipse Che operator : ${ctx.newCheOperatorImage}:${ctx.newCheOperatorTag}.`) - - 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 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}' + + 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}]`) - } - - const confirmed = await cli.confirm('If you want to continue - press Y') - if (!confirmed) { - this.exit(0) - } + 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}]`) } - await updateTasks.run(ctx) - await postUpdateTasks.run(ctx) + const confirmed = await cli.confirm('If you want to continue - press Y') + if (!confirmed) { + this.exit(0) + } } + + await updateTasks.run(ctx) + await postUpdateTasks.run(ctx) + this.log(getCommandSuccessMessage(this, ctx)) } catch (err) { this.error(err) @@ -190,7 +159,10 @@ export default class Update extends Command { this.exit(0) } - async setDomainFlag(flags: any): Promise { + /** + * Copies spec.k8s.ingressDomain. It is needed later for updates. + */ + private async setDomainFlag(flags: any): Promise { const kubeHelper = new KubeHelper(flags) const cheCluster = await kubeHelper.getCheCluster(flags.chenamespace) if (cheCluster && cheCluster.spec.k8s && cheCluster.spec.k8s.ingressDomain) { @@ -198,7 +170,10 @@ export default class Update extends Command { } } - async setDefaultInstaller(flags: any): Promise { + /** + * Sets installer type depending on the previous installation. + */ + private async setDefaultInstaller(flags: any): Promise { const kubeHelper = new KubeHelper(flags) try { await kubeHelper.getOperatorSubscription(SUBSCRIPTION_NAME, flags.chenamespace) diff --git a/src/common-flags.ts b/src/common-flags.ts index 418437a6f..8c95276e0 100644 --- a/src/common-flags.ts +++ b/src/common-flags.ts @@ -33,7 +33,8 @@ export const cheDeployment = string({ export const listrRenderer = string({ description: 'Listr renderer', options: ['default', 'silent', 'verbose'], - default: 'default' + default: 'default', + hidden: true, }) export const ACCESS_TOKEN_KEY = 'access-token' @@ -57,7 +58,14 @@ export const cheApiEndpoint = string({ export const CHE_OPERATOR_CR_PATCH_YAML_KEY = 'che-operator-cr-patch-yaml' export const cheOperatorCRPatchYaml = string({ description: 'Path to a yaml file that overrides the default values in CheCluster CR used by the operator. This parameter is used only when the installer is the \'operator\' or the \'olm\'.', - default: '' + default: '', +}) + +export const assumeYes = boolean({ + description: 'Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively', + char: 'y', + default: false, + required: false, }) export const CHE_OPERATOR_CR_YAML_KEY = 'che-operator-cr-yaml' diff --git a/src/constants.ts b/src/constants.ts index ef599034f..d74ffd7df 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,10 +8,10 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ + // images export const DEFAULT_CHE_IMAGE = 'quay.io/eclipse/che-server:nightly' export const DEFAULT_CHE_OPERATOR_IMAGE = 'quay.io/eclipse/che-operator:nightly' export const DEFAULT_DEV_WORKSPACE_CONTROLLER_IMAGE = 'quay.io/devfile/devworkspace-controller:next' - // This image should be updated manually when needed. // Repository location: https://github.com/che-dockerfiles/che-cert-manager-ca-cert-generator-image export const CA_CERT_GENERATION_JOB_IMAGE = 'quay.io/eclipse/che-cert-manager-ca-cert-generator:671342c' @@ -23,6 +23,10 @@ export const DEFAULT_CA_CERT_FILE_NAME = 'cheCA.crt' export const CHE_CLUSTER_CR_NAME = 'eclipse-che' export const CHE_CLUSTER_CRD = 'checlusters.org.eclipse.che' +// operator +export const OPERATOR_DEPLOYMENT_NAME = 'che-operator' +export const CHE_OPERATOR_SELECTOR = 'app=che-operator' + // OLM export const DEFAULT_CHE_OLM_PACKAGE_NAME = 'eclipse-che' export const OLM_STABLE_CHANNEL_NAME = 'stable' @@ -47,5 +51,7 @@ export const DOCS_LINK_HOW_TO_CREATE_USER_OS3 = 'https://docs.openshift.com/cont export const DOC_LINK_OBTAIN_ACCESS_TOKEN = 'https://www.eclipse.org/che/docs/che-7/administration-guide/authenticating-users/#obtaining-the-token-from-keycloak_authenticating-to-the-che-server' export const DOC_LINK_OBTAIN_ACCESS_TOKEN_OAUTH = 'https://www.eclipse.org/che/docs/che-7/administration-guide/authenticating-users/#obtaining-the-token-from-openshift-token-through-keycloak_authenticating-to-the-che-server' +export const OUTPUT_SEPARATOR = '-------------------------------------------------------------------------------' + // DevWorkspace export const DEFAULT_DEV_WORKSPACE_CONTROLLER_NAMESPACE = 'devworkspace-controller' diff --git a/src/tasks/che.ts b/src/tasks/che.ts index 4f5fa41c8..2926a7c99 100644 --- a/src/tasks/che.ts +++ b/src/tasks/che.ts @@ -16,9 +16,8 @@ import { CheServerLoginManager } from '../api/che-login-manager' import { KubeHelper } from '../api/kube' import { OpenShiftHelper } from '../api/openshift' import { VersionHelper } from '../api/version' -import { DOC_LINK, DOC_LINK_RELEASE_NOTES } from '../constants' +import { CHE_OPERATOR_SELECTOR, DOC_LINK, DOC_LINK_RELEASE_NOTES, OUTPUT_SEPARATOR } from '../constants' -import { OperatorTasks } from './installers/operator' import { KubeTasks } from './kube' /** @@ -528,7 +527,7 @@ export class CheTasks { title: `${follow ? 'Start following' : 'Read'} Operator logs`, skip: () => flags.installer !== 'operator' && flags.installer !== 'olm', task: async (ctx: any, task: any) => { - await this.che.readPodLog(flags.chenamespace, OperatorTasks.CHE_OPERATOR_SELECTOR, ctx.directory, follow) + await this.che.readPodLog(flags.chenamespace, CHE_OPERATOR_SELECTOR, ctx.directory, follow) task.title = `${task.title}...done` } }, @@ -629,7 +628,7 @@ export class CheTasks { if (DOC_LINK_RELEASE_NOTES) { messages.push(`Release Notes : ${DOC_LINK_RELEASE_NOTES}`) } - messages.push('') + messages.push(OUTPUT_SEPARATOR) const cheUrl = await this.che.cheURL(flags.chenamespace) messages.push(`Users Dashboard : ${cheUrl}`) @@ -637,7 +636,7 @@ export class CheTasks { if (cheCluster && cheCluster.spec.auth && cheCluster.spec.auth.updateAdminPassword) { messages.push('Admin user login : "admin:admin". NOTE: must change after first login.') } - messages.push('') + messages.push(OUTPUT_SEPARATOR) const cheConfigMap = await this.kube.getConfigMap('che', flags.chenamespace) if (cheConfigMap && cheConfigMap.data) { @@ -647,7 +646,7 @@ export class CheTasks { if (cheConfigMap.data.CHE_WORKSPACE_DEVFILE__REGISTRY__URL) { messages.push(`Devfile Registry : ${cheConfigMap.data.CHE_WORKSPACE_DEVFILE__REGISTRY__URL}`) } - messages.push('') + messages.push(OUTPUT_SEPARATOR) if (cheConfigMap.data.CHE_KEYCLOAK_AUTH__SERVER__URL) { messages.push(`Identity Provider URL : ${cheConfigMap.data.CHE_KEYCLOAK_AUTH__SERVER__URL}`) @@ -655,7 +654,7 @@ export class CheTasks { if (ctx.identityProviderUsername && ctx.identityProviderPassword) { messages.push(`Identity Provider login : "${ctx.identityProviderUsername}:${ctx.identityProviderPassword}".`) } - messages.push('') + messages.push(OUTPUT_SEPARATOR) } ctx.highlightedMessages = messages.concat(ctx.highlightedMessages) task.title = `${task.title}...done` diff --git a/src/tasks/component-installers/devfile-workspace-operator-installer.ts b/src/tasks/component-installers/devfile-workspace-operator-installer.ts index faa6ec9d5..ddfc55f8b 100644 --- a/src/tasks/component-installers/devfile-workspace-operator-installer.ts +++ b/src/tasks/component-installers/devfile-workspace-operator-installer.ts @@ -161,7 +161,7 @@ export class DevWorkspaceTasks { }, { title: 'Create dev workspace controller ConfigMap', - task: async (_ctx: any, task: any) => { + task: async (ctx: any, task: any) => { const yamlConfigFile = path.join(this.getTemplatePath(), 'controller_config.yaml') const rawYaml = await fs.readFile(yamlConfigFile, 'utf-8') const configMapYaml: any = yaml.safeLoad(rawYaml) @@ -176,8 +176,7 @@ export class DevWorkspaceTasks { let webHooksValue = 'false' let routingClass = 'basic' - const isOpenShift = await this.kubeHelper.isOpenShift() - if (isOpenShift) { + if (ctx.isOpenShift) { routingClass = 'openshift-oauth' webHooksValue = 'true' } @@ -197,15 +196,14 @@ export class DevWorkspaceTasks { }, { title: 'Create dev workspace controller', - task: async (_ctx: any, task: any) => { + task: async (ctx: any, task: any) => { const exists = await this.kubeHelper.deploymentExist('devworkspace-controller', this.getNamespace()) if (exists) { task.title = `${task.title}...It already exists.` return } - const isOpenShift = await this.kubeHelper.isOpenShift() const yamls: any[] = [] - if (isOpenShift) { + if (ctx.isOpenShift) { const yamlControllerFile = path.join(this.getTemplatePath(), 'os', 'controller.yaml') const rawYaml = await fs.readFile(yamlControllerFile, 'utf-8') yaml.safeLoadAll(rawYaml, yaml => { diff --git a/src/tasks/installers/common-tasks.ts b/src/tasks/installers/common-tasks.ts index bb6411e51..77ccdaf8d 100644 --- a/src/tasks/installers/common-tasks.ts +++ b/src/tasks/installers/common-tasks.ts @@ -102,7 +102,7 @@ export function updateEclipseCheCluster(flags: any, kube: KubeHelper, command: C return { title: `Update the Custom Resource of type ${CHE_CLUSTER_CRD} in the namespace ${flags.chenamespace}`, task: async (ctx: any, task: any) => { - let crPatch: any = ctx.CRPatch || {} + let crPatch: any = ctx.crPatch || {} const cheCluster = await kube.getCheCluster(flags.chenamespace) if (!cheCluster) { diff --git a/src/tasks/installers/minishift-addon.ts b/src/tasks/installers/minishift-addon.ts index 2ee8489c8..685756559 100644 --- a/src/tasks/installers/minishift-addon.ts +++ b/src/tasks/installers/minishift-addon.ts @@ -124,7 +124,7 @@ export class MinishiftAddonTasks { private async checkLogged(command: Command) { const openshiftHelper = new OpenShiftHelper() - const ok = await openshiftHelper.status() + const ok = await openshiftHelper.isOpenShiftRunning() if (!ok) { command.error('Not logged with OC tool. Please log-in with oc login command') } diff --git a/src/tasks/installers/operator.ts b/src/tasks/installers/operator.ts index 5d2a47649..bc3e116f0 100644 --- a/src/tasks/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -15,22 +15,19 @@ import * as yaml from 'js-yaml' import * as Listr from 'listr' import { KubeHelper } from '../../api/kube' -import { CHE_CLUSTER_CRD } from '../../constants' +import { CHE_CLUSTER_CRD, CHE_OPERATOR_SELECTOR, OPERATOR_DEPLOYMENT_NAME } from '../../constants' import { isStableVersion } from '../../util' import { KubeTasks } from '../kube' import { copyOperatorResources, createEclipseCheCluster, createNamespaceTask, updateEclipseCheCluster } from './common-tasks' export class OperatorTasks { - public static CHE_OPERATOR_SELECTOR = 'app=che-operator' - operatorServiceAccount = 'che-operator' operatorRole = 'che-operator' operatorClusterRole = 'che-operator' operatorRoleBinding = 'che-operator' operatorClusterRoleBinding = 'che-operator' cheClusterCrd = 'checlusters.org.eclipse.che' - operatorName = 'che-operator' /** * Returns tasks list which perform preflight platform checks. @@ -142,9 +139,9 @@ export class OperatorTasks { } }, { - title: `Create deployment ${this.operatorName} in namespace ${flags.chenamespace}`, + title: `Create deployment ${OPERATOR_DEPLOYMENT_NAME} in namespace ${flags.chenamespace}`, task: async (ctx: any, task: any) => { - const exist = await kube.deploymentExist(this.operatorName, flags.chenamespace) + const exist = await kube.deploymentExist(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) if (exist) { task.title = `${task.title}...It already exists.` } else { @@ -155,7 +152,7 @@ export class OperatorTasks { }, { title: 'Operator pod bootstrap', - task: () => kubeTasks.podStartTasks(OperatorTasks.CHE_OPERATOR_SELECTOR, flags.chenamespace) + task: () => kubeTasks.podStartTasks(CHE_OPERATOR_SELECTOR, flags.chenamespace) }, { title: 'Prepare Eclipse Che cluster CR', @@ -184,10 +181,9 @@ export class OperatorTasks { { title: 'Checking versions compatibility before updating', task: async (ctx: any, _task: any) => { - const operatorDeployment = await kube.getDeployment(this.operatorName, flags.chenamespace) + const operatorDeployment = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) if (!operatorDeployment) { - command.error(`${this.operatorName} deployment is not found in namespace ${flags.chenamespace}.\nProbably Eclipse Che was initially deployed with another installer`) - return + command.error(`${OPERATOR_DEPLOYMENT_NAME} deployment is not found in namespace ${flags.chenamespace}.\nProbably Eclipse Che was initially deployed with another installer`) } const deployedCheOperator = this.retrieveContainerImage(operatorDeployment) const deployedCheOperatorImageAndTag = deployedCheOperator.split(':', 2) @@ -327,9 +323,9 @@ export class OperatorTasks { } }, { - title: `Updating deployment ${this.operatorName} in namespace ${flags.chenamespace}`, + title: `Updating deployment ${OPERATOR_DEPLOYMENT_NAME} in namespace ${flags.chenamespace}`, task: async (ctx: any, task: any) => { - const exist = await kube.deploymentExist(this.operatorName, flags.chenamespace) + const exist = await kube.deploymentExist(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) if (exist) { await kube.replaceDeploymentFromFile(ctx.resourcesPath + 'operator.yaml', flags.chenamespace, flags['che-operator-image']) task.title = `${task.title}...updated.` @@ -343,7 +339,7 @@ export class OperatorTasks { title: 'Waiting newer operator to be run', task: async (_ctx: any, _task: any) => { await cli.wait(1000) - await kube.waitLatestReplica(this.operatorName, flags.chenamespace) + await kube.waitLatestReplica(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) } }, updateEclipseCheCluster(flags, kube, command), @@ -371,6 +367,11 @@ export class OperatorTasks { { title: `Delete the Custom Resource of type ${CHE_CLUSTER_CRD}`, task: async (_ctx: any, task: any) => { + const checluster = await kh.getCheCluster(flags.chenamespace) + if (checluster) { + await kh.patchCheClusterCustomResource(checluster.metadata.name, flags.chenamespace, { metadata: { finalizers: null } }) + } + await kh.deleteCheCluster(flags.chenamespace) do { await cli.wait(2000) //wait a couple of secs for the finalizers to be executed diff --git a/src/tasks/kube.ts b/src/tasks/kube.ts index b3b6a546e..61521c588 100644 --- a/src/tasks/kube.ts +++ b/src/tasks/kube.ts @@ -79,6 +79,12 @@ export class KubeTasks { throw new Error(`Failed to start a pod, reason: ${failedState.reason}, message: ${failedState.message}`) } + const terminatedState = await this.kubeHelper.getPodLastTerminatedState(namespace, selector) + if (terminatedState) { + task.title = `${task.title}...failed` + throw new Error(`Failed to start a pod, reason: ${terminatedState.reason}, message: ${terminatedState.message}`) + } + const allStarted = await this.isPodConditionStatusPassed(namespace, selector, 'Ready') if (allStarted) { task.title = `${task.title}...done.` @@ -88,7 +94,7 @@ export class KubeTasks { await cli.wait(500) } - throw new Error(`Failed to download image: ${await this.getTimeOutErrorMessage(namespace, selector)}`) + throw new Error(`Failed to start a pod: ${await this.getTimeOutErrorMessage(namespace, selector)}`) } } ]) diff --git a/src/tasks/platforms/api.ts b/src/tasks/platforms/api.ts index 5926523b4..015e32b90 100644 --- a/src/tasks/platforms/api.ts +++ b/src/tasks/platforms/api.ts @@ -26,11 +26,10 @@ export class ApiTasks { task: async (ctx: any, task: any) => { try { await kube.checkKubeApi() - task.title = await `${task.title}...OK` + task.title = `${task.title}...OK` - ctx.isOpenShift = await kube.isOpenShift() if (ctx.isOpenShift) { - task.title = await `${task.title} (it's OpenShift)` + task.title = `${task.title} (it's OpenShift)` } } catch (error) { command.error(`Failed to connect to Kubernetes API, error: ${error.message}. If you're sure that your Kubernetes cluster is healthy - you can skip this check with '--skip-kubernetes-health-check' flag.`) diff --git a/src/tasks/platforms/common-platform-tasks.ts b/src/tasks/platforms/common-platform-tasks.ts index 44c543ed2..2617323e0 100644 --- a/src/tasks/platforms/common-platform-tasks.ts +++ b/src/tasks/platforms/common-platform-tasks.ts @@ -15,7 +15,6 @@ import * as Listr from 'listr' import { KubeHelper } from '../../api/kube' import { DOCS_LINK_HOW_TO_ADD_IDENTITY_PROVIDER_OS4, DOCS_LINK_HOW_TO_CREATE_USER_OS3 } from '../../constants' -import { isOpenshiftPlatformFamily } from '../../util' export namespace CommonPlatformTasks { /** @@ -81,16 +80,16 @@ export namespace CommonPlatformTasks { let kube = new KubeHelper(flags) return { title: 'Verify Openshift oauth.', - enabled: ctx => isOpenshiftPlatformFamily(flags.platform) && isOAuthEnabled(ctx), + enabled: (ctx: any) => ctx.isOpenShift && isOAuthEnabled(ctx), task: async (ctx: any, task: any) => { - if (await kube.isOpenShift4()) { + if (ctx.isOpenShift4) { const providers = await kube.getOpenshiftAuthProviders() if (!providers || providers.length === 0) { ctx.highlightedMessages.push(`❗ ${ansi.yellow('[WARNING]')} OpenShift OAuth is turned off, because there is no any identity providers configured. ${DOCS_LINK_HOW_TO_ADD_IDENTITY_PROVIDER_OS4}`) ctx.CROverrides = { spec: { auth: { openShiftoAuth: false } } } } } else { - if (await kube.getAmoutUsers() === 0) { + if (await kube.getUsersNumber() === 0) { ctx.highlightedMessages.push(`❗ ${ansi.yellow('[WARNING]')} OpenShift OAuth is turned off, because there are no any users added. See: "${DOCS_LINK_HOW_TO_CREATE_USER_OS3}"`) ctx.CROverrides = { spec: { auth: { openShiftoAuth: false } } } } @@ -105,7 +104,7 @@ export namespace CommonPlatformTasks { * Returns true if Openshift oAuth is enabled (or omitted) and false if it is explicitly disabled. */ function isOAuthEnabled(ctx: any): boolean { - const crPatch = ctx.CRPatch + const crPatch = ctx.crPatch if (crPatch && crPatch.spec && crPatch.spec.auth && typeof crPatch.spec.auth.openShiftoAuth === 'boolean') { return crPatch.spec.auth.openShiftoAuth } diff --git a/src/tasks/platforms/openshift.ts b/src/tasks/platforms/openshift.ts index 59b3be826..963888195 100644 --- a/src/tasks/platforms/openshift.ts +++ b/src/tasks/platforms/openshift.ts @@ -10,9 +10,9 @@ import { Command } from '@oclif/command' import * as commandExists from 'command-exists' -import * as execa from 'execa' import * as Listr from 'listr' +import { OpenShiftHelper } from '../../api/openshift' import { VersionHelper } from '../../api/version' export class OpenshiftTasks { @@ -34,9 +34,9 @@ export class OpenshiftTasks { { title: 'Verify if openshift is running', task: async (_ctx: any, task: any) => { - const openshiftIsRunning = await this.isOpenshiftRunning() - if (!openshiftIsRunning) { - command.error(`E_PLATFORM_NOT_READY: oc status command failed. If there is no project, please create it before by running "oc new-project ${flags.chenamespace}"`) + const openShiftHelper = new OpenShiftHelper() + if (!await openShiftHelper.isOpenShiftRunning()) { + command.error('PLATFORM_NOT_READY: \'oc status\' command failed. Please login with \'oc login\' command and try again.') } else { task.title = `${task.title}...done.` } @@ -46,10 +46,4 @@ export class OpenshiftTasks { VersionHelper.getK8sCheckVersionTask(flags), ], { renderer: flags['listr-renderer'] as any }) } - - async isOpenshiftRunning(): Promise { - const { exitCode } = await execa('oc', ['status'], { timeout: 60000, reject: false }) - return exitCode === 0 - } - } diff --git a/src/util.ts b/src/util.ts index b882c7fc1..747810940 100644 --- a/src/util.ts +++ b/src/util.ts @@ -12,7 +12,10 @@ import { Command } from '@oclif/command' import * as commandExists from 'command-exists' import { existsSync, readFileSync } from 'fs-extra' import * as yaml from 'js-yaml' +import Listr = require('listr') +import { KubeHelper } from './api/kube' +import { CHE_OPERATOR_CR_PATCH_YAML_KEY, CHE_OPERATOR_CR_YAML_KEY } from './common-flags' import { DEFAULT_CHE_OPERATOR_IMAGE } from './constants' export const KUBERNETES_CLI = 'kubectl' @@ -102,10 +105,18 @@ export function sleep(ms: number): Promise { /** * Initialize command context. */ -export function initializeContext(): any { +export async function initializeContext(flags?: any): Promise { + const kube = new KubeHelper(flags) const ctx: any = {} + ctx.isOpenShift = await kube.isOpenShift() + ctx.isOpenShift4 = await kube.isOpenShift4() ctx.highlightedMessages = [] as string[] - ctx.starTime = Date.now() + ctx.startTime = Date.now() + ctx.customCR = readCRFile(flags, CHE_OPERATOR_CR_YAML_KEY) + ctx.crPatch = readCRFile(flags, CHE_OPERATOR_CR_PATCH_YAML_KEY) + if (flags['listr-renderer'] as any) { + ctx.listrOptions = { renderer: (flags['listr-renderer'] as any), collapse: false } as Listr.ListrOptions + } return ctx } @@ -115,7 +126,7 @@ export function initializeContext(): any { * @param CRKey - key for CR file flag * @param command - parent command */ -export function readCRFile(flags: any, CRKey: string, command: Command): any { +export function readCRFile(flags: any, CRKey: string): any { const CRFilePath = flags[CRKey] if (!CRFilePath) { return @@ -125,19 +136,19 @@ export function readCRFile(flags: any, CRKey: string, command: Command): any { return yaml.safeLoad(readFileSync(CRFilePath).toString()) } - command.error(`Unable to find file defined in the flag '--${CRKey}'`) + throw new Error(`Unable to find file defined in the flag '--${CRKey}'`) } /** * Returns command success message with execution time. */ export function getCommandSuccessMessage(command: Command, ctx: any): string { - if (ctx.starTime) { + if (ctx.startTime) { if (!ctx.endTime) { ctx.endTime = Date.now() } - const workingTimeInSeconds = Math.round((ctx.endTime - ctx.starTime) / 1000) + const workingTimeInSeconds = Math.round((ctx.endTime - ctx.startTime) / 1000) const minutes = Math.floor(workingTimeInSeconds / 60) const seconds = (workingTimeInSeconds - minutes * 60) % 60 const minutesToStr = minutes.toLocaleString([], { minimumIntegerDigits: 2 }) diff --git a/test/e2e/minikube.test.ts b/test/e2e/minikube.test.ts index 13217fce5..dba7db050 100644 --- a/test/e2e/minikube.test.ts +++ b/test/e2e/minikube.test.ts @@ -189,7 +189,7 @@ describe('Workspace creation, list, start, inject, delete. Support stop and dele test .stdout() .stderr({ print: true }) - .command(['server:delete', '--skip-deletion-check', '--delete-namespace']) + .command(['server:delete', '--yes', '--delete-namespace']) .exit(0) .it('deletes Eclipse Che resources on minikube successfully') }) diff --git a/test/e2e/minishift.test.ts b/test/e2e/minishift.test.ts index 11bf3438e..48a28bb82 100644 --- a/test/e2e/minishift.test.ts +++ b/test/e2e/minishift.test.ts @@ -191,7 +191,7 @@ describe('Workspace creation, list, start, inject, delete. Support stop and dele test .stdout() .stderr({ print: true }) - .command(['server:delete', '--skip-deletion-check', '--delete-namespace']) + .command(['server:delete', '--yes', '--delete-namespace']) .exit(0) .it('deletes Eclipse Che resources on minishift successfully') }) diff --git a/test/e2e/openshift.test.ts b/test/e2e/openshift.test.ts index 8733ee5c9..f58b7d9ca 100644 --- a/test/e2e/openshift.test.ts +++ b/test/e2e/openshift.test.ts @@ -194,7 +194,7 @@ describe('Workspace creation, list, start, inject, delete. Support stop and dele test .stdout() .stderr({ print: true }) - .command(['server:delete', '--skip-deletion-check', '--delete-namespace']) + .command(['server:delete', '--yes', '--delete-namespace']) .exit(0) .it('deletes Eclipse Che resources on minishift successfully') }) diff --git a/test/tasks/platforms/openshift.test.ts b/test/tasks/platforms/openshift.test.ts index fe199088c..830ad9360 100644 --- a/test/tasks/platforms/openshift.test.ts +++ b/test/tasks/platforms/openshift.test.ts @@ -10,11 +10,11 @@ import * as execa from 'execa' import { expect, fancy } from 'fancy-test' -import { OpenshiftTasks } from '../../../src/tasks/platforms/openshift' +import { OpenShiftHelper } from '../../../src/api/openshift' jest.mock('execa') -let openshift = new OpenshiftTasks() +let openShiftHelper = new OpenShiftHelper() describe('start', () => { fancy @@ -37,7 +37,7 @@ describe('start', () => { 3 infos identified, use 'oc status --suggest' to see details.`; (execa as any).mockResolvedValue({ exitCode: 0, stdout: status }) - const res = await openshift.isOpenshiftRunning() + const res = await openShiftHelper.isOpenShiftRunning() expect(res).to.equal(true) }) @@ -47,7 +47,7 @@ describe('start', () => { `; (execa as any).mockResolvedValue({ exitCode: 1, stdout: status }) - const res = await openshift.isOpenshiftRunning() + const res = await openShiftHelper.isOpenShiftRunning() expect(res).to.equal(false) }) })