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+ }
0 commit comments