Skip to content

Commit ce00ceb

Browse files
committed
Devfile Registry editor is to allow devfile version selection and use when creating a component redhat-developer#4189
Fixes: redhat-developer#4189 Issue: redhat-developer#3850 Signed-off-by: Victor Rubezhny <[email protected]>
1 parent 094fa1a commit ce00ceb

31 files changed

+1218
-994
lines changed

src/cli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55

66
import { VSCodeSettings } from '@redhat-developer/vscode-redhat-telemetry/lib/common/vscode/settings';
77
import * as cp from 'child_process';
8+
import * as hasha from 'hasha';
89
import * as vscode from 'vscode';
910
import { CommandText } from './base/command';
1011
import { ToolsConfig } from './tools';
1112
import { ChildProcessUtil, CliExitData } from './util/childProcessUtil';
1213
import { VsCommandError } from './vscommand';
1314

1415
export class ExecutionContext extends Map<string, any> {
16+
public static key(value: string): string {
17+
return hasha(value);
18+
}
1519
}
1620

1721
export class CliChannel {

src/devfile-registry/devfileInfo.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*-----------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE file in the project root for license information.
4+
*-----------------------------------------------------------------------------------------------*/
5+
6+
import { Registry } from '../odo/componentType';
7+
import { Data } from '../odo/componentTypeDescription';
8+
9+
export type DevfileRegistryInfo = Registry;
10+
11+
export type DevfileLinksInfo = {
12+
any
13+
};
14+
15+
export type DevfileCommandGroupsInfo = {
16+
build: boolean,
17+
debug: boolean,
18+
deploy: boolean,
19+
run: boolean,
20+
test: boolean
21+
};
22+
23+
export type DevfileVersionInfo = {
24+
version: string,
25+
schemaVersion: string,
26+
default: boolean,
27+
description: string,
28+
tags: string[],
29+
icon: string,
30+
links: DevfileLinksInfo,
31+
commandGroups: DevfileCommandGroupsInfo,
32+
resources: string[],
33+
starterProjects: string[],
34+
};
35+
36+
export type DevfileInfo = {
37+
name: string,
38+
displayName: string,
39+
description: string,
40+
type: string,
41+
tags: string[],
42+
architectures: string[],
43+
icon: string,
44+
projectType: string,
45+
language: string,
46+
provider: string,
47+
supportUrl: string,
48+
versions: DevfileVersionInfo[],
49+
registry?: DevfileRegistryInfo
50+
};
51+
52+
export type DevfileInfoExt = DevfileInfo & {
53+
proposedVersion?: string
54+
};
55+
56+
export type DevfileData = Data & {
57+
yaml: string;
58+
};
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*-----------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE file in the project root for license information.
4+
*-----------------------------------------------------------------------------------------------*/
5+
import * as https from 'https';
6+
import * as YAML from 'js-yaml';
7+
import { ExecutionContext } from '../cli';
8+
import { Registry } from '../odo/componentType';
9+
import { Odo } from '../odo/odoWrapper';
10+
import { DevfileData, DevfileInfo } from './devfileInfo';
11+
12+
export const DEVFILE_VERSION_LATEST: string = 'latest';
13+
14+
/**
15+
* Wraps some the Devfile Registry REST API calls.
16+
*/
17+
export class DevfileRegistry {
18+
private static instance: DevfileRegistry;
19+
20+
private executionContext: ExecutionContext = new ExecutionContext();
21+
22+
public static get Instance(): DevfileRegistry {
23+
if (!DevfileRegistry.instance) {
24+
DevfileRegistry.instance = new DevfileRegistry();
25+
}
26+
return DevfileRegistry.instance;
27+
}
28+
29+
private constructor() {
30+
// no state
31+
}
32+
33+
/**
34+
* Get list of Devfile Infos from the specified Registry.
35+
*
36+
* GET http://{registry host}/v2index/all
37+
*
38+
* @param url Devfile Registry URL
39+
* @param abortTimeout (Optional) If provided, allow cancelling the operation by timeout
40+
* @param abortController (Optional) If provided, allows cancelling the operation by signal
41+
*/
42+
public async getDevfileInfoList(url: string, abortTimeout?: number, abortController?: AbortController): Promise<DevfileInfo[]> {
43+
const requestUrl = `${url}/v2index/all`;
44+
const key = ExecutionContext.key(requestUrl);
45+
if (this.executionContext && this.executionContext.has(key)) {
46+
return this.executionContext.get(key);
47+
}
48+
const rawList = await DevfileRegistry._get(`${url}/v2index/all`, abortTimeout, abortController);
49+
const jsonList = JSON.parse(rawList);
50+
this.executionContext.set(key, jsonList);
51+
return jsonList;
52+
}
53+
54+
/**
55+
* Get Devfile of specified version from Registry.
56+
*
57+
* GET http://{registry host}/devfiles/{stack}/{version}
58+
*
59+
* @param url Devfile Registry URL
60+
* @param stack Devfile stack
61+
* @param version (Optional) If specified, the version of Devfile to be received, otherwize 'latest' version is requested
62+
* @param abortTimeout (Optional) If provided, allow cancelling the operation by timeout
63+
* @param abortController (Optional) If provided, allows cancelling the operation by signal
64+
*/
65+
private async _getDevfile(url: string, stack: string, version?: string, abortTimeout?: number, abortController?: AbortController): Promise<string> {
66+
const requestUrl = `${url}/devfiles/${stack}/${version ? version : DEVFILE_VERSION_LATEST}`;
67+
const key = ExecutionContext.key(requestUrl);
68+
if (this.executionContext && this.executionContext.has(key)) {
69+
return this.executionContext.get(key);
70+
}
71+
const devfile = DevfileRegistry._get(`${url}/devfiles/${stack}/${version ? version : DEVFILE_VERSION_LATEST}`,
72+
abortTimeout, abortController);
73+
this.executionContext.set(key, devfile);
74+
return devfile;
75+
}
76+
77+
/**
78+
* Returns a list of the devfile registries from ODO preferences.
79+
*
80+
* @returns a list of the devfile registries
81+
*/
82+
public async getRegistries(registryUrl?: string): Promise<Registry[]> {
83+
// Return only registries registered for user (from ODO preferences)
84+
// and filter by registryUrl (if provided)
85+
86+
let registries: Registry[] = [];
87+
const key = ExecutionContext.key('getRegistries');
88+
if (this.executionContext && !this.executionContext.has(key)) {
89+
registries = await Odo.Instance.getRegistries();
90+
this.executionContext.set(key, registries);
91+
} else {
92+
registries = this.executionContext.get(key);
93+
}
94+
95+
return !registries ? [] :
96+
registries.filter((reg) => {
97+
if (registryUrl) {
98+
return (reg.url === registryUrl)
99+
}
100+
return true;
101+
});
102+
}
103+
104+
/**
105+
* Returns a list of the devfile infos for the specified registry or all the
106+
* registries, if not specified.
107+
*
108+
* @returns a list of the devfile infos
109+
*/
110+
public async getRegistryDevfileInfos(registryUrl?: string): Promise<DevfileInfo[]> {
111+
const registries: Registry[] = await this.getRegistries(registryUrl);
112+
if (!registries || registries.length === 0) {
113+
throw new Error('No Devfile registries available. Default registry is missing');
114+
}
115+
116+
const devfiles: DevfileInfo[] = [];
117+
await Promise.all(registries
118+
.map(async (registry): Promise<void> => {
119+
const devfileInfoList = (await this.getDevfileInfoList(registry.url))
120+
.filter((devfileInfo) => 'stack' === devfileInfo.type.toLowerCase());
121+
devfileInfoList.forEach((devfileInfo) => {
122+
devfileInfo.registry = registry;
123+
});
124+
devfiles.push(...devfileInfoList);
125+
}));
126+
127+
return devfiles.sort((a, b) => (a.name < b.name ? -1 : 1));
128+
}
129+
130+
/**
131+
* Returns a devfile data with the raw devfile text attached
132+
*
133+
* @returns a devfile data with raw devfile text attached
134+
*/
135+
public async getRegistryDevfile(registryUrl: string, name: string, version?: string): Promise<DevfileData> {
136+
const rawDevfile = await this._getDevfile(registryUrl, name, version ? version : 'latest');
137+
const devfile = YAML.load(rawDevfile) as DevfileData;
138+
devfile.yaml = rawDevfile;
139+
return devfile;
140+
}
141+
142+
private static async _get(url: string, abortTimeout?: number, abortController?: AbortController): Promise<string> {
143+
return new Promise<string>((resolve, reject) => {
144+
const signal = abortController?.signal;
145+
const timeout = abortTimeout ? abortTimeout : 5000;
146+
const options = { rejectUnauthorized: false, signal, timeout };
147+
let result: string = '';
148+
https.get(url, options, (response) => {
149+
if (response.statusCode < 500) {
150+
response.on('data', (d) => {
151+
result = result.concat(d);
152+
});
153+
response.resume();
154+
response.on('end', () => {
155+
if (!response.complete) {
156+
reject(new Error(`The connection was terminated while the message was still being sent: ${response.statusMessage}`));
157+
} else {
158+
resolve(result);
159+
}
160+
});
161+
} else {
162+
reject(new Error(`Connect error: ${response.statusMessage}`));
163+
}
164+
}).on('error', (e) => {
165+
reject(new Error(`Connect error: ${e}`));
166+
}).on('success', (s) => {
167+
resolve(result);
168+
});
169+
});
170+
}
171+
172+
/**
173+
* Clears the Execution context as well as all cached data
174+
*/
175+
public clearCache() {
176+
if (this.executionContext) {
177+
this.executionContext.clear();
178+
}
179+
this.executionContext = new ExecutionContext();
180+
}
181+
182+
}

src/odo/command.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,24 @@ export class Command {
6262

6363
@verbose
6464
static createLocalComponent(
65-
type = '', // will use empty string in case of undefined type passed in
65+
devfileType = '', // will use empty string in case of undefined devfileType passed in
66+
devfileVersion: string = undefined,
6667
registryName: string,
6768
name: string,
6869
portNumber: number,
6970
starter: string = undefined,
7071
useExistingDevfile = false,
71-
customDevfilePath = '',
72-
devfileVersion: string = undefined,
72+
customDevfilePath = ''
7373
): CommandText {
7474
const cTxt = new CommandText('odo', 'init', [
7575
new CommandOption('--name', name)
7676
]
7777
);
78-
if (type !== '') {
79-
cTxt.addOption(new CommandOption('--devfile', type));
78+
if (devfileType !== '') {
79+
cTxt.addOption(new CommandOption('--devfile', devfileType));
80+
}
81+
if (devfileVersion) {
82+
cTxt.addOption(new CommandOption('--devfile-version', devfileVersion, false));
8083
}
8184
if (registryName) {
8285
cTxt.addOption(new CommandOption('--devfile-registry', registryName));
@@ -90,9 +93,6 @@ export class Command {
9093
if (customDevfilePath.length > 0) {
9194
cTxt.addOption(new CommandOption('--devfile-path', customDevfilePath, false));
9295
}
93-
if (devfileVersion) {
94-
cTxt.addOption(new CommandOption('--devfile-version', devfileVersion, false));
95-
}
9696
if (portNumber) {
9797
cTxt.addOption(new CommandOption(' --run-port', portNumber.toString(), false));
9898
}

src/odo/components.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Licensed under the MIT License. See LICENSE file in the project root for license information.
44
*-----------------------------------------------------------------------------------------------*/
55

6+
// TODO: Not used. A candidate to removal?
7+
68
export interface ComponentsJson {
79
// list is not present when there are no components
810
// i.e. if there are no components the JSON is `{}`

0 commit comments

Comments
 (0)