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