Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a chectl preflight check step that verifies that the platform version is supported #500

Merged
merged 8 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@
"skipFiles": [
"<node_internals>/**"
]
},
{
"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",
}
}
]
}
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
```
Expand Down
133 changes: 133 additions & 0 deletions src/api/version.ts
Original file line number Diff line number Diff line change
@@ -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<string | undefined> {
return getVersionWithOC('openshift ')
}

export async function getK8sVersionWithOC(): Promise<string | undefined> {
return getVersionWithOC('kubernetes ')
}

export async function getK8sVersionWithKubectl(): Promise<string | undefined> {
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<string | undefined> {
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<string | undefined> {
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
}
}
8 changes: 6 additions & 2 deletions src/commands/server/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down
13 changes: 10 additions & 3 deletions src/tasks/installers/helm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -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}`)
}
Expand Down
4 changes: 4 additions & 0 deletions src/tasks/platforms/crc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/tasks/platforms/docker-desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -58,6 +59,7 @@ export class DockerDesktopTasks {
}
}
},
VersionHelper.getK8sCheckVersionTask(flags),
{
title: 'Verify if nginx ingress is installed',
task: async (ctx: any) => {
Expand Down
2 changes: 2 additions & 0 deletions src/tasks/platforms/k8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -64,6 +65,7 @@ export class K8sTasks {
}
}
},
VersionHelper.getK8sCheckVersionTask(flags),
// Should automatically compute route if missing
{
title: 'Verify domain is set',
Expand Down
5 changes: 3 additions & 2 deletions src/tasks/platforms/microk8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) => {
Expand Down
5 changes: 3 additions & 2 deletions src/tasks/platforms/minikube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) => {
Expand Down
6 changes: 4 additions & 2 deletions src/tasks/platforms/minishift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 })
}

Expand Down
4 changes: 4 additions & 0 deletions src/tasks/platforms/openshift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -40,6 +42,8 @@ export class OpenshiftTasks {
}
}
},
VersionHelper.getOpenShiftCheckVersionTask(flags),
VersionHelper.getK8sCheckVersionTask(flags)
], { renderer: flags['listr-renderer'] as any })
}

Expand Down
Loading