diff --git a/src/commands/server/start.ts b/src/commands/server/start.ts index fb756a502..8b781c058 100644 --- a/src/commands/server/start.ts +++ b/src/commands/server/start.ts @@ -23,6 +23,7 @@ import { MinishiftAddonHelper } from '../../installers/minishift-addon' import { OperatorHelper } from '../../installers/operator' 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' @@ -92,7 +93,7 @@ export default class Start extends Command { }), platform: string({ char: 'p', - description: 'Type of Kubernetes platform. Valid values are \"minikube\", \"minishift\", \"k8s\", \"openshift\".', + description: 'Type of Kubernetes platform. Valid values are \"minikube\", \"minishift\", \"k8s\", \"openshift\", \"microk8s\".', default: 'minikube' }) } @@ -144,6 +145,7 @@ export default class Start extends Command { 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() @@ -160,7 +162,7 @@ export default class Start extends Command { this.error(`🛑 Current platform is ${flags.platform}. Minishift addon is only available on top of Minishift platform.`) } } else if (flags.installer === 'helm') { - if (flags.platform !== 'k8s' && flags.platform !== 'minikube' && flags.platform !== 'docker-desktop') { + if (flags.platform !== 'k8s' && flags.platform !== 'minikube' && flags.platform !== 'microk8s' && flags.platform !== 'docker-desktop') { this.error(`🛑 Current platform is ${flags.platform}. Helm installer is only available on top of Kubernetes flavor platform (including Minikube, Docker Desktop).`) } } @@ -178,6 +180,11 @@ export default class Start extends Command { 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 === 'openshift') { platformCheckTasks.add({ title: '✈️ Openshift preflight checklist', diff --git a/src/installers/operator.ts b/src/installers/operator.ts index 15a7e4707..a3c895dcc 100644 --- a/src/installers/operator.ts +++ b/src/installers/operator.ts @@ -38,7 +38,7 @@ export class OperatorHelper { const exist = await che.cheNamespaceExist(flags.chenamespace) if (exist) { task.title = `${task.title}...It already exist.` - } else if (flags.platform === 'minikube' || flags.platform === 'k8s') { + } else if (flags.platform === 'minikube' || flags.platform === 'k8s' || flags.platform === 'microk8s') { await execa.shell(`kubectl create namespace ${flags.chenamespace}`) task.title = `${task.title}...done.` } else if (flags.platform === 'minishift' || flags.platform === 'openshift') { @@ -53,7 +53,7 @@ export class OperatorHelper { const exist = await kube.serviceAccountExist(this.operatorServiceAccount, flags.chenamespace) if (exist) { task.title = `${task.title}...It already exist.` - } else if (flags.platform === 'minikube' || flags.platform === 'k8s') { + } else if (flags.platform === 'minikube' || flags.platform === 'k8s' || flags.platform === 'microk8s') { await execa.shell(`kubectl create serviceaccount ${this.operatorServiceAccount} -n=${flags.chenamespace}`) task.title = `${task.title}...done.` } else if (flags.platform === 'minishift' || flags.platform === 'openshift') { diff --git a/src/platforms/microk8s.ts b/src/platforms/microk8s.ts new file mode 100644 index 000000000..4371baf0f --- /dev/null +++ b/src/platforms/microk8s.ts @@ -0,0 +1,120 @@ +/********************************************************************* + * 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 + **********************************************************************/ +// tslint:disable:object-curly-spacing + +import { Command } from '@oclif/command' +import * as commandExists from 'command-exists' +import * as execa from 'execa' +import * as Listr from 'listr' + +export class MicroK8sHelper { + startTasks(flags: any, command: Command): Listr { + return new Listr([ + { + title: 'Verify if kubectl is installed', + task: () => { + if (!commandExists.sync('kubectl')) { + command.error('E_REQUISITE_NOT_FOUND') + } + } + }, + { title: 'Verify if microk8s is installed', + task: () => { + if (!commandExists.sync('microk8s.status')) { + command.error('E_REQUISITE_NOT_FOUND', { code: 'E_REQUISITE_NOT_FOUND' }) + } + } + }, + { title: 'Verify if microk8s is running', + task: async (ctx: any) => { + ctx.isMicroK8sRunning = await this.isMicroK8sRunning() + } + }, + { title: 'Start microk8s', + skip: (ctx: any) => { + if (ctx.isMicroK8sRunning) { + return 'MicroK8s is already running.' + } + }, + task: () => { + // microk8s.start requires sudo permissions + // this.startMicroK8s() + 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: () => {}}, + { title: 'Verify if microk8s ingress and storage addons is enabled', + task: async (ctx: any) => { + ctx.enabledAddons = await this.enabledAddons() + } + }, + { title: 'Enable microk8s ingress addon', + skip: (ctx: any) => { + if (ctx.enabledAddons.ingress) { + return 'Ingress addon is already enabled.' + } + }, + task: () => this.enableIngressAddon() + }, + { title: 'Enable microk8s storage addon', + skip: (ctx: any) => { + if (ctx.enabledAddons.storage) { + return 'Storage addon is already enabled.' + } + }, + task: () => { + // Enabling storage requires sudo permissions + // this.enableStorageAddon() + return command.error('The storage addon hasn\'t been enabled in microk8s', { code: 'E_REQUISITE_NOT_FOUND' }) + } + }, + { title: 'Retrieving microk8s IP and domain for ingress URLs', + enabled: () => flags.domain !== undefined, + task: async (_ctx: any, task: any) => { + const ip = await this.getMicroK8sIP() + flags.domain = ip + '.nip.io' + task.title = `${task.title}...${flags.domain}.` + } + }, + ], {renderer: flags['listr-renderer'] as any}) + } + + async isMicroK8sRunning(): Promise { + const { code } = await execa('microk8s.status', { timeout: 1000, reject: false }) + if (code === 0) { return true } else { return false } + } + + async startMicroK8s() { + execa('microk8s.start', { timeout: 180000 }) + } + + async enabledAddons(): Promise { + const { stdout } = await execa('microk8s.status', [], { timeout: 10000 }) + return { + ingress: stdout.includes('ingress: enabled'), + storage: stdout.includes('storage: enabled') + } + } + + async enableIngressAddon() { + await execa('microk8s.enable', ['ingress'], { timeout: 10000 }) + } + + async enableStorageAddon() { + await execa('microk8s.enable', ['storage'], { timeout: 10000 }) + } + + async getMicroK8sIP(): Promise { + const { stdout } = await execa('microk8s.config', { timeout: 10000 }) + const regMatch = /server:\s*https?:\/\/([\d.]+)/.exec(stdout) + return regMatch ? regMatch[1] : '' + } +} diff --git a/test/platforms/microk8s.test.ts b/test/platforms/microk8s.test.ts new file mode 100644 index 000000000..db40cb78e --- /dev/null +++ b/test/platforms/microk8s.test.ts @@ -0,0 +1,46 @@ +/********************************************************************* + * 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 + **********************************************************************/ +// tslint:disable:object-curly-spacing +import { expect, fancy } from 'fancy-test' +import {MicroK8sHelper} from '../../src/platforms/microk8s'; +import * as execa from 'execa'; + +jest.mock('execa'); + +let mh = new MicroK8sHelper() + +describe('start', () => { + fancy + .it('verifies that microk8s is running', async () => { + (execa as any).mockResolvedValue({ code: 0 }) + const res = await mh.isMicroK8sRunning() + expect(res).to.equal(true) + }) + + fancy + .it('verifies that microk8s is not running', async () => { + (execa as any).mockResolvedValue({ code: 1 }) + const res = await mh.isMicroK8sRunning() + expect(res).to.equal(false) + }) + + fancy + .it('obtains the ip', async () => { + const output = `apiVersion: v1 + clusters: + - cluster: + server: http://127.0.0.1:8080 + name: microk8s-cluster`; + + (execa as any).mockResolvedValue({ code: 0, stdout: output }) + const res = await mh.getMicroK8sIP() + expect(res).to.equal('127.0.0.1') + }) +})