Skip to content

Commit

Permalink
Support creating workspaces with a workspace config JSON (#34)
Browse files Browse the repository at this point in the history
* Updates to support workspaceconfig.json

* Fix issue with URL regex

* Fix trailing whitespace

* Fixup after Artem review
  • Loading branch information
l0rd authored Dec 31, 2018
1 parent cb49bac commit f361c31
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 39 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ USAGE
$ chectl workspace:start
OPTIONS
-f, --devfile=devfile (required) path to a valid devfile
-h, --help show CLI help
-n, --chenamespace=chenamespace [default: kube-che] kubernetes namespace where Che server is deployed
-f, --devfile=devfile path to a valid devfile
-h, --help show CLI help
-n, --chenamespace=chenamespace [default: kube-che] kubernetes namespace where Che server is deployed
-w, --workspaceconfig=workspaceconfig path to a valid workspace configuration json file
```

_See code: [src/commands/workspace/start.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/workspace/start.ts)_
Expand Down
11 changes: 9 additions & 2 deletions src/commands/workspace/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ export default class Start extends Command {
char: 'f',
description: 'path to a valid devfile',
env: 'DEVFILE_PATH',
required: true,
required: false,
}),
workspaceconfig: string({
char: 'w',
description: 'path to a valid workspace configuration json file',
env: 'WORKSPACE_CONFIG_JSON_PATH',
required: false,
}),
}

Expand All @@ -32,7 +38,8 @@ export default class Start extends Command {
const che = new CheHelper()
const tasks = new Listr([
{ title: 'Verify if Che server is running', task: async () => { if (!await che.isCheServerReady(flags.chenamespace)) { this.error(`E_SRV_NOT_RUNNING - Che Server is not running.\nChe Server cannot be found in Kubernetes Namespace "${flags.chenamespace}". Have you already start it?\nFix with: start Che server: chectl server:start\nhttps://github.com/eclipse/che`, { code: 'E_SRV_NOT_RUNNNG'}) } } },
{ title: `Create Workspaces from Devfile ${flags.devfile}`, task: async (ctx: any) => { ctx.workspaceIdeURL = await che.createWorkspaceFromDevfile(flags.chenamespace, flags.devfile) } },
{ title: `Create workspace from Devfile ${flags.devfile}`, enabled: () => flags.devfile !== undefined, task: async (ctx: any) => { ctx.workspaceIdeURL = await che.createWorkspaceFromDevfile(flags.chenamespace, flags.devfile) } },
{ title: `Create workspace from Workspace Config ${flags.workspaceconfig}`, enabled: () => flags.workspaceconfig !== undefined, task: async (ctx: any) => { ctx.workspaceIdeURL = await che.createWorkspaceFromWorkspaceConfig(flags.chenamespace, flags.workspaceconfig) } },
])

try {
Expand Down
61 changes: 45 additions & 16 deletions src/helpers/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,37 +136,62 @@ export class CheHelper {
}

let devfile
let url = await this.cheURL(namespace)

try {
let url = await this.cheURL(namespace)
devfile = fs.readFileSync(devfilePath, 'utf8')
let response = await axios.post(`${url}/api/devfile`, devfile, {headers: {'Content-Type': 'text/yaml'}})
if (response && response.data && response.data.links && response.data.links.ide) {
// console.log(response.data)
// console.log(response.status)
// console.log(response.statusText)
// console.log(response.headers)
// console.log(response.config)
let ideURL = response.data.links.ide
return ideURL.replace(/\/\w*\/\w*$/g, '\/dashboard\/#\/ide$&')
return this.buildDashboardURL(ideURL)
} else {
throw new Error('E_BAD_RESP_CHE_SERVER')
}
} catch (error) {
if (!devfile) { throw new Error(`E_NOT_FOUND_DEFILE - ${devfilePath} - ${error.message}`) }
if (error.response && error.response.status && error.response.status === 400) {
// console.log(error.response.data)
// console.log(error.response.status)
// console.log(error.response.headers)
throw new Error(`E_BAD_DEFILE_FORMAT - Message: ${error.response.data.message}`)
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 (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
throw new Error(`E_CHE_SERVER_UNKNOWN_ERROR - Status: ${error.response.status}`)
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
throw new Error(`E_CHE_SERVER_NO_RESPONSE - ${error.message}`)
} else {
// Something happened in setting up the request that triggered an Error
throw new Error(`E_CHECTL_UNKNOWN_ERROR - Message: ${error.message}`)
}
}
}

async createWorkspaceFromWorkspaceConfig(namespace: string | undefined, workspaceConfigPath: string | undefined = ''): Promise<string> {
if (!await this.cheNamespaceExist(namespace)) {
throw new Error('E_BAD_NS')
}

let workspaceConfig
try {
let url = await this.cheURL(namespace)
let workspaceConfig = fs.readFileSync(workspaceConfigPath, 'utf8')
let response = await axios.post(`${url}/api/workspace`, workspaceConfig, {headers: {'Content-Type': 'application/json'}})
if (response && response.data && response.data.links && response.data.links.ide) {
let ideURL = response.data.links.ide
return this.buildDashboardURL(ideURL)
} else {
throw new Error('E_BAD_RESP_CHE_SERVER')
}
} catch (error) {
if (!workspaceConfig) { throw new Error(`E_NOT_FOUND_WORKSPACE_CONFIG_FILE - ${workspaceConfigPath} - ${error.message}`) }
if (error.response && error.response.status === 400) {
throw new Error(`E_BAD_WORKSPACE_CONFIG_FORMAT - Message: ${error.response.data.message}`)
}
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
// console.log(error.response.data)
// console.log(error.response.status)
// console.log(error.response.headers)
throw new Error(`E_CHE_SERVER_UNKNOWN_ERROR - Status: ${error.response.status}`)
} else if (error.request) {
// The request was made but no response was received
Expand All @@ -179,4 +204,8 @@ export class CheHelper {
}
}
}

async buildDashboardURL(ideURL: string): Promise<string> {
return ideURL.replace(/\/[^/|.]*\/[^/|.]*$/g, '\/dashboard\/#\/ide$&')
}
}
37 changes: 19 additions & 18 deletions test/helpers/che.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,30 +123,31 @@ describe('Che helper', () => {
'Content-Type': 'application/json'
}))
.do(() => ch.createWorkspaceFromDevfile(namespace, __dirname + '/requests/devfile.invalid'))
.catch(/E_BAD_DEFILE_FORMAT/)
.catch(/E_BAD_DEVFILE_FORMAT/)
.it('fails creating a workspace from an invalid devfile')
fancy
.stub(ch, 'cheNamespaceExist', () => true)
.stub(ch, 'cheURL', () => cheURL)
.do(() => ch.createWorkspaceFromDevfile(namespace, __dirname + '/requests/devfile.inexistent'))
.catch(/E_NOT_FOUND_DEFILE/)
.catch(/E_NOT_FOUND_DEVFILE/)
.it('fails creating a workspace from a non-existing devfile')
// fancy
// .stub(ch, 'cheNamespaceExist', () => true)
// .stub(ch, 'cheURL', () => cheURL)
// .nock(cheURL, api => api
// .post('/api/devfile')
// .replyWithFile(400, __dirname + '/replies/create-workspace-from-valid-devfile', { 'Content-Type': 'application/json' }))
// // .get('/api/system/state')
// // .reply(404)
// // .get('/api/system/state')
// // .reply(404)
// // .get('/api/system/state')
// // .reply(503))
// .it('fails creating a workspace from an invalid devfile', async () => {
// const res = await ch.createWorkspaceFromDevfile(namespace, __dirname + '/requests/devfile.valid')
// expect(res).to.equal('http://che-kube-che.192.168.64.39.nip.io/che/chectl')
// })
fancy
.stub(ch, 'cheNamespaceExist', () => true)
.stub(ch, 'cheURL', () => cheURL)
.nock(cheURL, api => api
.post('/api/workspace')
.replyWithFile(201, __dirname + '/replies/create-workspace-from-valid-devfile.json', { 'Content-Type': 'application/json' }))
.it('succeds creating a workspace from a valid workspaceconfig', async () => {
const res = await ch.createWorkspaceFromWorkspaceConfig(namespace, __dirname + '/requests/workspaceconfig.valid')
expect(res).to.equal('https://che-kube-che.192.168.64.39.nip.io/dashboard/#/ide/che/chectl')
})
fancy
.it('builds the Dashboard URL of a workspace given the IDE link', async () => {
let ideURL = 'https://che-kube-che.192.168.64.40.nip.io/che/name-with-dashes'
let dashboardURL = 'https://che-kube-che.192.168.64.40.nip.io/dashboard/#/ide/che/name-with-dashes'
let res = await ch.buildDashboardURL(ideURL)
expect(res).to.equal(dashboardURL)
})
describe('getWorkspacePod', () => {
fancy
.stub(kc, 'makeApiClient', () => k8sApi)
Expand Down
109 changes: 109 additions & 0 deletions test/helpers/replies/create-workspace-from-valid-workspaceconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"links": {
"self": "http://che-kube-che.192.168.64.40.nip.io/api/workspace/workspace1ioy0lx0zg7j0jio",
"ide": "http://che-kube-che.192.168.64.40.nip.io/che/inner-loop-buildah"
},
"attributes": {
"created": "1545089353884"
},
"namespace": "che",
"temporary": false,
"id": "workspace1ioy0lx0zg7j0jio",
"status": "STOPPED",
"config": {
"defaultEnv": "default",
"environments": {
"default": {
"machines": {
"ws/dev": {
"attributes": {
"memoryLimitBytes": "536870912"
},
"servers": {},
"volumes": {
"projects": {
"path": "/projects"
}
},
"installers": [],
"env": {
"": ""
}
}
},
"recipe": {
"type": "kubernetes",
"content": "kind: List\nitems:\n - \n apiVersion: v1\n kind: Pod\n metadata:\n name: ws\n spec:\n containers:\n - \n image: mariolet/che-plugin-dev-tooling:jug\n name: dev\n resources:\n limits:\n memory: 512Mi\n",
"contentType": "application/x-yaml"
}
}
},
"projects": [
{
"links": [],
"name": "spring-petclinic",
"attributes": {},
"source": {
"location": "https://github.com/sleshchenko/spring-petclinic.git",
"type": "git",
"parameters": {}
},
"path": "/spring-petclinic",
"description": "",
"mixins": [],
"problems": []
}
],
"name": "inner-loop-buildah",
"attributes": {
"editor": "org.eclipse.che.editor.theia:1.0.0",
"sidecar.theia-ide.memory_limit": "512Mi",
"plugins": "che-machine-exec-plugin:0.0.1"
},
"commands": [
{
"commandLine": "cd /projects/spring-petclinic && kubectl apply -f app4jug.yaml",
"name": "Run Kube App",
"attributes": {
"machineName": "ws/dev",
"goal": "Run"
},
"type": "che"
},
{
"commandLine": "cd /projects/spring-petclinic && ./mvnw package",
"name": "Build Java",
"attributes": {
"machineName": "ws/dev",
"goal": "Build"
},
"type": "che"
},
{
"commandLine": "cd /projects/spring-petclinic && buildah bud -t petclinic .",
"name": "Build Container Image",
"attributes": {
"machineName": "ws/dev",
"goal": "Build"
},
"type": "che"
},
{
"commandLine": "cd /projects/spring-petclinic && buildah push --tls-verify=false petclinic docker://$(kubectl get services -n kube-system -o jsonpath={.spec.clusterIP} registry)/mariolet/petclinic:latest",
"name": "Push Container Image",
"attributes": {
"machineName": "ws/dev",
"goal": "Build"
},
"type": "che"
},
{
"commandLine": "echo ${CHE_OSO_CLUSTER//api/console}",
"name": "Get OpenShift Console URL",
"attributes": {},
"type": "custom"
}
],
"links": []
}
}
96 changes: 96 additions & 0 deletions test/helpers/requests/workspaceconfig.valid
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"environments": {
"default": {
"recipe": {
"contentType": "application/x-yaml",
"type": "kubernetes",
"content": "kind: List\nitems:\n - \n apiVersion: v1\n kind: Pod\n metadata:\n name: ws\n spec:\n containers:\n - \n image: mariolet/che-plugin-dev-tooling:jug\n name: dev\n resources:\n limits:\n memory: 512Mi\n"
},
"machines": {
"ws/dev": {
"env": {
"": ""
},
"installers": [],
"servers": {},
"volumes": {
"projects": {
"path": "/projects"
}
},
"attributes": {
"memoryLimitBytes": "536870912"
}
}
}
}
},
"commands": [
{
"commandLine": "cd /projects/spring-petclinic && kubectl apply -f app4jug.yaml",
"name": "Run Kube App",
"type": "che",
"attributes": {
"machineName": "ws/dev",
"goal": "Run"
}
},
{
"commandLine": "cd /projects/spring-petclinic && ./mvnw package",
"name": "Build Java",
"type": "che",
"attributes": {
"machineName": "ws/dev",
"goal": "Build"
}
},
{
"commandLine": "cd /projects/spring-petclinic && buildah bud -t petclinic .",
"name": "Build Container Image",
"type": "che",
"attributes": {
"machineName": "ws/dev",
"goal": "Build"
}
},
{
"commandLine": "cd /projects/spring-petclinic && buildah push --tls-verify=false petclinic docker://$(kubectl get services -n kube-system -o jsonpath={.spec.clusterIP} registry)/mariolet/petclinic:latest",
"name": "Push Container Image",
"type": "che",
"attributes": {
"machineName": "ws/dev",
"goal": "Build"
}
},
{
"commandLine": "echo ${CHE_OSO_CLUSTER//api/console}",
"name": "Get OpenShift Console URL",
"type": "custom",
"attributes": {}
}
],
"projects": [
{
"source": {
"location": "https://github.com/sleshchenko/spring-petclinic.git",
"type": "git",
"parameters": {}
},
"links": [],
"description": "",
"problems": [],
"mixins": [],
"name": "spring-petclinic",
"path": "/spring-petclinic",
"attributes": {}
}
],
"defaultEnv": "default",
"name": "inner-loop-buildah",
"attributes": {
"editor": "org.eclipse.che.editor.theia:1.0.0",
"plugins": "che-machine-exec-plugin:0.0.1",
"sidecar.theia-ide.memory_limit": "512Mi"
},
"links": []
}

0 comments on commit f361c31

Please sign in to comment.