diff --git a/package.json b/package.json index 64fed1540..1a4294f79 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "axios": "^0.19.0", "cli-ux": "^5.3.2", "command-exists": "^1.2.8", + "debug": "^4.1.1", "eclipse-che": "git://github.com/eclipse/che#master", - "eclipse-che-minishift": "git://github.com/minishift/minishift#master", + "eclipse-che-minishift": "git://github.com/sleshchenko/minishift#updateChe", "eclipse-che-operator": "git://github.com/eclipse/che-operator#master", "esprima": "^4.0.1", "execa": "^2.0.0", diff --git a/src/api/che.ts b/src/api/che.ts index 212d8cdf7..35b581bbd 100644 --- a/src/api/che.ts +++ b/src/api/che.ts @@ -13,17 +13,21 @@ import { cli } from 'cli-ux' import * as fs from 'fs' import * as yaml from 'js-yaml' -import { KubeHelper } from '../api/kube' import { OpenShiftHelper } from '../api/openshift' import { Devfile } from './devfile' +import { KubeHelper } from './kube' export class CheHelper { defaultCheResponseTimeoutMs = 3000 kc = new KubeConfig() - kube = new KubeHelper() + kube: KubeHelper oc = new OpenShiftHelper() + constructor(flags: any) { + this.kube = new KubeHelper(flags) + } + async cheServerPodExist(namespace: string): Promise { const kc = new KubeConfig() kc.loadFromDefault() diff --git a/src/api/kube.ts b/src/api/kube.ts index def2ba4ef..886f127a1 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -524,6 +524,31 @@ export class KubeHelper { } } + async deploymentReady(name = '', namespace = ''): Promise { + const k8sApi = this.kc.makeApiClient(Apps_v1Api) + try { + const res = await k8sApi.readNamespacedDeployment(name, namespace) + return ((res && res.body && + res.body.status && res.body.status.readyReplicas + && res.body.status.readyReplicas > 0) as boolean) + } catch { + return false + } + } + + async deploymentStopped(name = '', namespace = ''): Promise { + const k8sApi = this.kc.makeApiClient(Apps_v1Api) + try { + const res = await k8sApi.readNamespacedDeployment(name, namespace) + if (res && res.body && res.body.spec && res.body.spec.replicas) { + throw new Error(`Deployment '${name}' without replicas in spec is fetched`) + } + return res.body.spec.replicas === 0 + } catch { + return false + } + } + async isDeploymentPaused(name = '', namespace = ''): Promise { const k8sApi = this.kc.makeApiClient(Apps_v1Api) try { diff --git a/src/api/openshift.ts b/src/api/openshift.ts index 9f5231eeb..7a61edd12 100644 --- a/src/api/openshift.ts +++ b/src/api/openshift.ts @@ -45,17 +45,6 @@ export class OpenShiftHelper { const args = ['delete', 'route', '--all', '--namespace', namespace] await execa(command, args, { timeout: 60000 }) } - async deploymentConfigExist(name = '', namespace = ''): Promise { - const command = 'oc' - const args = ['get', 'deploymentconfig', '--namespace', namespace, '-o', `jsonpath={range.items[?(.metadata.name=='${name}')]}{.metadata.name}{end}`] - const { stdout } = await execa(command, args, { timeout: 60000 }) - return stdout.trim().includes(name) - } - async scaleDeploymentConfig(name = '', namespace = '', replicas: number) { - const command = 'oc' - const args = ['scale', 'deploymentconfig', '--namespace', namespace, name, `--replicas=${replicas}`] - await execa(command, args, { timeout: 60000 }) - } async deleteAllDeploymentConfigs(namespace = '') { const command = 'oc' const args = ['delete', 'deploymentconfig', '--all', '--namespace', namespace] diff --git a/src/commands/server/delete.ts b/src/commands/server/delete.ts index 13ed20865..c0cf19fed 100644 --- a/src/commands/server/delete.ts +++ b/src/commands/server/delete.ts @@ -9,14 +9,14 @@ **********************************************************************/ import { Command, flags } from '@oclif/command' -import { cli } from 'cli-ux' -import * as commandExists from 'command-exists' +import * as Listrq from 'listr' -import { KubeHelper } from '../../api/kube' -import { OpenShiftHelper } from '../../api/openshift' import { cheNamespace, listrRenderer } from '../../common-flags' -import { HelmHelper } from '../../installers/helm' -import { MinishiftAddonHelper } from '../../installers/minishift-addon' +import { CheTasks } from '../../tasks/che' +import { HelmTasks } from '../../tasks/installers/helm' +import { MinishiftAddonTasks } from '../../tasks/installers/minishift-addon' +import { OperatorTasks } from '../../tasks/installers/operator' +import { K8sTasks } from '../../tasks/platforms/k8s' export default class Delete extends Command { static description = 'delete any Che related resource: Kubernetes/OpenShift/Helm' @@ -29,194 +29,24 @@ export default class Delete extends Command { async run() { const { flags } = this.parse(Delete) - const Listr = require('listr') + const notifier = require('node-notifier') - const kh = new KubeHelper(flags) - const oh = new OpenShiftHelper() - const helm = new HelmHelper() - const msAddon = new MinishiftAddonHelper() - const tasks = new Listr([ - { - title: 'Verify Kubernetes API', - task: async (ctx: any, task: any) => { - try { - await kh.checkKubeApi() - ctx.isOpenShift = await kh.isOpenShift() - task.title = await `${task.title}...OK` - if (ctx.isOpenShift) { - task.title = await `${task.title} (it's OpenShift)` - } - } catch (error) { - this.error(`Failed to connect to Kubernetes API. ${error.message}`) - } - } - }, - { - title: 'Delete the CR eclipse-che of type checlusters.org.eclipse.che', - task: async (_ctx: any, task: any) => { - if (await kh.crdExist('checlusters.org.eclipse.che') && - await kh.cheClusterExist('eclipse-che', flags.chenamespace)) { - await kh.deleteCheCluster('eclipse-che', flags.chenamespace) - await cli.wait(2000) //wait a couple of secs for the finalizers to be executed - task.title = await `${task.title}...OK` - } else { - task.title = await `${task.title}...CR not found` - } - } - }, - { - title: 'Delete all deployment configs', - enabled: (ctx: any) => ctx.isOpenShift, - task: async (_ctx: any, task: any) => { - await oh.deleteAllDeploymentConfigs(flags.chenamespace) - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete all deployments', - task: async (_ctx: any, task: any) => { - await kh.deleteAllDeployments(flags.chenamespace) - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete CRD checlusters.org.eclipse.che', - task: async (_ctx: any, task: any) => { - if (await kh.crdExist('checlusters.org.eclipse.che')) { - await kh.deleteCrd('checlusters.org.eclipse.che') - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete all services', - task: async (_ctx: any, task: any) => { - await kh.deleteAllServices(flags.chenamespace) - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete all ingresses', - enabled: (ctx: any) => !ctx.isOpenShift, - task: async (_ctx: any, task: any) => { - await kh.deleteAllIngresses(flags.chenamespace) - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete all routes', - enabled: (ctx: any) => ctx.isOpenShift, - task: async (_ctx: any, task: any) => { - await oh.deleteAllRoutes(flags.chenamespace) - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete configmaps che and che-operator', - task: async (_ctx: any, task: any) => { - if (await kh.configMapExist('che', flags.chenamespace)) { - await kh.deleteConfigMap('che', flags.chenamespace) - } - if (await kh.configMapExist('che-operator', flags.chenamespace)) { - await kh.deleteConfigMap('che-operator', flags.chenamespace) - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete role che-operator', - task: async (_ctx: any, task: any) => { - if (await kh.roleExist('che-operator', flags.chenamespace)) { - await kh.deleteRole('che-operator', flags.chenamespace) - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete cluster role che-operator', - task: async (_ctx: any, task: any) => { - if (await kh.clusterRoleExist('che-operator')) { - await kh.deleteClusterRole('che-operator') - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete rolebindings che, che-operator, che-workspace-exec and che-workspace-view', - task: async (_ctx: any, task: any) => { - if (await kh.roleBindingExist('che', flags.chenamespace)) { - await kh.deleteRoleBinding('che', flags.chenamespace) - } - if (await kh.roleBindingExist('che-operator', flags.chenamespace)) { - await kh.deleteRoleBinding('che-operator', flags.chenamespace) - } - if (await kh.roleBindingExist('che-workspace-exec', flags.chenamespace)) { - await kh.deleteRoleBinding('che-workspace-exec', flags.chenamespace) - } - if (await kh.roleBindingExist('che-workspace-view', flags.chenamespace)) { - await kh.deleteRoleBinding('che-workspace-view', flags.chenamespace) - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete cluster role binding che-operator', - task: async (_ctx: any, task: any) => { - if (await kh.clusterRoleBindingExist('che-operator')) { - await kh.deleteClusterRoleBinding('che-operator') - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete service accounts che, che-operator, che-workspace', - task: async (_ctx: any, task: any) => { - if (await kh.serviceAccountExist('che', flags.chenamespace)) { - await kh.deleteServiceAccount('che', flags.chenamespace) - } - if (await kh.roleBindingExist('che-operator', flags.chenamespace)) { - await kh.deleteServiceAccount('che-operator', flags.chenamespace) - } - if (await kh.roleBindingExist('che-workspace', flags.chenamespace)) { - await kh.deleteServiceAccount('che-workspace', flags.chenamespace) - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Delete PVC postgres-data and che-data-volume', - task: async (_ctx: any, task: any) => { - if (await kh.persistentVolumeClaimExist('che-operator', flags.chenamespace)) { - await kh.deletePersistentVolumeClaim('postgres-data', flags.chenamespace) - } - task.title = await `${task.title}...OK` - } - }, - { - title: 'Purge che Helm chart', - enabled: (ctx: any) => !ctx.isOpenShift, - task: async (_ctx: any, task: any) => { - if (await !commandExists.sync('helm')) { - task.title = await `${task.title}...OK (Helm not found)` - } else { - await helm.purgeHelmChart('che') - task.title = await `${task.title}...OK` - } - } - }, - { - title: 'Remove Che minishift addon', - enabled: (ctx: any) => ctx.isOpenShift, - task: async (_ctx: any, task: any) => { - if (!commandExists.sync('minishift')) { - task.title = await `${task.title}...OK (minishift not found)` - } else { - await msAddon.removeAddon() - task.title = await `${task.title}...OK` - } - } - }, - ], { renderer: flags['listr-renderer'] as any }) + + const k8sTasks = new K8sTasks() + const helmTasks = new HelmTasks() + const msAddonTasks = new MinishiftAddonTasks() + const operatorTasks = new OperatorTasks() + const cheTasks = new CheTasks(flags) + + let tasks = new Listrq(undefined, + { renderer: flags['listr-renderer'] as any } + ) + + tasks.add(k8sTasks.testApiTasks(flags, this)) + tasks.add(operatorTasks.deleteTasks(flags)) + tasks.add(cheTasks.deleteTasks(flags)) + tasks.add(helmTasks.deleteTasks(flags)) + tasks.add(msAddonTasks.deleteTasks(flags)) await tasks.run() diff --git a/src/commands/server/start.ts b/src/commands/server/start.ts index db24932b1..e8683b6ea 100644 --- a/src/commands/server/start.ts +++ b/src/commands/server/start.ts @@ -7,6 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ + import { Command, flags } from '@oclif/command' import { string } from '@oclif/parser/lib/flags' import * as fs from 'fs-extra' @@ -14,21 +15,13 @@ import * as Listr from 'listr' import * as notifier from 'node-notifier' import * as path from 'path' -import { CheHelper } from '../../api/che' -import { KubeHelper } from '../../api/kube' +import { ListrOptions } from '../../../types/listr-options' import { cheDeployment, cheNamespace, listrRenderer } from '../../common-flags' -import { HelmHelper } from '../../installers/helm' -import { MinishiftAddonHelper } from '../../installers/minishift-addon' -import { OperatorHelper } from '../../installers/operator' -import { CRCHelper } from '../../platforms/crc' -import { DockerDesktopHelper } from '../../platforms/docker-desktop' -import { K8sHelper } from '../../platforms/k8s' -import { MicroK8sHelper } from '../../platforms/microk8s' -import { MinikubeHelper } from '../../platforms/minikube' -import { MinishiftHelper } from '../../platforms/minishift' -import { OpenshiftHelper } from '../../platforms/openshift' +import { CheTasks } from '../../tasks/che' +import { InstallerTasks } from '../../tasks/installers/installer' +import { K8sTasks } from '../../tasks/platforms/k8s' +import { PlatformTasks } from '../../tasks/platforms/platform' -let kube: KubeHelper export default class Start extends Command { static description = 'start Eclipse Che Server' @@ -88,9 +81,15 @@ export default class Start extends Command { description: 'Authorize usage of self signed certificates for encryption. Note that `self-signed-cert` secret with CA certificate must be created in the configured namespace.', default: false }), + 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'], + }), installer: string({ char: 'a', - description: 'Installer type. Valid values are \"helm\", \"operator\" and \"minishift-addon\"', + description: 'Installer type', + options: ['helm', 'operator', 'minishift-addon'], default: '' }), domain: string({ @@ -98,10 +97,6 @@ export default class Start extends Command { description: 'Domain of the Kubernetes cluster (e.g. example.k8s-cluster.com or .nip.io)', default: '' }), - platform: string({ - char: 'p', - description: 'Type of Kubernetes platform. Valid values are \"minikube\", \"minishift\", \"k8s (for kubernetes)\", \"openshift\", \"crc (for CodeReady Containers)\", \"microk8s\".' - }), 'os-oauth': flags.boolean({ description: 'Enable use of OpenShift credentials to log into Che', default: false @@ -162,22 +157,7 @@ export default class Start extends Command { } } - async run() { - const { flags } = this.parse(Start) - kube = new KubeHelper(flags) - Start.setPlaformDefaults(flags) - const minikube = new MinikubeHelper() - const microk8s = new MicroK8sHelper() - const minishift = new MinishiftHelper() - const openshift = new OpenshiftHelper() - const k8s = new K8sHelper() - const dockerDesktop = new DockerDesktopHelper() - const crc = new CRCHelper() - const helm = new HelmHelper() - const che = new CheHelper() - const operator = new OperatorHelper() - const minishiftAddon = new MinishiftAddonHelper() - + checkPlatformCompatibility(flags: any) { // matrix checks if (flags.installer) { if (flags.installer === 'minishift-addon') { @@ -198,139 +178,64 @@ export default class Start extends Command { } } } + } - // Platform Checks - let platformCheckTasks = new Listr(undefined, { renderer: flags['listr-renderer'] as any, collapse: false }) - if (!flags.platform) { - this.error("--platform parameter is mandatory. The command 'chectl server:start --help' will list all available platforms.") - } - if (flags.platform === 'minikube') { - platformCheckTasks.add({ - title: '✈️ Minikube preflight checklist', - task: () => minikube.startTasks(flags, this) - }) - } else if (flags.platform === 'minishift') { - platformCheckTasks.add({ - title: '✈️ Minishift preflight checklist', - task: () => minishift.startTasks(flags, this) - }) - } else if (flags.platform === 'microk8s') { - platformCheckTasks.add({ - title: '✈️ MicroK8s preflight checklist', - task: () => microk8s.startTasks(flags, this) - }) - } else if (flags.platform === 'crc') { - platformCheckTasks.add({ - title: '✈️ CodeReady Containers preflight checklist', - task: () => crc.startTasks(flags, this) - }) - } else if (flags.platform === 'openshift') { - platformCheckTasks.add({ - title: '✈️ Openshift preflight checklist', - task: () => openshift.startTasks(flags, this) - }) - } else if (flags.platform === 'k8s') { - platformCheckTasks.add({ - title: '✈️ Kubernetes preflight checklist', - task: () => k8s.startTasks(flags, this) - }) - } else if (flags.platform === 'docker-desktop') { - platformCheckTasks.add({ - title: '✈️ Docker Desktop preflight checklist', - task: () => dockerDesktop.startTasks(flags, this) - }) - } else { - this.error(`Platform ${flags.platform} is not supported yet ¯\\_(ツ)_/¯`) - this.exit() - } + async run() { + const { flags } = this.parse(Start) - // Installer - let installerTasks = new Listr({ renderer: flags['listr-renderer'] as any, collapse: false }) - if (flags.installer === 'helm') { - installerTasks.add({ - title: '🏃‍ Running Helm to install Che', - task: () => helm.startTasks(flags, this) - }) - } else if (flags.installer === 'operator') { - // The operator installs Che multiuser only - flags.multiuser = true - // Installers use distinct ingress names - installerTasks.add({ - title: '🏃‍ Running the Che Operator', - task: () => operator.startTasks(flags, this) - }) - } else if (flags.installer === 'minishift-addon') { - // minishift-addon supports Che singleuser only - flags.multiuser = false - // Installers use distinct ingress names - installerTasks.add({ - title: '🏃‍ Running the Che minishift-addon', - task: () => minishiftAddon.startTasks(flags, this) - }) - } else { - this.error(`Installer ${flags.installer} is not supported ¯\\_(ツ)_/¯`) - this.exit() - } + const listrOptions = ListrOptions.getTasksListrOptions(flags['listr-renderer']) - // Post Install Checks - let cheBootstrapSubTasks = new Listr() - const cheStartCheckTasks = new Listr([{ - title: '✅ Post installation checklist', - task: () => cheBootstrapSubTasks - }], - { - renderer: flags['listr-renderer'] as any, - collapse: false - } - ) + const cheTasks = new CheTasks(flags) + const platformTasks = new PlatformTasks() + const installerTasks = new InstallerTasks() + const k8sTasks = new K8sTasks() - if (flags.multiuser) { - cheBootstrapSubTasks.add({ - title: 'PostgreSQL pod bootstrap', - task: () => this.podStartTasks(this.getPostgresSelector(), flags.chenamespace) - }) - cheBootstrapSubTasks.add({ - title: 'Keycloak pod bootstrap', - task: () => this.podStartTasks(this.getKeycloakSelector(), flags.chenamespace) - }) - } + // Platform Checks + let platformCheckTasks = new Listr(platformTasks.preflightCheckTasks(flags, this), listrOptions) + + // Checks if Che is already deployed + let preInstallTasks = new Listr(undefined, listrOptions) + preInstallTasks.add(k8sTasks.testApiTasks(flags, this)) + preInstallTasks.add({ + title: '👀 Looking for an already existing Che instance', + task: () => new Listr(cheTasks.checkIfCheIsInstalledTasks(flags, this)) + }) - if (!flags['devfile-registry-url'] && flags.installer !== 'minishift-addon') { - cheBootstrapSubTasks.add({ - title: 'Devfile registry pod bootstrap', - task: () => this.podStartTasks(this.getDevfileRegistrySelector(), flags.chenamespace) - }) - } + Start.setPlaformDefaults(flags) + let installTasks = new Listr(installerTasks.installTasks(flags, this), listrOptions) - if (!flags['plugin-registry-url'] && flags.installer !== 'minishift-addon') { - cheBootstrapSubTasks.add({ - title: 'Plugin registry pod bootstrap', - task: () => this.podStartTasks(this.getPluginRegistrySelector(), flags.chenamespace) - }) - } + const startDeployedCheTasks = new Listr([{ + title: '👀 Starting already deployed Che', + task: () => new Listr(cheTasks.scaleCheUpTasks(this)) + }], listrOptions) - cheBootstrapSubTasks.add({ - title: 'Che pod bootstrap', - task: () => this.podStartTasks(this.getCheServerSelector(flags), flags.chenamespace) - }) + // Post Install Checks + const postInstallTasks = new Listr([{ + title: '✅ Post installation checklist', + task: () => new Listr(cheTasks.waitDeployedChe(flags, this)) + }], listrOptions) - cheBootstrapSubTasks.add({ - title: 'Retrieving Che Server URL', - task: async (ctx: any, task: any) => { - ctx.cheURL = await che.cheURL(flags.chenamespace) - task.title = await `${task.title}...${ctx.cheURL}` + try { + const ctx: any = {} + await preInstallTasks.run(ctx) + + if (!ctx.isCheDeployed) { + this.checkPlatformCompatibility(flags) + await platformCheckTasks.run(ctx) + await installTasks.run(ctx) + } else if (!ctx.isCheReady + || (ctx.isPostgresDeployed && !ctx.isPostgresReady) + || (ctx.isKeycloakDeployed && !ctx.isKeycloakReady) + || (ctx.isPluginRegistryDeployed && !ctx.isPluginRegistryReady) + || (ctx.isDevfileRegistryDeployed && !ctx.isDevfileRegistryReady)) { + if (flags.platform || flags.installer) { + this.warn('Deployed Che is found and the specified installation parameters will be ignored') + } + // perform Che start task if there is any component that is not ready + await startDeployedCheTasks.run(ctx) } - }) - - cheBootstrapSubTasks.add({ - title: 'Che status check', - task: async ctx => che.isCheServerReady(ctx.cheURL) - }) - try { - await platformCheckTasks.run() - await installerTasks.run() - await cheStartCheckTasks.run() + await postInstallTasks.run(ctx) this.log('Command server:start has completed successfully.') } catch (err) { this.error(err) @@ -343,65 +248,4 @@ export default class Start extends Command { this.exit(0) } - - getPostgresSelector(): string { - return 'app=che,component=postgres' - } - - getKeycloakSelector(): string { - return 'app=che,component=keycloak' - } - - getDevfileRegistrySelector(): string { - return 'app=che,component=devfile-registry' - } - - getPluginRegistrySelector(): string { - return 'app=che,component=plugin-registry' - } - - getCheServerSelector(flags: any): string { - if (flags.installer === 'minishift-addon') { - return 'app=che' - } else { - return 'app=che,component=che' - } - } - - podStartTasks(selector: string, namespace = ''): Listr { - return new Listr([ - { - title: 'scheduling', - task: async (_ctx: any, task: any) => { - let phase - const title = task.title - try { - phase = await kube.getPodPhase(selector, namespace) - } catch (_err) { - // not able to grab current phase - this.debug(_err) - } - // wait only if not yet running - if (phase !== 'Running') { - await kube.waitForPodPending(selector, namespace) - } - task.title = `${title}...done.` - } - }, - { - title: 'downloading images', - task: async (_ctx: any, task: any) => { - await kube.waitForPodPhase(selector, 'Running', namespace) - task.title = `${task.title}...done.` - } - }, - { - title: 'starting', - task: async (_ctx: any, task: any) => { - await kube.waitForPodReady(selector, namespace) - task.title = `${task.title}...done.` - } - } - ]) - } } diff --git a/src/commands/server/stop.ts b/src/commands/server/stop.ts index 813585bb3..cd5be77d0 100644 --- a/src/commands/server/stop.ts +++ b/src/commands/server/stop.ts @@ -11,10 +11,9 @@ import { Command, flags } from '@oclif/command' import { string } from '@oclif/parser/lib/flags' -import { CheHelper } from '../../api/che' -import { KubeHelper } from '../../api/kube' -import { OpenShiftHelper } from '../../api/openshift' -import { cheDeployment, cheNamespace, listrRenderer } from '../../common-flags' +import { accessToken, cheDeployment, cheNamespace, listrRenderer } from '../../common-flags' +import { CheTasks } from '../../tasks/che' +import { K8sTasks } from '../../tasks/platforms/k8s' export default class Stop extends Command { static description = 'stop Eclipse Che Server' @@ -28,10 +27,7 @@ export default class Stop extends Command { default: 'app=che,component=che', env: 'CHE_SELECTOR' }), - 'access-token': string({ - description: 'Che OIDC Access Token', - env: 'CHE_ACCESS_TOKEN' - }), + 'access-token': accessToken, 'listr-renderer': listrRenderer } @@ -39,226 +35,40 @@ export default class Stop extends Command { const { flags } = this.parse(Stop) const Listr = require('listr') const notifier = require('node-notifier') - const che = new CheHelper() - const kh = new KubeHelper() - const oc = new OpenShiftHelper() - const tasks = new Listr([ - { - title: 'Verify Kubernetes API', - task: async (ctx: any, task: any) => { - try { - await kh.checkKubeApi() - ctx.isOpenShift = await kh.isOpenShift() - task.title = await `${task.title}...done` - if (ctx.isOpenShift) { - task.title = await `${task.title} (it's OpenShift)` - } - } catch (error) { - this.error(`Failed to connect to Kubernetes API. ${error.message}`) - } - } - }, - { - title: `Verify if deployment \"${flags['deployment-name']}\" exist in namespace \"${flags.chenamespace}\"`, - task: async (ctx: any, task: any) => { - if (ctx.isOpenShift && await oc.deploymentConfigExist(flags['deployment-name'], flags.chenamespace)) { - // minishift addon and the openshift templates use a deployment config - ctx.deploymentConfigExist = true - ctx.foundKeycloakDeployment = await oc.deploymentConfigExist('keycloak', flags.chenamespace) - ctx.foundPostgresDeployment = await oc.deploymentConfigExist('postgres', flags.chenamespace) - if (ctx.foundKeycloakDeployment && ctx.foundPostgresDeployment) { - task.title = await `${task.title}...the dc "${flags['deployment-name']}" exists (as well as keycloak and postgres)` - } else { - task.title = await `${task.title}...the dc "${flags['deployment-name']}" exists` - } - } else if (await kh.deploymentExist(flags['deployment-name'], flags.chenamespace)) { - // helm chart and Che operator use a deployment - ctx.foundKeycloakDeployment = await kh.deploymentExist('keycloak', flags.chenamespace) - ctx.foundPostgresDeployment = await kh.deploymentExist('postgres', flags.chenamespace) - ctx.foundDevfileRegistryDeployment = await kh.deploymentExist('devfile-registry', flags.chenamespace) - ctx.foundPluginRegistryDeployment = await kh.deploymentExist('plugin-registry', flags.chenamespace) - if (ctx.foundKeycloakDeployment && ctx.foundPostgresDeployment) { - task.title = await `${task.title}...it does (as well as keycloak and postgres)` - } else { - task.title = await `${task.title}...it does` - } - } else { - this.error(`E_BAD_DEPLOY - Deployment and DeploymentConfig do not exist.\nNeither a Deployment nor a DeploymentConfig named "${flags['deployment-name']}" exist in namespace \"${flags.chenamespace}\", Che Server cannot be stopped.\nFix with: verify the namespace where Che is running (oc get projects)\nhttps://github.com/eclipse/che`, { code: 'E_BAD_DEPLOY' }) - } - } - }, - { - title: `Verify if Che server pod is running (selector "${flags['che-selector']}")`, - task: async (ctx: any, task: any) => { - const cheServerPodExist = await kh.podsExistBySelector(flags['che-selector'] as string, flags.chenamespace) - if (!cheServerPodExist) { - task.title = `${task.title}...It doesn't.\nChe server was already stopped.` - ctx.isAlreadyStopped = true - } else { - const cheServerPodReadyStatus = await kh.getPodReadyConditionStatus(flags['che-selector'] as string, flags.chenamespace) - if (cheServerPodReadyStatus !== 'True') { - task.title = `${task.title}...It doesn't.\nChe server is not ready yet. Try again in a few seconds.` - ctx.isNotReadyYet = true - } else { - task.title = `${task.title}...done.` - } - } - } - }, - { - title: 'Check Che server status', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet, - task: async (ctx: any, task: any) => { - let cheURL = '' - try { - cheURL = await che.cheURL(flags.chenamespace) - const status = await che.getCheServerStatus(cheURL) - ctx.isAuthEnabled = await che.isAuthenticationEnabled(cheURL) - const auth = ctx.isAuthEnabled ? '(auth enabled)' : '(auth disabled)' - task.title = await `${task.title}...${status} ${auth}` - } catch (error) { - this.error(`E_CHECK_CHE_STATUS_FAIL - Failed to check Che status (URL: ${cheURL}). ${error.message}`) - } - } - }, - { - title: 'Stop Che server and wait until it\'s ready to shutdown', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet, - task: async (ctx: any, task: any) => { - if (ctx.isAuthEnabled && !flags['access-token']) { - this.error('E_AUTH_REQUIRED - Che authentication is enabled and an access token need to be provided (flag --access-token).\nFor instructions to retreive a valid access token refer to https://www.eclipse.org/che/docs/che-6/authentication.html') - } - try { - const cheURL = await che.cheURL(flags.chenamespace) - await che.startShutdown(cheURL, flags['access-token']) - await che.waitUntilReadyToShutdown(cheURL) - task.title = await `${task.title}...done` - } catch (error) { - this.error(`E_SHUTDOWN_CHE_SERVER_FAIL - Failed to shutdown Che server. ${error.message}`) - } - } - }, - { - title: `Scale \"${flags['deployment-name']}\" deployment to zero`, - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet, - task: async (ctx: any, task: any) => { - try { - if (ctx.deploymentConfigExist) { - await oc.scaleDeploymentConfig(flags['deployment-name'], flags.chenamespace, 0) - } else { - await kh.scaleDeployment(flags['deployment-name'], flags.chenamespace, 0) - } - task.title = await `${task.title}...done` - } catch (error) { - this.error(`E_SCALE_DEPLOY_FAIL - Failed to scale deployment. ${error.message}`) - } - } - }, - { - title: 'Wait until Che pod is deleted', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet, - task: async (_ctx: any, task: any) => { - await kh.waitUntilPodIsDeleted('app=che,component=che', flags.chenamespace) - task.title = `${task.title}...done.` - } - }, - { - title: 'Scale \"keycloak\" deployment to zero', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet && ctx.foundKeycloakDeployment, - task: async (ctx: any, task: any) => { - try { - if (ctx.deploymentConfigExist) { - await oc.scaleDeploymentConfig('keycloak', flags.chenamespace, 0) - } else { - await kh.scaleDeployment('keycloak', flags.chenamespace, 0) - } - task.title = await `${task.title}...done` - } catch (error) { - this.error(`E_SCALE_DEPLOY_FAIL - Failed to scale keycloak deployment. ${error.message}`) - } - } - }, - { - title: 'Wait until Keycloak pod is deleted', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet && ctx.foundKeycloakDeployment, - task: async (_ctx: any, task: any) => { - await kh.waitUntilPodIsDeleted('app=keycloak', flags.chenamespace) - task.title = `${task.title}...done.` - } - }, - { - title: 'Scale \"postgres\" deployment to zero', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet && ctx.foundPostgresDeployment, - task: async (ctx: any, task: any) => { - try { - if (ctx.deploymentConfigExist) { - await oc.scaleDeploymentConfig('postgres', flags.chenamespace, 0) - } else { - await kh.scaleDeployment('postgres', flags.chenamespace, 0) - } - task.title = await `${task.title}...done` - } catch (error) { - this.error(`E_SCALE_DEPLOY_FAIL - Failed to scale postgres deployment. ${error.message}`) - } - } - }, - { - title: 'Wait until Postgres pod is deleted', - enabled: (ctx: any) => !ctx.isAlreadyStopped && !ctx.isNotReadyYet && ctx.foundPostgresDeployment, - task: async (_ctx: any, task: any) => { - await kh.waitUntilPodIsDeleted('app=postgres', flags.chenamespace) - task.title = `${task.title}...done.` - } - }, + const cheTasks = new CheTasks(flags) + const kubeTasks = new K8sTasks() + + let tasks = new Listr(undefined, { - title: 'Scale \"devfile registry\" deployment to zero', - enabled: (ctx: any) => ctx.foundDevfileRegistryDeployment, - task: async (ctx: any, task: any) => { - try { - if (ctx.deploymentConfigExist) { - await oc.scaleDeploymentConfig('devfile-registry', flags.chenamespace, 0) - } else { - await kh.scaleDeployment('devfile-registry', flags.chenamespace, 0) - } - task.title = await `${task.title}...done` - } catch (error) { - this.error(`E_SCALE_DEPLOY_FAIL - Failed to scale devfile-registry deployment. ${error.message}`) - } - } - }, + renderer: flags['listr-renderer'] as any, + collapse: false + } + ) + + tasks.add(kubeTasks.testApiTasks(flags, this)) + tasks.add(cheTasks.checkIfCheIsInstalledTasks(flags, this)) + tasks.add([ { - title: 'Wait until Devfile registry pod is deleted', - enabled: (ctx: any) => ctx.foundDevfileRegistryDeployment, - task: async (_ctx: any, task: any) => { - await kh.waitUntilPodIsDeleted('app=che,component=devfile-registry', flags.chenamespace) - task.title = `${task.title}...done.` + title: 'Deployment doesn\'t exist', + enabled: (ctx: any) => !ctx.isCheDeployed, + task: async () => { + await this.error(`E_BAD_DEPLOY - Deployment do not exist.\nA Deployment named "${flags['deployment-name']}" exist in namespace \"${flags.chenamespace}\", Che Server cannot be stopped.\nFix with: verify the namespace where Che is running (oc get projects)\nhttps://github.com/eclipse/che`, { code: 'E_BAD_DEPLOY' }) } }, { - title: 'Scale \"plugin registry\" deployment to zero', - enabled: (ctx: any) => ctx.foundPluginRegistryDeployment, - task: async (ctx: any, task: any) => { - try { - if (ctx.deploymentConfigExist) { - await oc.scaleDeploymentConfig('plugin-registry', flags.chenamespace, 0) - } else { - await kh.scaleDeployment('plugin-registry', flags.chenamespace, 0) - } - task.title = await `${task.title}...done` - } catch (error) { - this.error(`E_SCALE_DEPLOY_FAIL - Failed to scale plugin-registry deployment. ${error.message}`) - } - } + title: 'Che server was already stopped', + enabled: (ctx: any) => (ctx.isStopped), + task: async () => { } }, { - title: 'Wait until Plugin registry pod is deleted', - enabled: (ctx: any) => ctx.foundPluginRegistryDeployment, - task: async (_ctx: any, task: any) => { - await kh.waitUntilPodIsDeleted('app=che,component=plugin-registry', flags.chenamespace) - task.title = `${task.title}...done.` - } - }, - ], { renderer: flags['listr-renderer'] as any }) + title: 'Che server Pod is not ready. It may be failing to start. Skipping shutdown request', + enabled: (ctx: any) => (ctx.isNotReadyYet), + task: async () => { } + } + ], + { renderer: flags['listr-renderer'] as any } + ) + tasks.add(cheTasks.scaleCheDownTasks(this)) try { await tasks.run() diff --git a/src/commands/workspace/inject.ts b/src/commands/workspace/inject.ts index f1a8029e3..e7effaaf5 100644 --- a/src/commands/workspace/inject.ts +++ b/src/commands/workspace/inject.ts @@ -44,7 +44,7 @@ export default class Inject extends Command { async run() { const { flags } = this.parse(Inject) const notifier = require('node-notifier') - const che = new CheHelper() + const che = new CheHelper(flags) const tasks = new Listr([ { title: `Verify if namespace ${flags.chenamespace} exists`, @@ -76,7 +76,7 @@ export default class Inject extends Command { return 'Currently, only injecting a kubeconfig is supported. Please, specify flag -k' } }, - task: () => this.injectKubeconfigTasks(flags.chenamespace!, flags.workspace!, flags.container) + task: () => this.injectKubeconfigTasks(flags, flags.chenamespace!, flags.workspace!, flags.container) }, ], { renderer: flags['listr-renderer'] as any, collapse: false }) @@ -92,8 +92,8 @@ export default class Inject extends Command { }) } - async injectKubeconfigTasks(chenamespace: string, workspace: string, container?: string): Promise { - const che = new CheHelper() + async injectKubeconfigTasks(flags: any, chenamespace: string, workspace: string, container?: string): Promise { + const che = new CheHelper(flags) const tasks = new Listr({ exitOnError: false, concurrent: true }) const containers = container ? [container] : await che.getWorkspacePodContainers(chenamespace!, workspace!) for (const cont of containers) { diff --git a/src/commands/workspace/start.ts b/src/commands/workspace/start.ts index f96034a5b..14b8bdb55 100644 --- a/src/commands/workspace/start.ts +++ b/src/commands/workspace/start.ts @@ -51,7 +51,7 @@ export default class Start extends Command { const { flags } = this.parse(Start) const Listr = require('listr') const notifier = require('node-notifier') - const che = new CheHelper() + const che = new CheHelper(flags) if (!flags.devfile && !flags.workspaceconfig) { this.error('workspace:start command is expecting a devfile or workspace configuration parameter.') } diff --git a/src/tasks/che.ts b/src/tasks/che.ts new file mode 100644 index 000000000..9e5a3a9ce --- /dev/null +++ b/src/tasks/che.ts @@ -0,0 +1,473 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { Command } from '@oclif/command' +import * as Listr from 'listr' + +import { CheHelper } from '../api/che' +import { KubeHelper } from '../api/kube' +import { OpenShiftHelper } from '../api/openshift' + +import { KubeTasks } from './kube' + +/** + * Holds tasks to work with Che component. + */ +export class CheTasks { + kube: KubeHelper + kubeTasks: KubeTasks + oc = new OpenShiftHelper() + che: CheHelper + + cheNamespace: string + + cheAccessToken: string + cheSelector: string + cheDeploymentName: string + + keycloakDeploymentName = 'keycloak' + keycloakSelector = 'app=che,component=keycloak' + + postgresDeploymentName = 'postgres' + postgresSelector = 'app=che,component=postgres' + + devfileRegistryDeploymentName = 'devfile-registry' + devfileRegistrySelector = 'app=che,component=devfile-registry' + + pluginRegistryDeploymentName = 'plugin-registry' + pluginRegistrySelector = 'app=che,component=devfile-registry' + + constructor(flags: any) { + this.kube = new KubeHelper(flags) + this.kubeTasks = new KubeTasks(flags) + this.che = new CheHelper(flags) + + if (flags.installer === 'minishift-addon') { + this.cheSelector = 'app=che' + } else { + this.cheSelector = 'app=che,component=che' + } + + this.cheAccessToken = flags['access-token'] + + this.cheNamespace = flags.chenamespace + this.cheDeploymentName = flags['deployment-name'] + } + + /** + * Returns tasks list that waits until every Che component will be started. + * + * Note that Che components statuses should be already set in context. + * + * @see che.checkIfCheIsInstalledTasks + */ + waitDeployedChe(flags: any, command: Command): ReadonlyArray { + return [ + { + title: 'PostgreSQL pod bootstrap', + enabled: ctx => ctx.isPostgresDeployed && !ctx.isPostgresReady, + task: () => this.kubeTasks.podStartTasks(command, this.postgresSelector, this.cheNamespace) + }, + { + title: 'Keycloak pod bootstrap', + enabled: ctx => ctx.isKeycloakDeployed && !ctx.isKeycloakReady, + task: () => this.kubeTasks.podStartTasks(command, this.keycloakSelector, this.cheNamespace) + }, + { + title: 'Devfile registry pod bootstrap', + enabled: ctx => ctx.isDevfileRegistryDeployed && !ctx.isDevfileRegistryReady, + task: () => this.kubeTasks.podStartTasks(command, this.devfileRegistrySelector, this.cheNamespace) + }, + { + title: 'Plugin registry pod bootstrap', + enabled: ctx => ctx.isPluginRegistryDeployed && !ctx.isPluginRegistryReady, + task: () => this.kubeTasks.podStartTasks(command, this.pluginRegistrySelector, this.cheNamespace) + }, + { + title: 'Che pod bootstrap', + enabled: ctx => !ctx.isCheReady, + task: () => this.kubeTasks.podStartTasks(command, this.cheSelector, this.cheNamespace) + }, + { + title: 'Retrieving Che Server URL', + task: async (ctx: any, task: any) => { + ctx.cheURL = await this.che.cheURL(flags.chenamespace) + task.title = await `${task.title}...${ctx.cheURL}` + } + }, + { + title: 'Che status check', + task: async ctx => this.che.isCheServerReady(ctx.cheURL) + } + ] + } + + /** + * Returns list of tasks that checks if Che is already installed. + * + * After executing the following properties are set in context: + * is[Component]Deployed, is[Component]Stopped, is[Component]Ready + * where component is one the: Che, Keycloak, Postgres, PluginRegistry, DevfileRegistry + */ + checkIfCheIsInstalledTasks(_flags: any, command: Command): ReadonlyArray { + return [ + { + title: `Verify if Che is deployed into namespace \"${this.cheNamespace}\"`, + task: async (ctx: any, task: any) => { + if (await this.kube.deploymentExist(this.cheDeploymentName, this.cheNamespace)) { + // helm chart and Che operator use a deployment + ctx.isCheDeployed = true + ctx.isCheReady = await this.kube.deploymentReady(this.cheDeploymentName, this.cheNamespace) + if (!ctx.isCheReady) { + ctx.isCheStopped = await this.kube.deploymentStopped(this.cheDeploymentName, this.cheNamespace) + } + + ctx.isKeycloakDeployed = await this.kube.deploymentExist(this.keycloakDeploymentName, this.cheNamespace) + if (ctx.isKeycloakDeployed) { + ctx.isKeycloakReady = await this.kube.deploymentReady(this.keycloakDeploymentName, this.cheNamespace) + if (!ctx.isKeycloakReady) { + ctx.isKeycloakStopped = await this.kube.deploymentStopped(this.keycloakDeploymentName, this.cheNamespace) + } + } + + ctx.isPostgresDeployed = await this.kube.deploymentExist(this.postgresDeploymentName, this.cheNamespace) + if (ctx.isPostgresDeployed) { + ctx.isPostgresReady = await this.kube.deploymentReady(this.postgresDeploymentName, this.cheNamespace) + if (!ctx.isPostgresReady) { + ctx.isPostgresStopped = await this.kube.deploymentStopped(this.postgresDeploymentName, this.cheNamespace) + } + } + + ctx.isDevfileRegistryDeployed = await this.kube.deploymentExist(this.devfileRegistryDeploymentName, this.cheNamespace) + if (ctx.isDevfileRegistryDeployed) { + ctx.isDevfileRegistryReady = await this.kube.deploymentReady(this.devfileRegistryDeploymentName, this.cheNamespace) + if (!ctx.isDevfileRegistryReady) { + ctx.isDevfileRegistryStopped = await this.kube.deploymentStopped(this.devfileRegistryDeploymentName, this.cheNamespace) + } + } + + ctx.isPluginRegistryDeployed = await this.kube.deploymentExist(this.pluginRegistryDeploymentName, this.cheNamespace) + if (ctx.isPluginRegistryDeployed) { + ctx.isPluginRegistryReady = await this.kube.deploymentReady(this.pluginRegistryDeploymentName, this.cheNamespace) + if (!ctx.isPluginRegistryReady) { + ctx.isPluginRegistryStopped = await this.kube.deploymentStopped(this.pluginRegistryDeploymentName, this.cheNamespace) + } + } + } + + if (!ctx.isCheDeployed) { + task.title = await `${task.title}...it is not` + } else { + return new Listr([ + { + enabled: () => ctx.isCheDeployed, + title: `Found ${ctx.isCheStopped ? 'stopped' : 'running'} che deployment`, + task: () => { } + }, + { + enabled: () => ctx.isPostgresDeployed, + title: `Found ${ctx.isPostgresStopped ? 'stopped' : 'running'} postgres deployment`, + task: () => { } + }, + { + enabled: () => ctx.isKeycloakDeployed, + title: `Found ${ctx.isKeycloakStopped ? 'stopped' : 'running'} keycloak deployment`, + task: () => { } + }, + { + enabled: () => ctx.isPluginRegistryDeployed, + title: `Found ${ctx.isPluginRegistryStopped ? 'stopped' : 'running'} plugin registry deployment`, + task: () => { } + }, + { + enabled: () => ctx.isDevfileRegistryDeployed, + title: `Found ${ctx.isDevfileRegistryStopped ? 'stopped' : 'running'} devfile registry deployment`, + task: () => { } + } + ]) + } + } + }, + { + title: 'Check Che server status', + enabled: (ctx: any) => ctx.isCheDeployed && ctx.isCheReady, + task: async (ctx: any, task: any) => { + let cheURL = '' + try { + cheURL = await this.che.cheURL(this.cheNamespace) + const status = await this.che.getCheServerStatus(cheURL) + ctx.isAuthEnabled = await this.che.isAuthenticationEnabled(cheURL) + const auth = ctx.isAuthEnabled ? '(auth enabled)' : '(auth disabled)' + task.title = await `${task.title}...${status} ${auth}` + } catch (error) { + command.error(`E_CHECK_CHE_STATUS_FAIL - Failed to check Che status (URL: ${cheURL}). ${error.message}`) + } + } + } + ] + } + + /** + * Returns tasks list which scale down all Che components which are deployed. + * It requires {@link this#checkIfCheIsInstalledTasks} to be executed before. + * + * @see [CheTasks](#checkIfCheIsInstalledTasks) + */ + scaleCheUpTasks(_command: Command): ReadonlyArray { + return [ + { + title: 'Scaling up Che Deployments', + enabled: (ctx: any) => ctx.isCheDeployed, + task: async (ctx: any, task: any) => { + if (ctx.isPostgresDeployed) { + await this.kube.scaleDeployment(this.postgresDeploymentName, this.cheNamespace, 1) + } + if (ctx.isKeycloakDeployed) { + await this.kube.scaleDeployment(this.keycloakDeploymentName, this.cheNamespace, 1) + } + if (ctx.isPluginRegistryDeployed) { + await this.kube.scaleDeployment(this.pluginRegistryDeploymentName, this.cheNamespace, 1) + } + if (ctx.isDevfileRegistryDeployed) { + await this.kube.scaleDeployment(this.devfileRegistryDeploymentName, this.cheNamespace, 1) + } + await this.kube.scaleDeployment(this.cheDeploymentName, this.cheNamespace, 1) + task.title = `${task.title}...done.` + } + }, + { + title: `Che is already running in namespace \"${this.cheNamespace}\".`, + enabled: (ctx: any) => (ctx.isCheDeployed && ctx.isCheAvailable), + task: async (ctx: any, task: any) => { + ctx.cheDeploymentExist = true + ctx.cheIsAlreadyRunning = true + ctx.cheURL = await this.che.cheURL(this.cheNamespace) + task.title = await `${task.title}...it's URL is ${ctx.cheURL}` + } + } + ] + } + + /** + * Returns tasks list which scale down all Che components which are deployed. + * It requires {@link this#checkIfCheIsInstalledTasks} to be executed before. + * + * @see [CheTasks](#checkIfCheIsInstalledTasks) + */ + scaleCheDownTasks(command: Command): ReadonlyArray { + return [{ + title: 'Stop Che server and wait until it\'s ready to shutdown', + enabled: (ctx: any) => !ctx.isCheStopped, + task: async (ctx: any, task: any) => { + if (ctx.isAuthEnabled && !this.cheAccessToken) { + command.error('E_AUTH_REQUIRED - Che authentication is enabled and an access token need to be provided (flag --access-token).\nFor instructions to retreive a valid access token refer to https://www.eclipse.org/che/docs/che-6/authentication.html') + } + try { + const cheURL = await this.che.cheURL(this.cheNamespace) + await this.che.startShutdown(cheURL, this.cheAccessToken) + await this.che.waitUntilReadyToShutdown(cheURL) + task.title = await `${task.title}...done` + } catch (error) { + command.error(`E_SHUTDOWN_CHE_SERVER_FAIL - Failed to shutdown Che server. ${error.message}`) + } + } + }, + { + title: `Scale \"${this.cheDeploymentName}\" deployment to zero`, + enabled: (ctx: any) => !ctx.isCheStopped, + task: async (_ctx: any, task: any) => { + try { + await this.kube.scaleDeployment(this.cheDeploymentName, this.cheNamespace, 0) + task.title = await `${task.title}...done` + } catch (error) { + command.error(`E_SCALE_DEPLOY_FAIL - Failed to scale deployment. ${error.message}`) + } + } + }, + { + title: 'Wait until Che pod is deleted', + enabled: (ctx: any) => !ctx.isCheStopped, + task: async (_ctx: any, task: any) => { + await this.kube.waitUntilPodIsDeleted(this.cheSelector, this.cheNamespace) + task.title = `${task.title}...done.` + } + }, + { + title: 'Scale \"keycloak\" deployment to zero', + enabled: (ctx: any) => ctx.isKeycloakDeployed && !ctx.isKeycloakStopped, + task: async (_ctx: any, task: any) => { + try { + await this.kube.scaleDeployment('keycloak', this.cheNamespace, 0) + task.title = await `${task.title}...done` + } catch (error) { + command.error(`E_SCALE_DEPLOY_FAIL - Failed to scale keycloak deployment. ${error.message}`) + } + } + }, + { + title: 'Wait until Keycloak pod is deleted', + enabled: (ctx: any) => ctx.isKeycloakDeployed && !ctx.isKeycloakStopped, + task: async (_ctx: any, task: any) => { + await this.kube.waitUntilPodIsDeleted('app=keycloak', this.cheNamespace) + task.title = `${task.title}...done.` + } + }, + { + title: 'Scale \"postgres\" deployment to zero', + enabled: (ctx: any) => ctx.isPostgresDeployed && !ctx.isPostgresStopped, + task: async (_ctx: any, task: any) => { + try { + await this.kube.scaleDeployment('postgres', this.cheNamespace, 0) + task.title = await `${task.title}...done` + } catch (error) { + command.error(`E_SCALE_DEPLOY_FAIL - Failed to scale postgres deployment. ${error.message}`) + } + } + }, + { + title: 'Wait until Postgres pod is deleted', + enabled: (ctx: any) => ctx.isPostgresDeployed && !ctx.isPostgresStopped, + task: async (_ctx: any, task: any) => { + await this.kube.waitUntilPodIsDeleted('app=postgres', this.cheNamespace) + task.title = `${task.title}...done.` + } + }, + { + title: 'Scale \"devfile registry\" deployment to zero', + enabled: (ctx: any) => ctx.isDevfileRegistryDeployed && !ctx.isDevfileRegistryStopped, + task: async (_ctx: any, task: any) => { + try { + await this.kube.scaleDeployment('devfile-registry', this.cheNamespace, 0) + task.title = await `${task.title}...done` + } catch (error) { + command.error(`E_SCALE_DEPLOY_FAIL - Failed to scale devfile-registry deployment. ${error.message}`) + } + } + }, + { + title: 'Wait until Devfile registry pod is deleted', + enabled: (ctx: any) => ctx.isDevfileRegistryDeployed && !ctx.isDevfileRegistryStopped, + task: async (_ctx: any, task: any) => { + await this.kube.waitUntilPodIsDeleted('app=che,component=devfile-registry', this.cheNamespace) + task.title = `${task.title}...done.` + } + }, + { + title: 'Scale \"plugin registry\" deployment to zero', + enabled: (ctx: any) => ctx.isPluginRegistryDeployed && !ctx.isPluginRegistryStopped, + task: async (_ctx: any, task: any) => { + try { + await this.kube.scaleDeployment('plugin-registry', this.cheNamespace, 0) + task.title = await `${task.title}...done` + } catch (error) { + command.error(`E_SCALE_DEPLOY_FAIL - Failed to scale plugin-registry deployment. ${error.message}`) + } + } + }, + { + title: 'Wait until Plugin registry pod is deleted', + enabled: (ctx: any) => ctx.isPluginRegistryDeployed && !ctx.isPluginRegistryStopped, + task: async (_ctx: any, task: any) => { + await this.kube.waitUntilPodIsDeleted('app=che,component=plugin-registry', this.cheNamespace) + task.title = `${task.title}...done.` + } + }] + } + + /** + * Returns tasks which remove all Che related resources. + */ + deleteTasks(flags: any): ReadonlyArray { + return [ + { + title: 'Delete all deployments', + task: async (_ctx: any, task: any) => { + await this.kube.deleteAllDeployments(flags.chenamespace) + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete all services', + task: async (_ctx: any, task: any) => { + await this.kube.deleteAllServices(flags.chenamespace) + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete all ingresses', + enabled: (ctx: any) => !ctx.isOpenShift, + task: async (_ctx: any, task: any) => { + await this.kube.deleteAllIngresses(flags.chenamespace) + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete all routes', + enabled: (ctx: any) => ctx.isOpenShift, + task: async (_ctx: any, task: any) => { + await this.oc.deleteAllRoutes(flags.chenamespace) + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete configmaps che and che-operator', + task: async (_ctx: any, task: any) => { + if (await this.kube.configMapExist('che', flags.chenamespace)) { + await this.kube.deleteConfigMap('che', flags.chenamespace) + } + if (await this.kube.configMapExist('che-operator', flags.chenamespace)) { + await this.kube.deleteConfigMap('che-operator', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete rolebindings che, che-workspace-exec and che-workspace-view', + task: async (_ctx: any, task: any) => { + if (await this.kube.roleBindingExist('che', flags.chenamespace)) { + await this.kube.deleteRoleBinding('che', flags.chenamespace) + } + if (await this.kube.roleBindingExist('che-operator', flags.chenamespace)) { + await this.kube.deleteRoleBinding('che-operator', flags.chenamespace) + } + if (await this.kube.roleBindingExist('che-workspace-exec', flags.chenamespace)) { + await this.kube.deleteRoleBinding('che-workspace-exec', flags.chenamespace) + } + if (await this.kube.roleBindingExist('che-workspace-view', flags.chenamespace)) { + await this.kube.deleteRoleBinding('che-workspace-view', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete service accounts che, che-workspace', + task: async (_ctx: any, task: any) => { + if (await this.kube.serviceAccountExist('che', flags.chenamespace)) { + await this.kube.deleteServiceAccount('che', flags.chenamespace) + } + if (await this.kube.roleBindingExist('che-workspace', flags.chenamespace)) { + await this.kube.deleteServiceAccount('che-workspace', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete PVC postgres-data and che-data-volume', + task: async (_ctx: any, task: any) => { + if (await this.kube.persistentVolumeClaimExist('postgres-data', flags.chenamespace)) { + await this.kube.deletePersistentVolumeClaim('postgres-data', flags.chenamespace) + } + if (await this.kube.persistentVolumeClaimExist('che-data-volume', flags.chenamespace)) { + await this.kube.deletePersistentVolumeClaim('che-data-volume', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }] + } +} diff --git a/src/installers/helm.ts b/src/tasks/installers/helm.ts similarity index 89% rename from src/installers/helm.ts rename to src/tasks/installers/helm.ts index bedf22b81..9c3ffac54 100644 --- a/src/installers/helm.ts +++ b/src/tasks/installers/helm.ts @@ -17,9 +17,12 @@ import * as Listr from 'listr' import { ncp } from 'ncp' import * as path from 'path' -import { KubeHelper } from '../api/kube' +import { KubeHelper } from '../../api/kube' -export class HelmHelper { +export class HelmTasks { + /** + * Returns list of tasks which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { @@ -33,7 +36,7 @@ export class HelmHelper { return flags.tls }, task: async (_ctx: any, task: any) => { - const kh = new KubeHelper() + const kh = new KubeHelper(flags) const tlsSecret = await kh.getSecret('che-tls', `${flags.chenamespace}`) if (!tlsSecret) { @@ -54,7 +57,7 @@ export class HelmHelper { return flags['self-signed-cert'] }, task: async (_ctx: any, task: any) => { - const kh = new KubeHelper() + const kh = new KubeHelper(flags) const selfSignedCertSecret = await kh.getSecret('self-signed-cert', `${flags.chenamespace}`) if (!selfSignedCertSecret) { @@ -132,6 +135,24 @@ export class HelmHelper { ], { renderer: flags['listr-renderer'] as any }) } + /** + * Returns list of tasks which remove helm chart + */ + deleteTasks(_flags: any): ReadonlyArray { + return [{ + title: 'Purge che Helm chart', + enabled: (ctx: any) => !ctx.isOpenShift, + task: async (_ctx: any, task: any) => { + if (await !commandExists.sync('helm')) { + task.title = await `${task.title}...OK (Helm not found)` + } else { + await this.purgeHelmChart('che') + task.title = await `${task.title}...OK` + } + } + }] + } + async tillerRoleBindingExist(execTimeout = 30000): Promise { const { exitCode } = await execa('kubectl', ['get', 'clusterrolebinding', 'add-on-cluster-admin'], { timeout: execTimeout, reject: false }) if (exitCode === 0) { return true } else { return false } @@ -179,7 +200,11 @@ error: E_COMMAND_FAILED`) } } - async prepareCheHelmChart(flags: any, cacheDir: string) { + async purgeHelmChart(name: string, execTimeout = 30000) { + await execa('helm', ['delete', name, '--purge'], { timeout: execTimeout, reject: false }) + } + + private async prepareCheHelmChart(flags: any, cacheDir: string) { const srcDir = path.join(flags.templates, '/kubernetes/helm/che/') const destDir = path.join(cacheDir, '/templates/kubernetes/helm/che/') await remove(destDir) @@ -187,19 +212,22 @@ error: E_COMMAND_FAILED`) await ncp(srcDir, destDir, {}, (err: Error) => { if (err) { throw err } }) } - async updateCheHelmChartDependencies(cacheDir: string, execTimeout = 120000) { + private async updateCheHelmChartDependencies(cacheDir: string, execTimeout = 120000) { const destDir = path.join(cacheDir, '/templates/kubernetes/helm/che/') await execa(`helm dependencies update --skip-refresh ${destDir}`, { timeout: execTimeout, shell: true }) } - async upgradeCheHelmChart(_ctx: any, flags: any, cacheDir: string, execTimeout = 120000) { + private async upgradeCheHelmChart(ctx: any, flags: any, cacheDir: string, execTimeout = 120000) { const destDir = path.join(cacheDir, '/templates/kubernetes/helm/che/') let multiUserFlag = '' let tlsFlag = '' let setOptions = [] + ctx.isCheDeployed = true if (flags.multiuser) { + ctx.isPostgresDeployed = true + ctx.isKeaycloakDeployed = true multiUserFlag = `-f ${destDir}values/multi-user.yaml` } @@ -214,10 +242,14 @@ error: E_COMMAND_FAILED`) if (flags['plugin-registry-url']) { setOptions.push(`--set che.workspace.pluginRegistryUrl=${flags['plugin-registry-url']} --set chePluginRegistry.deploy=false`) + } else { + ctx.isPluginRegistryDeployed = true } if (flags['devfile-registry-url']) { setOptions.push(`--set che.workspace.devfileRegistryUrl=${flags['devfile-registry-url']} --set cheDevfileRegistry.deploy=false`) + } else { + ctx.isDevfileRegistryDeployed = true } setOptions.push(`--set global.ingressDomain=${flags.domain}`) @@ -254,8 +286,4 @@ error: E_COMMAND_FAILED`) } } - async purgeHelmChart(name: string, execTimeout = 30000) { - await execa('helm', ['delete', name, '--purge'], { timeout: execTimeout, reject: false }) - } - } diff --git a/src/tasks/installers/installer.ts b/src/tasks/installers/installer.ts new file mode 100644 index 000000000..3c0f05d1d --- /dev/null +++ b/src/tasks/installers/installer.ts @@ -0,0 +1,61 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import Command from '@oclif/command' +import * as Listr from 'listr' + +import { HelmTasks } from './helm' +import { MinishiftAddonTasks } from './minishift-addon' +import { OperatorTasks } from './operator' + +export class InstallerTasks { + installTasks(flags: any, command: Command): ReadonlyArray { + const helmTasks = new HelmTasks() + const operatorTasks = new OperatorTasks() + const minishiftAddonTasks = new MinishiftAddonTasks() + + let title: string + let task: any + + // let task: Listr.ListrTask + if (flags.installer === 'helm') { + title = '🏃‍ Running Helm to install Che' + task = () => helmTasks.startTasks(flags, command) + } else if (flags.installer === 'operator') { + title = '🏃‍ Running the Che Operator' + task = () => { + // The operator installs Che multiuser only + if (!flags.multiuser) { + command.warn("Che will be deployed in Multi-User mode since Configured 'operator' installer which support only such.") + flags.multiuser = true + } + + return operatorTasks.startTasks(flags, command) + } + } else if (flags.installer === 'minishift-addon') { + // minishift-addon supports Che singleuser only + if (flags.multiuser) { + command.warn("Che will be deployed in Single-User mode since Configured 'minishift-addon' installer which support only such.") + flags.multiuser = false + } + title = '🏃‍ Running the Che minishift-addon' + task = () => minishiftAddonTasks.startTasks(flags, command) + } else { + //TODO + title = '🏃‍ Installer preflight check' + task = () => command.error(`Installer ${flags.installer} is not supported ¯\\_(ツ)_/¯`) + } + + return [{ + title, + task + }] + } +} diff --git a/src/installers/minishift-addon.ts b/src/tasks/installers/minishift-addon.ts similarity index 73% rename from src/installers/minishift-addon.ts rename to src/tasks/installers/minishift-addon.ts index 2a9d3819f..e26a28a96 100644 --- a/src/installers/minishift-addon.ts +++ b/src/tasks/installers/minishift-addon.ts @@ -9,51 +9,27 @@ **********************************************************************/ import Command from '@oclif/command' +import * as commandExists from 'command-exists' import * as execa from 'execa' import { mkdirp, remove } from 'fs-extra' import * as Listr from 'listr' import { ncp } from 'ncp' import * as path from 'path' -import { OpenShiftHelper } from '../api/openshift' - -export class MinishiftAddonHelper { - static getImageRepository(image: string): string { - if (image.includes(':')) { - return image.split(':')[0] - } else { - return image - } - } - - static getImageTag(image: string) { - if (image.includes(':')) { - return image.split(':')[1] - } else { - return 'latest' - } - } - - static async grabVersion(): Promise { - let args = ['version'] - const { stdout } = await execa('minishift', - args, - { reject: false }) - if (stdout) { - return parseInt(stdout.replace(/\D/g, '').substring(0, 3), 10) - } - return -1 - - } - - resourcesPath = '' +import { OpenShiftHelper } from '../../api/openshift' +export class MinishiftAddonTasks { + /** + * Returns list of tasks which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { + let resourcesPath = '' + return new Listr([ { title: 'Check minishift version', task: async (_ctx: any, task: any) => { - const version = await MinishiftAddonHelper.grabVersion() + const version = await this.grabVersion() if (version < 133) { command.error('The minishift che addon is requiring minishift version >= 1.33.0. Please update your minishift installation with "minishift update" command.') } @@ -70,14 +46,14 @@ export class MinishiftAddonHelper { { title: 'Copying addon resources', task: async (_ctx: any, task: any) => { - this.resourcesPath = await this.copyResources(flags.templates, command.config.cacheDir) + resourcesPath = await this.copyResources(flags.templates, command.config.cacheDir) task.title = `${task.title}...done.` } }, { title: 'Check che addon is available', task: async (_ctx: any, task: any) => { - await this.installAddonIfMissing() + await this.installAddonIfMissing(resourcesPath) task.title = `${task.title}...done.` } }, @@ -91,7 +67,59 @@ export class MinishiftAddonHelper { ], { renderer: flags['listr-renderer'] as any }) } - async checkLogged(command: Command) { + /** + * Returns list of tasks which perform removing of addon if minishift is found. + */ + deleteTasks(_flags: any): ReadonlyArray { + return [{ + title: 'Remove Che minishift addon', + enabled: (ctx: any) => ctx.isOpenShift, + task: async (_ctx: any, task: any) => { + if (!commandExists.sync('minishift')) { + task.title = await `${task.title}...OK (minishift not found)` + } else { + await this.removeAddon() + task.title = await `${task.title}...OK` + } + } + } + ] + } + + async removeAddon(execTimeout = 120000) { + let args = ['addon', 'remove', 'che'] + await execa('minishift', args, { timeout: execTimeout, reject: false }) + } + + getImageRepository(image: string): string { + if (image.includes(':')) { + return image.split(':')[0] + } else { + return image + } + } + + getImageTag(image: string) { + if (image.includes(':')) { + return image.split(':')[1] + } else { + return 'latest' + } + } + + async grabVersion(): Promise { + let args = ['version'] + const { stdout } = await execa('minishift', + args, + { reject: false }) + if (stdout) { + return parseInt(stdout.replace(/\D/g, '').substring(0, 3), 10) + } + return -1 + + } + + private async checkLogged(command: Command) { const openshiftHelper = new OpenShiftHelper() const ok = await openshiftHelper.status() if (!ok) { @@ -99,7 +127,7 @@ export class MinishiftAddonHelper { } } - async installAddonIfMissing() { + private async installAddonIfMissing(resourcesPath: string) { let args = ['addon', 'list'] const { stdout } = await execa('minishift', args, @@ -110,15 +138,15 @@ export class MinishiftAddonHelper { } // now install - const addonDir = path.join(this.resourcesPath, 'che') + const addonDir = path.join(resourcesPath, 'che') await this.installAddon(addonDir) } - async applyAddon(flags: any, execTimeout = 120000) { + private async applyAddon(flags: any, execTimeout = 120000) { let args = ['addon', 'apply'] - const imageRepo = MinishiftAddonHelper.getImageRepository(flags.cheimage) - const imageTag = MinishiftAddonHelper.getImageTag(flags.cheimage) + const imageRepo = this.getImageRepository(flags.cheimage) + const imageTag = this.getImageTag(flags.cheimage) args = args.concat(['--addon-env', `NAMESPACE=${flags.chenamespace}`]) args = args.concat(['--addon-env', `CHE_IMAGE_REPO=${imageRepo}`]) args = args.concat(['--addon-env', `CHE_IMAGE_TAG=${imageTag}`]) @@ -150,22 +178,17 @@ error: E_COMMAND_FAILED`) } } - async removeAddon(execTimeout = 120000) { - let args = ['addon', 'remove', 'che'] - await execa('minishift', args, { timeout: execTimeout, reject: false }) - } - - async installAddon(directory: string, execTimeout = 120000) { + private async installAddon(directory: string, execTimeout = 120000) { let args = ['addon', 'install', directory] await execa('minishift', args, { timeout: execTimeout }) } - async uninstallAddon(execTimeout = 120000) { + private async uninstallAddon(execTimeout = 120000) { let args = ['addon', 'uninstall', 'che'] await execa('minishift', args, { timeout: execTimeout }) } - async copyResources(templatesDir: string, cacheDir: string): Promise { + private async copyResources(templatesDir: string, cacheDir: string): Promise { const srcDir = path.join(templatesDir, '/minishift-addon/') const destDir = path.join(cacheDir, '/templates/minishift-addon/') await remove(destDir) diff --git a/src/installers/operator.ts b/src/tasks/installers/operator.ts similarity index 66% rename from src/installers/operator.ts rename to src/tasks/installers/operator.ts index 2b3e00cde..eb694db56 100644 --- a/src/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -15,10 +15,10 @@ import * as Listr from 'listr' import { ncp } from 'ncp' import * as path from 'path' -import { CheHelper } from '../api/che' -import { KubeHelper } from '../api/kube' +import { CheHelper } from '../../api/che' +import { KubeHelper } from '../../api/kube' -export class OperatorHelper { +export class OperatorTasks { operatorServiceAccount = 'che-operator' operatorRole = 'che-operator' operatorClusterRole = 'che-operator' @@ -29,8 +29,11 @@ export class OperatorHelper { operatorCheCluster = 'eclipse-che' resourcesPath = '' + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { - const che = new CheHelper() + const che = new CheHelper(flags) const kube = new KubeHelper(flags) return new Listr([ { @@ -159,11 +162,20 @@ export class OperatorHelper { }, { title: `Create Che Cluster ${this.operatorCheCluster} in namespace ${flags.chenamespace}`, - task: async (_ctx: any, task: any) => { + task: async (ctx: any, task: any) => { const exist = await kube.cheClusterExist(this.operatorCheCluster, flags.chenamespace) if (exist) { task.title = `${task.title}...It already exists.` } else { + // Che Operator supports only Multi-User Che + ctx.isCheDeployed = true + ctx.isPostgresDeployed = true + ctx.isKeycloakDeployed = true + + // plugin and devfile registry will be deployed only when external ones are not configured + ctx.isPluginRegistryDeployed = !(flags['plugin-registry-url'] as boolean) + ctx.isDevfileRegistryDeployed = !(flags['devfile-registry-url'] as boolean) + const yamlFilePath = flags['che-operator-cr-yaml'] === '' ? this.resourcesPath + 'crds/org_v1_che_cr.yaml' : flags['che-operator-cr-yaml'] await kube.createCheClusterFromFile(yamlFilePath, flags, flags['che-operator-cr-yaml'] === '') task.title = `${task.title}...done.` @@ -173,6 +185,96 @@ export class OperatorHelper { ], { renderer: flags['listr-renderer'] as any }) } + /** + * Returns list of tasks which remove che operator related resources + */ + deleteTasks(flags: any): ReadonlyArray { + let kh = new KubeHelper(flags) + return [{ + title: 'Delete the CR eclipse-che of type checlusters.org.eclipse.che', + task: async (_ctx: any, task: any) => { + if (await kh.crdExist('checlusters.org.eclipse.che') && + await kh.cheClusterExist('eclipse-che', flags.chenamespace)) { + await kh.deleteCheCluster('eclipse-che', flags.chenamespace) + await cli.wait(2000) //wait a couple of secs for the finalizers to be executed + task.title = await `${task.title}...OK` + } else { + task.title = await `${task.title}...CR not found` + } + } + }, + { + title: 'Delete CRD checlusters.org.eclipse.che', + task: async (_ctx: any, task: any) => { + if (await kh.crdExist('checlusters.org.eclipse.che')) { + await kh.deleteCrd('checlusters.org.eclipse.che') + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete role che-operator', + task: async (_ctx: any, task: any) => { + if (await kh.roleExist('che-operator', flags.chenamespace)) { + await kh.deleteRole('che-operator', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete cluster role binding che-operator', + task: async (_ctx: any, task: any) => { + if (await kh.clusterRoleBindingExist('che-operator')) { + await kh.deleteClusterRoleBinding('che-operator') + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete cluster role che-operator', + task: async (_ctx: any, task: any) => { + if (await kh.clusterRoleExist('che-operator')) { + await kh.deleteClusterRole('che-operator') + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete rolebinding che-operator', + task: async (_ctx: any, task: any) => { + if (await kh.roleBindingExist('che', flags.chenamespace)) { + await kh.deleteRoleBinding('che', flags.chenamespace) + } + if (await kh.roleBindingExist('che-workspace-exec', flags.chenamespace)) { + await kh.deleteRoleBinding('che-workspace-exec', flags.chenamespace) + } + if (await kh.roleBindingExist('che-workspace-view', flags.chenamespace)) { + await kh.deleteRoleBinding('che-workspace-view', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete service accounts che-operator', + task: async (_ctx: any, task: any) => { + if (await kh.roleBindingExist('che-operator', flags.chenamespace)) { + await kh.deleteServiceAccount('che-operator', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + { + title: 'Delete PVC che-operator', + task: async (_ctx: any, task: any) => { + if (await kh.persistentVolumeClaimExist('che-operator', flags.chenamespace)) { + await kh.deletePersistentVolumeClaim('che-operator', flags.chenamespace) + } + task.title = await `${task.title}...OK` + } + }, + ] + } + async copyCheOperatorResources(templatesDir: string, cacheDir: string): Promise { const srcDir = path.join(templatesDir, '/che-operator/') const destDir = path.join(cacheDir, '/templates/che-operator/') diff --git a/src/tasks/kube.ts b/src/tasks/kube.ts new file mode 100644 index 000000000..93ea4378e --- /dev/null +++ b/src/tasks/kube.ts @@ -0,0 +1,58 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { Command } from '@oclif/command' +import * as Listr from 'listr' + +import { KubeHelper } from '../api/kube' + +export class KubeTasks { + kube: KubeHelper + debug = require('debug') + constructor(flags?: any) { + this.kube = new KubeHelper(flags) + } + + podStartTasks(_command: Command, selector: string, namespace = ''): Listr { + return new Listr([ + { + title: 'scheduling', + task: async (_ctx: any, task: any) => { + let phase + const title = task.title + try { + phase = await this.kube.getPodPhase(selector, namespace) + } catch (err) { + // not able to grab current phase + this.debug(err) + } + // wait only if not yet running + if (phase !== 'Running') { + await this.kube.waitForPodPending(selector, namespace) + } + task.title = `${title}...done.` + } + }, + { + title: 'downloading images', + task: async (_ctx: any, task: any) => { + await this.kube.waitForPodPhase(selector, 'Running', namespace) + task.title = `${task.title}...done.` + } + }, + { + title: 'starting', + task: async (_ctx: any, task: any) => { + await this.kube.waitForPodReady(selector, namespace) + task.title = `${task.title}...done.` + } + } + ]) + } +} diff --git a/src/platforms/crc.ts b/src/tasks/platforms/crc.ts similarity index 100% rename from src/platforms/crc.ts rename to src/tasks/platforms/crc.ts diff --git a/src/platforms/docker-desktop.ts b/src/tasks/platforms/docker-desktop.ts similarity index 94% rename from src/platforms/docker-desktop.ts rename to src/tasks/platforms/docker-desktop.ts index 610689868..bd8686c55 100644 --- a/src/platforms/docker-desktop.ts +++ b/src/tasks/platforms/docker-desktop.ts @@ -14,15 +14,18 @@ import * as execa from 'execa' import * as Listr from 'listr' import * as os from 'os' -import { KubeHelper } from '../api/kube' +import { KubeHelper } from '../../api/kube' -export class DockerDesktopHelper { +export class DockerDesktopTasks { private readonly kh: KubeHelper - constructor() { - this.kh = new KubeHelper() + constructor(flags: any) { + this.kh = new KubeHelper(flags) } + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { diff --git a/src/platforms/k8s.ts b/src/tasks/platforms/k8s.ts similarity index 60% rename from src/platforms/k8s.ts rename to src/tasks/platforms/k8s.ts index 6e80b0ad2..987cd5910 100644 --- a/src/platforms/k8s.ts +++ b/src/tasks/platforms/k8s.ts @@ -12,9 +12,36 @@ import { Command } from '@oclif/command' import * as commandExists from 'command-exists' import * as Listr from 'listr' -import { KubeHelper } from '../api/kube' +import { KubeHelper } from '../../api/kube' -export class K8sHelper { +export class K8sTasks { + /** + * Returns tasks which tests if K8s or OpenShift API is configured in the current context. + * + * `isOpenShift` property is provisioned into context. + */ + testApiTasks(flags: any, command: Command): Listr.ListrTask { + let kube = new KubeHelper(flags) + return { + title: 'Verify Kubernetes API', + task: async (ctx: any, task: any) => { + try { + await kube.checkKubeApi() + ctx.isOpenShift = await kube.isOpenShift() + task.title = await `${task.title}...OK` + if (ctx.isOpenShift) { + task.title = await `${task.title} (it's OpenShift)` + } + } catch (error) { + command.error(`Failed to connect to Kubernetes API. ${error.message}`) + } + } + } + } + + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { @@ -28,7 +55,7 @@ export class K8sHelper { { title: 'Verify remote kubernetes status', task: async (_ctx: any, task: any) => { - const kh = new KubeHelper() + const kh = new KubeHelper(flags) try { await kh.checkKubeApi() task.title = `${task.title}...done.` @@ -47,7 +74,8 @@ export class K8sHelper { task.title = `${task.title}...set to ${flags.domain}.` } }, - ], { renderer: flags['listr-renderer'] as any }) + ], + { renderer: flags['listr-renderer'] as any } + ) } - } diff --git a/src/platforms/microk8s.ts b/src/tasks/platforms/microk8s.ts similarity index 97% rename from src/platforms/microk8s.ts rename to src/tasks/platforms/microk8s.ts index 1e7da77e9..fe5af2358 100644 --- a/src/platforms/microk8s.ts +++ b/src/tasks/platforms/microk8s.ts @@ -13,7 +13,10 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' -export class MicroK8sHelper { +export class MicroK8sTasks { + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { diff --git a/src/platforms/minikube.ts b/src/tasks/platforms/minikube.ts similarity index 96% rename from src/platforms/minikube.ts rename to src/tasks/platforms/minikube.ts index 03f88a9cb..4bcf58e2d 100644 --- a/src/platforms/minikube.ts +++ b/src/tasks/platforms/minikube.ts @@ -13,7 +13,10 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' -export class MinikubeHelper { +export class MinikubeTasks { + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { diff --git a/src/platforms/minishift.ts b/src/tasks/platforms/minishift.ts similarity index 95% rename from src/platforms/minishift.ts rename to src/tasks/platforms/minishift.ts index 2e6e685c7..0d5b63e4a 100644 --- a/src/platforms/minishift.ts +++ b/src/tasks/platforms/minishift.ts @@ -13,7 +13,10 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' -export class MinishiftHelper { +export class MinishiftTasks { + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { diff --git a/src/platforms/openshift.ts b/src/tasks/platforms/openshift.ts similarity index 93% rename from src/platforms/openshift.ts rename to src/tasks/platforms/openshift.ts index 5269a824e..8bd16dc5f 100644 --- a/src/platforms/openshift.ts +++ b/src/tasks/platforms/openshift.ts @@ -13,7 +13,10 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' -export class OpenshiftHelper { +export class OpenshiftTasks { + /** + * Returns tasks list which perform preflight platform checks. + */ startTasks(flags: any, command: Command): Listr { return new Listr([ { diff --git a/src/tasks/platforms/platform.ts b/src/tasks/platforms/platform.ts new file mode 100644 index 000000000..59f571d4e --- /dev/null +++ b/src/tasks/platforms/platform.ts @@ -0,0 +1,81 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import Command from '@oclif/command' +import * as Listr from 'listr' + +import { CRCHelper } from './crc' +import { DockerDesktopTasks } from './docker-desktop' +import { K8sTasks } from './k8s' +import { MicroK8sTasks } from './microk8s' +import { MinikubeTasks } from './minikube' +import { MinishiftTasks } from './minishift' +import { OpenshiftTasks } from './openshift' + +export class PlatformTasks { + preflightCheckTasks(flags: any, command: Command): ReadonlyArray { + const minikubeTasks = new MinikubeTasks() + const microk8sTasks = new MicroK8sTasks() + const minishiftTasks = new MinishiftTasks() + const openshiftTasks = new OpenshiftTasks() + const k8sTasks = new K8sTasks() + const crc = new CRCHelper() + const dockerDesktopTasks = new DockerDesktopTasks(flags) + + let task: Listr.ListrTask + if (!flags.platform) { + task = { + title: '✈️ Platform preflight checklist', + task: () => command.error('Platform is required ¯\\_(ツ)_/¯') + } + } else if (flags.platform === 'minikube') { + task = { + title: '✈️ Minikube preflight checklist', + task: () => minikubeTasks.startTasks(flags, command) + } + } else if (flags.platform === 'minishift') { + task = { + title: '✈️ Minishift preflight checklist', + task: () => minishiftTasks.startTasks(flags, command) + } + } else if (flags.platform === 'microk8s') { + task = { + title: '✈️ MicroK8s preflight checklist', + task: () => microk8sTasks.startTasks(flags, command) + } + } else if (flags.platform === 'openshift') { + task = { + title: '✈️ Openshift preflight checklist', + task: () => openshiftTasks.startTasks(flags, command) + } + } else if (flags.platform === 'k8s') { + task = { + title: '✈️ Kubernetes preflight checklist', + task: () => k8sTasks.startTasks(flags, command) + } + } else if (flags.platform === 'docker-desktop') { + task = { + title: '✈️ Docker Desktop preflight checklist', + task: () => dockerDesktopTasks.startTasks(flags, command) + } + } else if (flags.platform === 'crc') { + task = { + title: '✈️ CodeReady Containers preflight checklist', + task: () => crc.startTasks(flags, command) + } + } else { + task = { + title: '✈️ Platform preflight checklist', + task: () => { command.error(`Platform ${flags.platform} is not supported yet ¯\\_(ツ)_/¯`) } + } + } + + return [task] + } +} diff --git a/test/api/che.test.ts b/test/api/che.test.ts index 3b7d5fe37..a4ae3a949 100644 --- a/test/api/che.test.ts +++ b/test/api/che.test.ts @@ -17,46 +17,45 @@ const workspace = 'workspace-0123' const cheURL = 'https://che-che.192.168.64.34.nip.io' const devfileServerURL = 'https://devfile-server' const devfileEndpoint = '/api/workspace/devfile' -let ch = new CheHelper() +let ch = new CheHelper({}) let kc = ch.kc let kube = ch.kube let oc = ch.oc let k8sApi = new Core_v1Api() - describe('Che helper', () => { describe('cheURL', () => { fancy .stub(ch, 'cheNamespaceExist', () => true) - .stub(kube, "isOpenShift", () => false) - .stub(kube, "ingressExist", () => true) - .stub(kube, "getIngressProtocol", () => "https") - .stub(kube, "getIngressHost", () => "example.org") + .stub(kube, 'isOpenShift', () => false) + .stub(kube, 'ingressExist', () => true) + .stub(kube, 'getIngressProtocol', () => 'https') + .stub(kube, 'getIngressHost', () => 'example.org') .it('computes Che URL on K8s', async () => { const cheURL = await ch.cheURL('che-namespace') - expect(cheURL).to.equals("https://example.org") + expect(cheURL).to.equals('https://example.org') }) fancy .stub(ch, 'cheNamespaceExist', () => true) - .stub(kube, "isOpenShift", () => false) - .stub(kube, "ingressExist", () => false) + .stub(kube, 'isOpenShift', () => false) + .stub(kube, 'ingressExist', () => false) .do(() => ch.cheURL('che-namespace')) .catch(err => expect(err.message).to.match(/ERR_INGRESS_NO_EXIST/)) .it('fails fetching che URL when ingress does not exist') fancy .stub(ch, 'cheNamespaceExist', () => true) - .stub(kube, "isOpenShift", () => true) - .stub(oc, "routeExist", () => true) - .stub(oc, "getRouteProtocol", () => "https") - .stub(oc, "getRouteHost", () => "example.org") + .stub(kube, 'isOpenShift', () => true) + .stub(oc, 'routeExist', () => true) + .stub(oc, 'getRouteProtocol', () => 'https') + .stub(oc, 'getRouteHost', () => 'example.org') .it('computes Che URL on OpenShift', async () => { const cheURL = await ch.cheURL('che-namespace') - expect(cheURL).to.equals("https://example.org") + expect(cheURL).to.equals('https://example.org') }) fancy .stub(ch, 'cheNamespaceExist', () => true) - .stub(kube, "isOpenShift", () => true) - .stub(oc, "routeExist", () => false) + .stub(kube, 'isOpenShift', () => true) + .stub(oc, 'routeExist', () => false) .do(() => ch.cheURL('che-namespace')) .catch(/ERR_ROUTE_NO_EXIST/) .it('fails fetching che URL when route does not exist') diff --git a/test/api/kube.test.ts b/test/api/kube.test.ts index 550482167..b0731243f 100644 --- a/test/api/kube.test.ts +++ b/test/api/kube.test.ts @@ -100,9 +100,9 @@ describe('Kube API helper', () => { }) fancy .nock(kubeClusterURL, api => api - .get(`/api/v1/namespaces/default/serviceaccounts`) + .get('/api/v1/namespaces/default/serviceaccounts') .replyWithFile(200, __dirname + '/replies/get-serviceaccounts.json', { 'Content-Type': 'application/json' }) - .get(`/api/v1/namespaces/default/secrets`) + .get('/api/v1/namespaces/default/secrets') .replyWithFile(200, __dirname + '/replies/get-secrets.json', { 'Content-Type': 'application/json' }) .get('/healthz') .reply(200, 'ok')) diff --git a/test/e2e/minikube.test.ts b/test/e2e/minikube.test.ts index 168b8f508..c5e2a0a64 100644 --- a/test/e2e/minikube.test.ts +++ b/test/e2e/minikube.test.ts @@ -28,7 +28,7 @@ describe('e2e test', () => { describe('server:start without parameters', () => { test .stdout() - .command(['server:start', '--listr-renderer=verbose']) + .command(['server:start', '--platform=minikube', '--listr-renderer=verbose']) .exit(0) .it('uses minikube as platform, helm as installer and auth is disabled', ctx => { expect(ctx.stdout).to.contain('Minikube preflight checklist') @@ -50,7 +50,7 @@ describe('e2e test', () => { describe('server:start mulituser', () => { test .stdout() - .command(['server:start', '--listr-renderer=verbose', '--multiuser']) + .command(['server:start', '--platform=minikube', '--listr-renderer=verbose', '--multiuser']) .exit(0) .it('uses minikube as platform, operator as installer and auth is enabled', ctx => { expect(ctx.stdout).to.contain('Minikube preflight checklist') diff --git a/test/e2e/minishift.test.ts b/test/e2e/minishift.test.ts index f63f96a9b..550fb2852 100644 --- a/test/e2e/minishift.test.ts +++ b/test/e2e/minishift.test.ts @@ -13,9 +13,11 @@ jest.setTimeout(600000) /* ## Before +# Note that VM Driver value should be set accordanly to your platform +VM_DRIVER=xhyve && \ PROFILE=chectl-e2e-tests && \ minishift profile set ${PROFILE} && \ -minishift start --memory=8GB --cpus=4 --disk-size=50g --vm-driver=xhyve --network-nameserver 8.8.8.8 --profile ${PROFILE} +minishift start --memory=8GB --cpus=4 --disk-size=50g --vm-driver=${VM_DRIVER} --network-nameserver 8.8.8.8 --profile ${PROFILE} yarn test --coverage=false --testRegex=/test/e2e/minishift.test.ts diff --git a/test/installers/minishift-addon.test.ts b/test/tasks/installers/minishift-addon.test.ts similarity index 75% rename from test/installers/minishift-addon.test.ts rename to test/tasks/installers/minishift-addon.test.ts index f7dc0b80b..d935aca96 100644 --- a/test/installers/minishift-addon.test.ts +++ b/test/tasks/installers/minishift-addon.test.ts @@ -7,39 +7,40 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +// tslint:disable:object-curly-spacing import { expect, fancy } from 'fancy-test' -import * as execa from 'execa' -import { MinishiftAddonHelper } from '../../src/installers/minishift-addon' +import { MinishiftAddonTasks } from '../../../src/tasks/installers/minishift-addon' jest.mock('execa') +let minishiftAddonTasks = new MinishiftAddonTasks() describe('Minishift addon helper', () => { fancy .it('extracts the tag part from an image name', async () => { const image = 'eclipse/che:latest' - const tag = MinishiftAddonHelper.getImageTag(image) + const tag = minishiftAddonTasks.getImageTag(image) expect(tag).to.equal('latest') }) fancy .it('extracts the repo part from an image name', async () => { const image = 'eclipse/che:latest' - const repository = MinishiftAddonHelper.getImageRepository(image) + const repository = minishiftAddonTasks.getImageRepository(image) expect(repository).to.equal('eclipse/che') }) fancy .it('returns the repo part even if an image has no tag', async () => { const image = 'eclipse/che' - const repository = MinishiftAddonHelper.getImageRepository(image) + const repository = minishiftAddonTasks.getImageRepository(image) expect(repository).to.equal('eclipse/che') }) fancy .it('returns latest as tag if an image has no tag', async () => { const image = 'eclipse/che' - const tag = MinishiftAddonHelper.getImageTag(image) + const tag = minishiftAddonTasks.getImageTag(image) expect(tag).to.equal('latest') }) @@ -47,7 +48,7 @@ describe('Minishift addon helper', () => { .it('check grab Version 1.34', async () => { const minishiftVersionOutput = 'minishift v1.34.0+f5db7cb'; (execa as any).mockResolvedValue({ exitCode: 0, stdout: minishiftVersionOutput }) - const version = await MinishiftAddonHelper.grabVersion(); + const version = await minishiftAddonTasks.grabVersion(); expect(version).to.equal(134) }) @@ -55,9 +56,7 @@ describe('Minishift addon helper', () => { .it('check grab Version 1.33', async () => { const minishiftVersionOutput = 'minishift v1.33.0+ba29431'; (execa as any).mockResolvedValue({ exitCode: 0, stdout: minishiftVersionOutput }) - const version = await MinishiftAddonHelper.grabVersion(); + const version = await minishiftAddonTasks.grabVersion(); expect(version).to.equal(133) }) - - }) diff --git a/test/platforms/crc.test.ts b/test/tasks/platforms/crc.test.ts similarity index 100% rename from test/platforms/crc.test.ts rename to test/tasks/platforms/crc.test.ts diff --git a/test/platforms/microk8s.test.ts b/test/tasks/platforms/microk8s.test.ts similarity index 89% rename from test/platforms/microk8s.test.ts rename to test/tasks/platforms/microk8s.test.ts index 9b8ea888d..5c70df6ad 100644 --- a/test/platforms/microk8s.test.ts +++ b/test/tasks/platforms/microk8s.test.ts @@ -7,13 +7,14 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import * as execa from 'execa' import { expect, fancy } from 'fancy-test' -import { MicroK8sHelper } from '../../src/platforms/microk8s'; -import * as execa from 'execa'; -jest.mock('execa'); +import { MicroK8sTasks } from '../../../src/tasks/platforms/microk8s' -let mh = new MicroK8sHelper() +jest.mock('execa') + +let mh = new MicroK8sTasks() describe('start', () => { fancy diff --git a/test/platforms/minikube.test.ts b/test/tasks/platforms/minikube.test.ts similarity index 86% rename from test/platforms/minikube.test.ts rename to test/tasks/platforms/minikube.test.ts index 805709a23..eb5c23ccf 100644 --- a/test/platforms/minikube.test.ts +++ b/test/tasks/platforms/minikube.test.ts @@ -7,13 +7,14 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import * as execa from 'execa' import { expect, fancy } from 'fancy-test' -import { MinikubeHelper } from '../../src/platforms/minikube'; -import * as execa from 'execa'; -jest.mock('execa'); +import { MinikubeTasks } from '../../../src/tasks/platforms/minikube' -let mh = new MinikubeHelper() +jest.mock('execa') + +let mh = new MinikubeTasks() describe('start', () => { fancy diff --git a/test/platforms/minishift.test.ts b/test/tasks/platforms/minishift.test.ts similarity index 95% rename from test/platforms/minishift.test.ts rename to test/tasks/platforms/minishift.test.ts index ee193cca3..3d419b5bf 100644 --- a/test/platforms/minishift.test.ts +++ b/test/tasks/platforms/minishift.test.ts @@ -10,11 +10,11 @@ import * as execa from 'execa' import { expect, fancy } from 'fancy-test' -import { MinishiftHelper } from '../../src/platforms/minishift' +import { MinishiftTasks } from '../../../src/tasks/platforms/minishift' jest.mock('execa') -let ms = new MinishiftHelper() +let ms = new MinishiftTasks() describe('start', () => { fancy diff --git a/test/platforms/openshift.test.ts b/test/tasks/platforms/openshift.test.ts similarity index 93% rename from test/platforms/openshift.test.ts rename to test/tasks/platforms/openshift.test.ts index f5c9255dc..fe199088c 100644 --- a/test/platforms/openshift.test.ts +++ b/test/tasks/platforms/openshift.test.ts @@ -10,30 +10,30 @@ import * as execa from 'execa' import { expect, fancy } from 'fancy-test' -import { OpenshiftHelper } from '../../src/platforms/openshift' +import { OpenshiftTasks } from '../../../src/tasks/platforms/openshift' jest.mock('execa') -let openshift = new OpenshiftHelper() +let openshift = new OpenshiftTasks() describe('start', () => { fancy .it('confirms that openshift is running when it does run', async () => { const status = `In project che on server https://master.rhpds311.openshift.opentlc.com:443 - + http://che-che.apps.rhpds311.openshift.opentlc.com (svc/che-host) deployment/che deploys eclipse/che-server:latest deployment #1 running for 18 hours - 1 pod - + http://keycloak-che.apps.rhpds311.openshift.opentlc.com (svc/keycloak) deployment/keycloak deploys registry.access.redhat.com/redhat-sso-7/sso72-openshift:1.2-8 deployment #1 running for 18 hours - 1 pod - + svc/postgres - 172.30.187.205:5432 deployment/postgres deploys registry.access.redhat.com/rhscl/postgresql-96-rhel7:1-25 deployment #1 running for 18 hours - 1 pod - - + + 3 infos identified, use 'oc status --suggest' to see details.`; (execa as any).mockResolvedValue({ exitCode: 0, stdout: status }) diff --git a/types/listr-options.ts b/types/listr-options.ts new file mode 100644 index 000000000..454b43b8d --- /dev/null +++ b/types/listr-options.ts @@ -0,0 +1,26 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +import * as Listr from 'listr' + +export namespace ListrOptions { + /** + * Returns ListrOptions for tasks rendering. + * + * @param listRenderer listRenderer that should be used + */ + export function getTasksListrOptions(listRenderer: any): Listr.ListrOptions { + return { + renderer: listRenderer as any, + collapse: false, + showSubtasks: true + } + } +} diff --git a/yarn.lock b/yarn.lock index 716e44bef..8fe788ea4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1666,17 +1666,17 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -"eclipse-che-minishift@git://github.com/minishift/minishift#master": +"eclipse-che-minishift@git://github.com/sleshchenko/minishift#updateChe": version "0.0.0" - resolved "git://github.com/minishift/minishift#c2ff9cb6d7ac70b2eb4fa2c66d3593989b107600" + resolved "git://github.com/sleshchenko/minishift#fb988bebebe933297820ee5ae43e489ab1da91ac" "eclipse-che-operator@git://github.com/eclipse/che-operator#master": version "0.0.0" - resolved "git://github.com/eclipse/che-operator#9682f3448fe240c216aa22d9d3f56cc056b01494" + resolved "git://github.com/eclipse/che-operator#92384460142e2aca601443d10736e17b8270754f" "eclipse-che@git://github.com/eclipse/che#master": version "0.0.0" - resolved "git://github.com/eclipse/che#40ee503a054b60c7b6dc05928d6c0f8d791b2745" + resolved "git://github.com/eclipse/che#bc900205dfd7f84320195ef883166e4643976407" editorconfig@^0.15.0: version "0.15.3"