diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f21d3b51..4611a7929 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,36 @@ "skipFiles": [ "/**" ] + }, + { + "type": "node", + "request": "launch", + "name": "Jest All", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": [ + "--runInBand" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + } + }, + { + "type": "node", + "request": "launch", + "name": "Jest Current File", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": [ + "${fileBasenameNoExtension}" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + } } ] } diff --git a/README.md b/README.md index b71e27f62..2e0d6882d 100644 --- a/README.md +++ b/README.md @@ -180,8 +180,8 @@ USAGE OPTIONS -h, --help show CLI help - -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Che server is supposed to be - deployed + -n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Eclipse Che server is supposed to + be deployed --debug-port=debug-port [default: 8000] Eclipse Che Server debug port @@ -316,6 +316,9 @@ OPTIONS Authorize usage of self signed certificates for encryption. Note that `self-signed-cert` secret with CA certificate must be created in the configured namespace. + --skip-version-check + Skip minimal versions check. + --workspace-pvc-storage-class-name=workspace-pvc-storage-class-name persistent volume(s) storage class name to use to store Eclipse Che workspaces data ``` diff --git a/src/api/version.ts b/src/api/version.ts new file mode 100644 index 000000000..2b67489b1 --- /dev/null +++ b/src/api/version.ts @@ -0,0 +1,133 @@ +/********************************************************************* + * Copyright (c) 2020 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 execa = require('execa') +import Listr = require('listr') + +export namespace VersionHelper { + export const MINIMAL_OPENSHIFT_VERSION = '3.11' + export const MINIMAL_K8S_VERSION = '1.9' + export const MINIMAL_HELM_VERSION = '2.15' + + export function getOpenShiftCheckVersionTask(flags: any): Listr.ListrTask { + return { + title: 'Check OpenShift version', + task: async (_ctx: any, task: any) => { + const actualVersion = await getOpenShiftVersion() + if (actualVersion) { + task.title = `${task.title}: Found ${actualVersion}.` + } else { + task.title = `${task.title}: Unknown.` + } + + if (!flags['skip-version-check'] && actualVersion) { + const checkPassed = checkMinimalVersion(actualVersion, MINIMAL_OPENSHIFT_VERSION) + if (!checkPassed) { + throw getError('OpenShift', actualVersion, MINIMAL_OPENSHIFT_VERSION) + } + } + } + } + } + export function getK8sCheckVersionTask(flags: any): Listr.ListrTask { + return { + title: 'Check Kubernetes version', + task: async (_ctx: any, task: any) => { + let actualVersion + switch (flags.platform) { + case 'minishift': + case 'openshift': + case 'crc': + actualVersion = await getK8sVersionWithOC() + break + default: + actualVersion = await getK8sVersionWithKubectl() + } + + if (actualVersion) { + task.title = `${task.title}: Found ${actualVersion}.` + } else { + task.title = `${task.title}: Unknown.` + } + + if (!flags['skip-version-check'] && actualVersion) { + const checkPassed = checkMinimalVersion(actualVersion, MINIMAL_K8S_VERSION) + if (!checkPassed) { + throw getError('Kubernetes', actualVersion, MINIMAL_K8S_VERSION) + } + } + } + } + } + + export async function getOpenShiftVersion(): Promise { + return getVersionWithOC('openshift ') + } + + export async function getK8sVersionWithOC(): Promise { + return getVersionWithOC('kubernetes ') + } + + export async function getK8sVersionWithKubectl(): Promise { + return getVersionWithKubectl('Server Version: ') + } + + export function checkMinimalK8sVersion(actualVersion: string): boolean { + return checkMinimalVersion(actualVersion, MINIMAL_K8S_VERSION) + } + + export function checkMinimalOpenShiftVersion(actualVersion: string): boolean { + return checkMinimalVersion(actualVersion, MINIMAL_OPENSHIFT_VERSION) + } + + export function checkMinimalHelmVersion(actualVersion: string): boolean { + return checkMinimalVersion(actualVersion, MINIMAL_HELM_VERSION) + } + + /** + * Compare versions and return true if actual version is greater or equal to minimal. + * The comparison will be done by major and minor versions. + */ + export function checkMinimalVersion(actual: string, minimal: string): boolean { + actual = removeVPrefix(actual) + let vers = actual.split('.') + const actualMajor = parseInt(vers[0], 10) + const actualMinor = parseInt(vers[1], 10) + + minimal = removeVPrefix(minimal) + vers = minimal.split('.') + const minimalMajor = parseInt(vers[0], 10) + const minimalMinor = parseInt(vers[1], 10) + + return (actualMajor > minimalMajor || (actualMajor === minimalMajor && actualMinor >= minimalMinor)) + } + + export function getError(actualVersion: string, minimalVersion: string, component: string): Error { + return new Error(`The minimal supported version of ${component} is '${minimalVersion} but found '${actualVersion}'. To bypass version check use '--skip-version-check' flag.`) + } + + async function getVersionWithOC(versionPrefix: string): Promise { + const command = 'oc' + const args = ['version'] + const { stdout } = await execa(command, args, { timeout: 60000 }) + return stdout.split('\n').filter(value => value.startsWith(versionPrefix)).map(value => value.substring(versionPrefix.length))[0] + } + + async function getVersionWithKubectl(versionPrefix: string): Promise { + const command = 'kubectl' + const args = ['version', '--short'] + const { stdout } = await execa(command, args, { timeout: 60000 }) + return stdout.split('\n').filter(value => value.startsWith(versionPrefix)).map(value => value.substring(versionPrefix.length))[0] + } + + function removeVPrefix(version: string): string { + return version.startsWith('v') ? version.substring(1) : version + } +} diff --git a/src/commands/server/start.ts b/src/commands/server/start.ts index 20cfe2676..f31cfc904 100644 --- a/src/commands/server/start.ts +++ b/src/commands/server/start.ts @@ -127,7 +127,11 @@ export default class Start extends Command { 'postgres-pvc-storage-class-name': string({ description: 'persistent volume storage class name to use to store Eclipse Che Postgres database', default: '' - }) + }), + 'skip-version-check': flags.boolean({ + description: 'Skip minimal versions check.', + default: false + }), } static getTemplatesDir(): string { @@ -270,7 +274,7 @@ export default class Start extends Command { await postInstallTasks.run(ctx) this.log('Command server:start has completed successfully.') } catch (err) { - this.error(err) + this.error(`${err}\nInstallation failed, check logs in '${ctx.directory}'`) } notifier.notify({ diff --git a/src/tasks/installers/helm.ts b/src/tasks/installers/helm.ts index b77d247cf..a39a9225f 100644 --- a/src/tasks/installers/helm.ts +++ b/src/tasks/installers/helm.ts @@ -18,6 +18,7 @@ import * as path from 'path' import { CheHelper } from '../../api/che' import { KubeHelper } from '../../api/kube' +import { VersionHelper } from '../../api/version' export class HelmTasks { /** @@ -34,10 +35,16 @@ export class HelmTasks { task: async (ctx: any, task: any) => { try { const version = await this.getVersion() - if (version.startsWith('v3.')) { - ctx.isHelmV3 = true + ctx.isHelmV3 = version.startsWith('v3.') + + if (!flags['skip-version-check']) { + const checkPassed = VersionHelper.checkMinimalHelmVersion(version) + if (!checkPassed) { + throw VersionHelper.getError(version, VersionHelper.MINIMAL_HELM_VERSION, 'helm') + } } - task.title = await `${task.title}: Found ${version}` + + task.title = `${task.title}: Found ${version}` } catch (error) { command.error(`Unable to get helm version. ${error.message}`) } diff --git a/src/tasks/platforms/crc.ts b/src/tasks/platforms/crc.ts index b5b6cbf00..6896e7b94 100644 --- a/src/tasks/platforms/crc.ts +++ b/src/tasks/platforms/crc.ts @@ -13,6 +13,8 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' +import { VersionHelper } from '../../api/version' + /** * Helper for Code Ready Container */ @@ -50,6 +52,8 @@ export class CRCHelper { } } }, + VersionHelper.getOpenShiftCheckVersionTask(flags), + VersionHelper.getK8sCheckVersionTask(flags), { title: 'Retrieving CodeReady Containers IP and domain for routes URLs', enabled: () => flags.domain !== undefined, diff --git a/src/tasks/platforms/docker-desktop.ts b/src/tasks/platforms/docker-desktop.ts index c765a0df8..7e4fd5b21 100644 --- a/src/tasks/platforms/docker-desktop.ts +++ b/src/tasks/platforms/docker-desktop.ts @@ -15,6 +15,7 @@ import * as Listr from 'listr' import * as os from 'os' import { KubeHelper } from '../../api/kube' +import { VersionHelper } from '../../api/version' export class DockerDesktopTasks { private readonly kh: KubeHelper @@ -58,6 +59,7 @@ export class DockerDesktopTasks { } } }, + VersionHelper.getK8sCheckVersionTask(flags), { title: 'Verify if nginx ingress is installed', task: async (ctx: any) => { diff --git a/src/tasks/platforms/k8s.ts b/src/tasks/platforms/k8s.ts index fe4bc3ccf..f7a61ddf5 100644 --- a/src/tasks/platforms/k8s.ts +++ b/src/tasks/platforms/k8s.ts @@ -13,6 +13,7 @@ import * as commandExists from 'command-exists' import * as Listr from 'listr' import { KubeHelper } from '../../api/kube' +import { VersionHelper } from '../../api/version' export class K8sTasks { /** @@ -64,6 +65,7 @@ export class K8sTasks { } } }, + VersionHelper.getK8sCheckVersionTask(flags), // Should automatically compute route if missing { title: 'Verify domain is set', diff --git a/src/tasks/platforms/microk8s.ts b/src/tasks/platforms/microk8s.ts index ad61dd3e0..49390acba 100644 --- a/src/tasks/platforms/microk8s.ts +++ b/src/tasks/platforms/microk8s.ts @@ -13,6 +13,8 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' +import { VersionHelper } from '../../api/version' + export class MicroK8sTasks { /** * Returns tasks list which perform preflight platform checks. @@ -54,8 +56,7 @@ export class MicroK8sTasks { command.error('MicroK8s is not running.', { code: 'E_REQUISITE_NOT_RUNNING' }) } }, - // { title: 'Verify microk8s memory configuration', skip: () => 'Not implemented yet', task: () => {}}, - // { title: 'Verify kubernetes version', skip: () => 'Not implemented yet', task: () => {}}, + VersionHelper.getK8sCheckVersionTask(flags), { title: 'Verify if microk8s ingress and storage addons is enabled', task: async (ctx: any) => { diff --git a/src/tasks/platforms/minikube.ts b/src/tasks/platforms/minikube.ts index aef72de6c..0e6849725 100644 --- a/src/tasks/platforms/minikube.ts +++ b/src/tasks/platforms/minikube.ts @@ -13,6 +13,8 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' +import { VersionHelper } from '../../api/version' + export class MinikubeTasks { /** * Returns tasks list which perform preflight platform checks. @@ -50,8 +52,7 @@ export class MinikubeTasks { }, task: () => this.startMinikube() }, - // { title: 'Verify minikube memory configuration', skip: () => 'Not implemented yet', task: () => {}}, - // { title: 'Verify kubernetes version', skip: () => 'Not implemented yet', task: () => {}}, + VersionHelper.getK8sCheckVersionTask(flags), { title: 'Verify if minikube ingress addon is enabled', task: async (ctx: any) => { diff --git a/src/tasks/platforms/minishift.ts b/src/tasks/platforms/minishift.ts index 18ea9fbb0..4611a4ece 100644 --- a/src/tasks/platforms/minishift.ts +++ b/src/tasks/platforms/minishift.ts @@ -13,6 +13,8 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' +import { VersionHelper } from '../../api/version' + export class MinishiftTasks { /** * Returns tasks list which perform preflight platform checks. @@ -50,8 +52,8 @@ export class MinishiftTasks { } } }, - // { title: 'Verify minishift memory configuration', skip: () => 'Not implemented yet', task: () => {}}, - // { title: 'Verify kubernetes version', skip: () => 'Not implemented yet', task: () => {}}, + VersionHelper.getOpenShiftCheckVersionTask(flags), + VersionHelper.getK8sCheckVersionTask(flags) ], { renderer: flags['listr-renderer'] as any }) } diff --git a/src/tasks/platforms/openshift.ts b/src/tasks/platforms/openshift.ts index b25ad96fa..247daf465 100644 --- a/src/tasks/platforms/openshift.ts +++ b/src/tasks/platforms/openshift.ts @@ -13,6 +13,8 @@ import * as commandExists from 'command-exists' import * as execa from 'execa' import * as Listr from 'listr' +import { VersionHelper } from '../../api/version' + export class OpenshiftTasks { /** * Returns tasks list which perform preflight platform checks. @@ -40,6 +42,8 @@ export class OpenshiftTasks { } } }, + VersionHelper.getOpenShiftCheckVersionTask(flags), + VersionHelper.getK8sCheckVersionTask(flags) ], { renderer: flags['listr-renderer'] as any }) } diff --git a/test/api/version.test.ts b/test/api/version.test.ts new file mode 100644 index 000000000..efb3f69cd --- /dev/null +++ b/test/api/version.test.ts @@ -0,0 +1,40 @@ +/********************************************************************* + * 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 { expect, fancy } from 'fancy-test' + +import { VersionHelper } from '../../src/api/version' + +describe('OpenShift API helper', () => { + fancy + .it('check minimal version: case #1', async () => { + const check = VersionHelper.checkMinimalVersion('v2.10', 'v2.10') + expect(check).to.true + }) + fancy + .it('check minimal version: case #2', async () => { + const check = VersionHelper.checkMinimalVersion('v3.12', 'v2.10') + expect(check).to.true + }) + fancy + .it('check minimal version: case #3', async () => { + const check = VersionHelper.checkMinimalVersion('v2.11', 'v2.10') + expect(check).to.true + }) + fancy + .it('check minimal version: case #4', async () => { + const check = VersionHelper.checkMinimalVersion('v2.09', 'v2.10') + expect(check).to.false + }) + fancy + .it('check minimal version: case #5', async () => { + const check = VersionHelper.checkMinimalVersion('v2.10', 'v3.10') + expect(check).to.false + }) +}) diff --git a/yarn.lock b/yarn.lock index 6938b3cae..d9dbcf819 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1677,11 +1677,11 @@ ecc-jsbn@~0.1.1: "eclipse-che-operator@git://github.com/eclipse/che-operator#master": version "0.0.0" - resolved "git://github.com/eclipse/che-operator#5039e1fdb2860d660285f4e45f75449f29c93542" + resolved "git://github.com/eclipse/che-operator#29198304be0cb89d823b725fba72eafb78d9fd9a" "eclipse-che@git://github.com/eclipse/che#master": version "0.0.0" - resolved "git://github.com/eclipse/che#45604b90ef68caea3238f2bc8faf9759c6828d60" + resolved "git://github.com/eclipse/che#95754848f677f735203b75d517e26cde12dd14cf" editorconfig@^0.15.0: version "0.15.3"