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 workspace:create command instead of workspace:start #592

Merged
merged 4 commits into from
Mar 25, 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"tslib": "^1"
},
"devDependencies": {
"@eclipse-che/api": "latest",
"@oclif/dev-cli": "^1",
"@oclif/test": "^1",
"@oclif/tslint": "^3",
Expand Down
58 changes: 49 additions & 9 deletions src/api/che.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
* Copyright (c) 2019-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 { che as chetypes } from '@eclipse-che/api'
import { CoreV1Api, KubeConfig, V1Pod, Watch } from '@kubernetes/client-node'
import axios, { AxiosInstance } from 'axios'
import * as cp from 'child_process'
Expand Down Expand Up @@ -197,13 +199,37 @@ export class CheHelper {
}
}

async createWorkspaceFromDevfile(namespace: string | undefined, devfilePath = '', workspaceName: string | undefined, accessToken = ''): Promise<string> {
async startWorkspace(cheNamespace: string, workspaceId: string, accessToken?: string): Promise<void> {
const cheUrl = await this.cheURL(cheNamespace)
const endpoint = `${cheUrl}/api/workspace/${workspaceId}/runtime`
let response

const headers: {[key: string]: string} = {}
if (accessToken) {
headers.Authorization = accessToken
}
try {
response = await this.axios.post(endpoint, undefined, { headers })
} catch (error) {
if (error.response && error.response.status === 404) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCheApiError function actually catches 404 error (rebase on master pls)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to handle 404 error in another way for this specific request.

throw new Error(`E_WORKSPACE_NOT_EXIST - workspace with "${workspaceId}" id doesn't exist`)
} else {
throw this.getCheApiError(error, endpoint)
}
}

if (!response || response.status !== 200 || !response.data) {
throw new Error('E_BAD_RESP_CHE_API')
}
}

async createWorkspaceFromDevfile(namespace: string | undefined, devfilePath = '', workspaceName: string | undefined, accessToken = ''): Promise<chetypes.workspace.Workspace> {
if (!await this.cheNamespaceExist(namespace)) {
throw new Error('E_BAD_NS')
}
let url = await this.cheURL(namespace)
let endpoint = `${url}/api/workspace/devfile`
let devfile
let devfile: string | undefined
let response
const headers: any = { 'Content-Type': 'text/yaml' }
if (accessToken && accessToken.length > 0) {
Expand All @@ -217,17 +243,31 @@ export class CheHelper {
json.metadata.name = workspaceName
devfile = yaml.dump(json)
}

response = await this.axios.post(endpoint, devfile, { headers })
} catch (error) {
if (!devfile) { throw new Error(`E_NOT_FOUND_DEVFILE - ${devfilePath} - ${error.message}`) }
if (error.response && error.response.status === 400) {
throw new Error(`E_BAD_DEVFILE_FORMAT - Message: ${error.response.data.message}`)
if (!devfile) {
throw new Error(`E_NOT_FOUND_DEVFILE - ${devfilePath} - ${error.message}`)
}

if (error.response) {
if (error.response.status === 400) {
throw new Error(`E_BAD_DEVFILE_FORMAT - Message: ${error.response.data.message}`)
}
if (error.response.status === 409) {
let message = ''
if (error.response.data) {
message = error.response.data.message
}
throw new Error(`E_CONFLICT - Message: ${message}`)
}
}

throw this.getCheApiError(error, endpoint)
}
if (response && response.data && response.data.links && response.data.links.ide) {
let ideURL = response.data.links.ide
return this.buildDashboardURL(ideURL)

if (response && response.data) {
return response.data as chetypes.workspace.Workspace
} else {
throw new Error('E_BAD_RESP_CHE_SERVER')
}
Expand Down
122 changes: 122 additions & 0 deletions src/commands/workspace/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*********************************************************************
* Copyright (c) 2019-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 { Command, flags } from '@oclif/command'
import { boolean, string } from '@oclif/parser/lib/flags'
import { cli } from 'cli-ux'
import * as fs from 'fs'
import * as Listr from 'listr'
import * as notifier from 'node-notifier'

import { CheHelper } from '../../api/che'
import { accessToken, cheNamespace } from '../../common-flags'
export default class Create extends Command {
static description = 'Creates a workspace from a devfile'

static flags = {
help: flags.help({ char: 'h' }),
chenamespace: cheNamespace,
devfile: string({
char: 'f',
description: 'path or URL to a valid devfile',
env: 'DEVFILE_PATH',
required: false,
}),
name: string({
description: 'workspace name: overrides the workspace name to use instead of the one defined in the devfile.',
required: false,
}),
start: boolean({
char: 's',
description: 'Starts the workspace after creation',
default: false
}),
'access-token': accessToken
}

async run() {
const { flags } = this.parse(Create)

const tasks = this.getWorkspaceCreateTasks(flags)
try {
let ctx = await tasks.run()
this.log('\nWorkspace IDE URL:')
cli.url(ctx.workspaceIdeURL, ctx.workspaceIdeURL)
} catch (err) {
this.error(err)
}

notifier.notify({
title: 'chectl',
message: 'Command workspace:create has completed successfully.'
})

this.exit(0)
}

getWorkspaceCreateTasks(flags: any): Listr<any> {
const che = new CheHelper(flags)
return new Listr([
{
title: 'Retrieving Eclipse Che server URL',
task: async (ctx: any, task: any) => {
ctx.cheURL = await che.cheURL(flags.chenamespace)
task.title = await `${task.title}... ${ctx.cheURL}`
}
},
{
title: 'Verify if Eclipse Che server is running',
task: async (ctx: any, task: any) => {
if (!await che.isCheServerReady(ctx.cheURL)) {
this.error(`E_SRV_NOT_RUNNING - Eclipse Che server is not available by ${ctx.cheURL}`, { code: 'E_SRV_NOT_RUNNNG' })
}
const status = await che.getCheServerStatus(ctx.cheURL)
ctx.isAuthEnabled = await che.isAuthenticationEnabled(ctx.cheURL)
const auth = ctx.isAuthEnabled ? '(auth enabled)' : '(auth disabled)'
task.title = await `${task.title}...${status} ${auth}`
}
},
{
title: `Create workspace from Devfile ${flags.devfile}`,
task: async (ctx: any) => {
if (ctx.isAuthEnabled && !flags['access-token']) {
this.error('E_AUTH_REQUIRED - Eclipse Che authentication is enabled and an access token needs to be provided (flag --access-token).')
}

let devfilePath: string
if (flags.devfile) {
devfilePath = flags.devfile
} else if (fs.existsSync('devfile.yaml')) {
devfilePath = 'devfile.yaml'
} else if (fs.existsSync('devfile.yml')) {
mmorhun marked this conversation as resolved.
Show resolved Hide resolved
devfilePath = 'devfile.yml'
} else {
throw new Error("E_DEVFILE_MISSING - Devfile wasn't specified via '-f' option and \'devfile.yaml' is not present in current directory.")
}

const workspaceConfig = await che.createWorkspaceFromDevfile(flags.chenamespace, devfilePath, flags.name, flags['access-token'])
ctx.workspaceId = workspaceConfig.id
if (workspaceConfig.links && workspaceConfig.links.ide) {
ctx.workspaceIdeURL = await che.buildDashboardURL(workspaceConfig.links.ide)
}
}
}, {
title: 'Start workspace',
enabled: () => flags.start,
task: async (ctx: any, task: any) => {
await che.startWorkspace(flags.chenamespace, ctx.workspaceId, flags['access-token'])
task.title = `${task.title}... Done`
}
}
], { renderer: 'silent' })

}

}
70 changes: 9 additions & 61 deletions src/commands/workspace/start.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
* Copyright (c) 2019-2020 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -8,73 +8,21 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { Command, flags } from '@oclif/command'
import { string } from '@oclif/parser/lib/flags'
import { cli } from 'cli-ux'
import * as notifier from 'node-notifier'

import { CheHelper } from '../../api/che'
import { accessToken, cheNamespace, listrRenderer } from '../../common-flags'
export default class Start extends Command {
static description = 'create and start a workspace'
import Create from './create'

static flags = {
help: flags.help({ char: 'h' }),
chenamespace: cheNamespace,
devfile: string({
char: 'f',
description: 'path or URL to a valid devfile',
env: 'DEVFILE_PATH',
required: true,
}),
name: string({
description: 'workspace name: overrides the workspace name to use instead of the one defined in the devfile. Works only for devfile',
required: false,
}),
'access-token': accessToken,
'listr-renderer': listrRenderer
}

async checkToken(flags: any, ctx: any) {
if (ctx.isAuthEnabled && !flags['access-token']) {
this.error('E_AUTH_REQUIRED - Eclipse Che authentication is enabled and an access token need to be provided (flag --access-token).')
}
}
export default class Start extends Create {
static description = 'Creates and starts workspace from a devfile'

async run() {
const { flags } = this.parse(Start)
const { flags } = this.parse(Create)
flags.start = true

const Listr = require('listr')
const notifier = require('node-notifier')
const che = new CheHelper(flags)
const tasks = new Listr([
{
title: 'Retrieving Eclipse Che server URL',
task: async (ctx: any, task: any) => {
ctx.cheURL = await che.cheURL(flags.chenamespace)
task.title = await `${task.title}... ${ctx.cheURL}`
}
},
{
title: 'Verify if Eclipse Che server is running',
task: async (ctx: any, task: any) => {
if (!await che.isCheServerReady(ctx.cheURL)) {
this.error(`E_SRV_NOT_RUNNING - Eclipse Che server is not available by ${ctx.cheURL}`, { code: 'E_SRV_NOT_RUNNNG' })
}
const status = await che.getCheServerStatus(ctx.cheURL)
ctx.isAuthEnabled = await che.isAuthenticationEnabled(ctx.cheURL)
const auth = ctx.isAuthEnabled ? '(auth enabled)' : '(auth disabled)'
task.title = await `${task.title}...${status} ${auth}`
}
},
{
title: `Create workspace from Devfile ${flags.devfile}`,
task: async (ctx: any) => {
await this.checkToken(flags, ctx)
ctx.workspaceIdeURL = await che.createWorkspaceFromDevfile(flags.chenamespace, flags.devfile, flags.name, flags['access-token'])
}
}
], { renderer: flags['listr-renderer'] as any })
const tasks = this.getWorkspaceCreateTasks(flags)

cli.warn('This command is deprecated. Please use "workspace:create --start" instead')
try {
let ctx = await tasks.run()
this.log('\nWorkspace IDE URL:')
Expand Down
4 changes: 2 additions & 2 deletions test/api/che.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('Eclipse Che helper', () => {
.replyWithFile(201, __dirname + '/replies/create-workspace-from-valid-devfile.json', { 'Content-Type': 'application/json' }))
.it('succeds creating a workspace from a valid devfile', async () => {
const res = await ch.createWorkspaceFromDevfile(namespace, __dirname + '/requests/devfile.valid', undefined)
expect(res).to.equal('https://che-che.192.168.64.39.nip.io/dashboard/#/ide/che/chectl')
expect(res.links!.ide).to.equal('https://che-che.192.168.64.39.nip.io/che/chectl')
})
fancy
.stub(ch, 'cheNamespaceExist', () => true)
Expand Down Expand Up @@ -172,7 +172,7 @@ describe('Eclipse Che helper', () => {
.replyWithFile(201, __dirname + '/replies/create-workspace-from-valid-devfile.json', { 'Content-Type': 'application/json' }))
.it('succeeds creating a workspace from a remote devfile', async () => {
const res = await ch.createWorkspaceFromDevfile(namespace, devfileServerURL + '/devfile.yaml', undefined)
expect(res).to.equal('https://che-che.192.168.64.39.nip.io/dashboard/#/ide/che/chectl')
expect(res.links!.ide).to.equal('https://che-che.192.168.64.39.nip.io/che/chectl')
})
fancy
.stub(ch, 'cheNamespaceExist', () => true)
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
exec-sh "^0.3.2"
minimist "^1.2.0"

"@eclipse-che/api@latest":
version "7.5.0-SNAPSHOT"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks strange

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. It is really latest version... https://www.npmjs.com/package/@eclipse-che/api

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing strange. I just used latest in package json, so it was resolved to 7.5.0-SNAPSHOT

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this at all?

Copy link
Contributor

@AndrienkoAleksandr AndrienkoAleksandr Mar 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @mmorhun wants to use dts types

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @AndrienkoAleksandr but I have no idea who is responsible now for @eclipse-che/api. Try to ask in Eclipse Che MM channel tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should a part of release process.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not use outdated version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there were some major changes. But we need to raise this question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've create issue for this problem: eclipse-che/che#16436

resolved "https://registry.yarnpkg.com/@eclipse-che/api/-/api-7.5.0-SNAPSHOT.tgz#bf0c5be60354e34c73bc52b2e18be8680ce8d900"

"@fimbul/bifrost@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.21.0.tgz#d0fafa25938fda475657a6a1e407a21bbe02c74e"
Expand Down