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+ // TODO: should throw 'new Error('No Devfile registries available. Default registry is missing');'
114+ // here so we can report this to users when a webview is open
115+ return [ ] ;
116+ }
117+
118+ const devfiles : DevfileInfo [ ] = [ ] ;
119+ await Promise . all ( registries
120+ . map ( async ( registry ) : Promise < void > => {
121+ const devfileInfoList = ( await this . getDevfileInfoList ( registry . url ) )
122+ . filter ( ( devfileInfo ) => 'stack' === devfileInfo . type . toLowerCase ( ) ) ;
123+ devfileInfoList . forEach ( ( devfileInfo ) => {
124+ devfileInfo . registry = registry ;
125+ } ) ;
126+ devfiles . push ( ...devfileInfoList ) ;
127+ } ) ) ;
128+
129+ return devfiles . sort ( ( a , b ) => ( a . name < b . name ? - 1 : 1 ) ) ;
130+ }
131+
132+ /**
133+ * Returns a devfile data with the raw devfile text attached
134+ *
135+ * @returns a devfile data with raw devfile text attached
136+ */
137+ public async getRegistryDevfile ( registryUrl : string , name : string , version ?: string ) : Promise < DevfileData > {
138+ const rawDevfile = await this . _getDevfile ( registryUrl , name , version ? version : 'latest' ) ;
139+ const devfile = YAML . load ( rawDevfile ) as DevfileData ;
140+ devfile . yaml = rawDevfile ;
141+ return devfile ;
142+ }
143+
144+ private static async _get ( url : string , abortTimeout ?: number , abortController ?: AbortController ) : Promise < string > {
145+ return new Promise < string > ( ( resolve , reject ) => {
146+ const signal = abortController ?. signal ;
147+ const timeout = abortTimeout ? abortTimeout : 5000 ;
148+ const options = { rejectUnauthorized : false , signal, timeout } ;
149+ let result : string = '' ;
150+ https . get ( url , options , ( response ) => {
151+ if ( response . statusCode < 500 ) {
152+ response . on ( 'data' , ( d ) => {
153+ result = result . concat ( d ) ;
154+ } ) ;
155+ response . resume ( ) ;
156+ response . on ( 'end' , ( ) => {
157+ if ( ! response . complete ) {
158+ reject ( new Error ( `The connection was terminated while the message was still being sent: ${ response . statusMessage } ` ) ) ;
159+ } else {
160+ resolve ( result ) ;
161+ }
162+ } ) ;
163+ } else {
164+ reject ( new Error ( `Connect error: ${ response . statusMessage } ` ) ) ;
165+ }
166+ } ) . on ( 'error' , ( e ) => {
167+ reject ( new Error ( `Connect error: ${ e } ` ) ) ;
168+ } ) . on ( 'success' , ( s ) => {
169+ resolve ( result ) ;
170+ } ) ;
171+ } ) ;
172+ }
173+
174+ /**
175+ * Clears the Execution context as well as all cached data
176+ */
177+ public clearCache ( ) {
178+ if ( this . executionContext ) {
179+ this . executionContext . clear ( ) ;
180+ }
181+ this . executionContext = new ExecutionContext ( ) ;
182+ }
183+
184+ }
0 commit comments