4
4
* See License.AGPL.txt in the project root for license information.
5
5
*/
6
6
7
- import * as crypto from "crypto" ;
8
7
import { inject , injectable } from "inversify" ;
8
+ import fetch from "node-fetch" ;
9
9
import * as path from "path" ;
10
+ import * as crypto from "crypto" ;
10
11
12
+ import { log , LogContext } from "@gitpod/gitpod-protocol/lib/util/logging" ;
11
13
import {
12
- AdditionalContentContext ,
13
- Commit ,
14
+ User ,
15
+ WorkspaceConfig ,
14
16
CommitContext ,
17
+ Repository ,
18
+ ImageConfigString ,
15
19
ExternalImageConfigFile ,
16
20
ImageConfigFile ,
17
- ImageConfigString ,
21
+ Commit ,
18
22
NamedWorkspaceFeatureFlag ,
19
- ProjectConfig ,
20
- Repository ,
21
- User ,
23
+ AdditionalContentContext ,
22
24
WithDefaultConfig ,
23
- WorkspaceConfig ,
25
+ ProjectConfig ,
24
26
} from "@gitpod/gitpod-protocol" ;
25
27
import { GitpodFileParser } from "@gitpod/gitpod-protocol/lib/gitpod-file-parser" ;
26
- import { log , LogContext } from "@gitpod/gitpod-protocol/lib/util/logging" ;
27
28
28
- import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing" ;
29
+ import { MaybeContent } from "../repohost/file-provider" ;
30
+ import { ConfigurationService } from "../config/configuration-service" ;
29
31
import { HostContextProvider } from "../auth/host-context-provider" ;
32
+ import { AuthorizationService } from "../user/authorization-service" ;
33
+ import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing" ;
30
34
import { Config } from "../config" ;
31
- import { ConfigurationService } from "../config/configuration-service" ;
35
+ import { EntitlementService } from "../billing/entitlement-service" ;
36
+ import { TeamDB } from "@gitpod/gitpod-db/lib" ;
32
37
33
38
const POD_PATH_WORKSPACE_BASE = "/workspace" ;
34
39
35
40
@injectable ( )
36
41
export class ConfigProvider {
37
- constructor (
38
- @inject ( GitpodFileParser ) private readonly gitpodParser : GitpodFileParser ,
39
- @inject ( HostContextProvider ) private readonly hostContextProvider : HostContextProvider ,
40
- @inject ( Config ) private readonly config : Config ,
41
- @inject ( ConfigurationService ) private readonly configurationService : ConfigurationService ,
42
- ) { }
42
+ static readonly DEFINITELY_GP_REPO : Repository = {
43
+ host : "github.com" ,
44
+ owner : "gitpod-io" ,
45
+ name : "definitely-gp" ,
46
+ cloneUrl : "https://github.com/gitpod-io/definitely-gp" ,
47
+ } ;
48
+
49
+ @inject ( GitpodFileParser ) protected readonly gitpodParser : GitpodFileParser ;
50
+ @inject ( HostContextProvider ) protected readonly hostContextProvider : HostContextProvider ;
51
+ @inject ( AuthorizationService ) protected readonly authService : AuthorizationService ;
52
+ @inject ( Config ) protected readonly config : Config ;
53
+ @inject ( ConfigurationService ) protected readonly configurationService : ConfigurationService ;
54
+ @inject ( EntitlementService ) protected readonly entitlementService : EntitlementService ;
55
+ @inject ( TeamDB ) protected readonly teamDB : TeamDB ;
43
56
44
57
public async fetchConfig (
45
58
ctx : TraceContext ,
@@ -83,10 +96,15 @@ export class ConfigProvider {
83
96
config . image = this . config . workspaceDefaults . workspaceImage ;
84
97
} else if ( ImageConfigFile . is ( config . image ) ) {
85
98
const dockerfilePath = [ configBasePath , config . image . file ] . filter ( ( s ) => ! ! s ) . join ( "/" ) ;
86
- const repo = commit . repository ;
87
- const rev = commit . revision ;
99
+ let repo = commit . repository ;
100
+ let rev = commit . revision ;
88
101
const image = config . image ! ;
89
102
103
+ if ( config . _origin === "definitely-gp" ) {
104
+ repo = ConfigProvider . DEFINITELY_GP_REPO ;
105
+ rev = "master" ;
106
+ image . file = dockerfilePath ;
107
+ }
90
108
if ( ! ( AdditionalContentContext . is ( commit ) && commit . additionalFiles [ dockerfilePath ] ) ) {
91
109
config . image = < ExternalImageConfigFile > {
92
110
...image ,
@@ -125,7 +143,7 @@ export class ConfigProvider {
125
143
}
126
144
}
127
145
128
- private async fetchCustomConfig (
146
+ protected async fetchCustomConfig (
129
147
ctx : TraceContext ,
130
148
user : User ,
131
149
commit : CommitContext ,
@@ -136,7 +154,7 @@ export class ConfigProvider {
136
154
137
155
try {
138
156
let customConfig : WorkspaceConfig | undefined ;
139
- const configBasePath = "" ;
157
+ let configBasePath = "" ;
140
158
if ( AdditionalContentContext . is ( commit ) && commit . additionalFiles [ ".gitpod.yml" ] ) {
141
159
customConfigString = commit . additionalFiles [ ".gitpod.yml" ] ;
142
160
const parseResult = this . gitpodParser . parse ( customConfigString ) ;
@@ -165,6 +183,21 @@ export class ConfigProvider {
165
183
customConfigString = await contextRepoConfig ;
166
184
let origin : WorkspaceConfig [ "_origin" ] = "repo" ;
167
185
186
+ if ( ! customConfigString ) {
187
+ /* We haven't found a Gitpod configuration file in the context repo - check definitely-gp.
188
+ *
189
+ * In case we had found a config file here, we'd still be checking the definitely GP repo, just to save some time.
190
+ * While all those checks will be in vain, they should not leak memory either as they'll simply
191
+ * be resolved and garbage collected.
192
+ */
193
+ const definitelyGpConfig = this . fetchExternalGitpodFileContent ( { span } , commit . repository ) ;
194
+ const { content, basePath } = await definitelyGpConfig ;
195
+ customConfigString = content ;
196
+ // We do not only care about the config itself but also where we got it from
197
+ configBasePath = basePath ;
198
+ origin = "definitely-gp" ;
199
+ }
200
+
168
201
if ( ! customConfigString ) {
169
202
const inferredConfig = this . configurationService . guessRepositoryConfiguration (
170
203
{ span } ,
@@ -215,7 +248,7 @@ export class ConfigProvider {
215
248
} ;
216
249
}
217
250
218
- private async fetchWorkspaceImageSourceDocker (
251
+ protected async fetchWorkspaceImageSourceDocker (
219
252
ctx : TraceContext ,
220
253
repository : Repository ,
221
254
revisionOrTagOrBranch : string ,
@@ -254,7 +287,101 @@ export class ConfigProvider {
254
287
}
255
288
}
256
289
257
- private async validateConfig ( config : WorkspaceConfig , user : User ) : Promise < void > {
290
+ protected async fillInDefaultLocations (
291
+ cfg : WorkspaceConfig | undefined ,
292
+ inferredConfig : Promise < WorkspaceConfig | undefined > ,
293
+ ) : Promise < void > {
294
+ if ( ! cfg ) {
295
+ // there is no config - return
296
+ return ;
297
+ }
298
+
299
+ if ( ! cfg . checkoutLocation ) {
300
+ const inferredCfg = await inferredConfig ;
301
+ if ( inferredCfg ) {
302
+ cfg . checkoutLocation = inferredCfg . checkoutLocation ;
303
+ }
304
+ }
305
+ if ( ! cfg . workspaceLocation ) {
306
+ const inferredCfg = await inferredConfig ;
307
+ if ( inferredCfg ) {
308
+ cfg . workspaceLocation = inferredCfg . workspaceLocation ;
309
+ }
310
+ }
311
+ }
312
+
313
+ protected async fetchExternalGitpodFileContent (
314
+ ctx : TraceContext ,
315
+ repository : Repository ,
316
+ ) : Promise < { content : MaybeContent ; basePath : string } > {
317
+ const span = TraceContext . startSpan ( "fetchExternalGitpodFileContent" , ctx ) ;
318
+ span . setTag ( "repo" , `${ repository . owner } /${ repository . name } ` ) ;
319
+
320
+ if ( this . config . definitelyGpDisabled ) {
321
+ span . finish ( ) ;
322
+ return {
323
+ content : undefined ,
324
+ basePath : `${ repository . name } ` ,
325
+ } ;
326
+ }
327
+
328
+ try {
329
+ const ownerConfigBasePath = `${ repository . name } /${ repository . owner } ` ;
330
+ const baseConfigBasePath = `${ repository . name } ` ;
331
+
332
+ const possibleConfigs = [
333
+ [ this . fetchDefinitelyGpContent ( { span } , `${ ownerConfigBasePath } /.gitpod.yml` ) , ownerConfigBasePath ] ,
334
+ [ this . fetchDefinitelyGpContent ( { span } , `${ ownerConfigBasePath } /.gitpod` ) , ownerConfigBasePath ] ,
335
+ [ this . fetchDefinitelyGpContent ( { span } , `${ baseConfigBasePath } /.gitpod.yml` ) , baseConfigBasePath ] ,
336
+ [ this . fetchDefinitelyGpContent ( { span } , `${ baseConfigBasePath } /.gitpod` ) , baseConfigBasePath ] ,
337
+ ] ;
338
+ for ( const [ configPromise , basePath ] of possibleConfigs ) {
339
+ const ownerConfig = await configPromise ;
340
+ if ( ownerConfig !== undefined ) {
341
+ return {
342
+ content : ownerConfig ,
343
+ basePath : basePath as string ,
344
+ } ;
345
+ }
346
+ }
347
+ return {
348
+ content : undefined ,
349
+ basePath : baseConfigBasePath ,
350
+ } ;
351
+ } catch ( e ) {
352
+ TraceContext . setError ( { span } , e ) ;
353
+ throw e ;
354
+ } finally {
355
+ span . finish ( ) ;
356
+ }
357
+ }
358
+
359
+ protected async fetchDefinitelyGpContent ( ctx : TraceContext , filePath : string ) {
360
+ const span = TraceContext . startSpan ( "fetchDefinitelyGpContent" , ctx ) ;
361
+ span . setTag ( "filePath" , filePath ) ;
362
+
363
+ try {
364
+ const url = `https://raw.githubusercontent.com/gitpod-io/definitely-gp/master/${ filePath } ` ;
365
+ const response = await fetch ( url , {
366
+ timeout : 10000 ,
367
+ method : "GET" ,
368
+ } ) ;
369
+ let content ;
370
+ if ( response . ok ) {
371
+ try {
372
+ content = await response . text ( ) ;
373
+ } catch { }
374
+ }
375
+ return content ;
376
+ } catch ( e ) {
377
+ TraceContext . setError ( { span } , e ) ;
378
+ throw e ;
379
+ } finally {
380
+ span . finish ( ) ;
381
+ }
382
+ }
383
+
384
+ protected async validateConfig ( config : WorkspaceConfig , user : User ) : Promise < void > {
258
385
// Make sure the projectRoot does not leave POD_PATH_WORKSPACE_BASE as that's a common
259
386
// assumption throughout the code (e.g. ws-daemon)
260
387
const checkoutLocation = config . checkoutLocation ;
@@ -280,7 +407,7 @@ export class ConfigProvider {
280
407
}
281
408
}
282
409
283
- private leavesWorkspaceBase ( normalizedPath : string ) {
410
+ protected leavesWorkspaceBase ( normalizedPath : string ) {
284
411
const pathSegments = normalizedPath . split ( path . sep ) ;
285
412
return normalizedPath . includes ( ".." ) || pathSegments . slice ( 0 , 2 ) . join ( "/" ) != POD_PATH_WORKSPACE_BASE ;
286
413
}
0 commit comments