44 * See License.AGPL.txt in the project root for license information.
55 */
66
7- import * as crypto from "crypto" ;
87import { inject , injectable } from "inversify" ;
8+ import fetch from "node-fetch" ;
99import * as path from "path" ;
10+ import * as crypto from "crypto" ;
1011
12+ import { log , LogContext } from "@gitpod/gitpod-protocol/lib/util/logging" ;
1113import {
12- AdditionalContentContext ,
13- Commit ,
14+ User ,
15+ WorkspaceConfig ,
1416 CommitContext ,
17+ Repository ,
18+ ImageConfigString ,
1519 ExternalImageConfigFile ,
1620 ImageConfigFile ,
17- ImageConfigString ,
21+ Commit ,
1822 NamedWorkspaceFeatureFlag ,
19- ProjectConfig ,
20- Repository ,
21- User ,
23+ AdditionalContentContext ,
2224 WithDefaultConfig ,
23- WorkspaceConfig ,
25+ ProjectConfig ,
2426} from "@gitpod/gitpod-protocol" ;
2527import { GitpodFileParser } from "@gitpod/gitpod-protocol/lib/gitpod-file-parser" ;
26- import { log , LogContext } from "@gitpod/gitpod-protocol/lib/util/logging" ;
2728
28- import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing" ;
29+ import { MaybeContent } from "../repohost/file-provider" ;
30+ import { ConfigurationService } from "../config/configuration-service" ;
2931import { HostContextProvider } from "../auth/host-context-provider" ;
32+ import { AuthorizationService } from "../user/authorization-service" ;
33+ import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing" ;
3034import { 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" ;
3237
3338const POD_PATH_WORKSPACE_BASE = "/workspace" ;
3439
3540@injectable ( )
3641export 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 ;
4356
4457 public async fetchConfig (
4558 ctx : TraceContext ,
@@ -83,10 +96,15 @@ export class ConfigProvider {
8396 config . image = this . config . workspaceDefaults . workspaceImage ;
8497 } else if ( ImageConfigFile . is ( config . image ) ) {
8598 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 ;
88101 const image = config . image ! ;
89102
103+ if ( config . _origin === "definitely-gp" ) {
104+ repo = ConfigProvider . DEFINITELY_GP_REPO ;
105+ rev = "master" ;
106+ image . file = dockerfilePath ;
107+ }
90108 if ( ! ( AdditionalContentContext . is ( commit ) && commit . additionalFiles [ dockerfilePath ] ) ) {
91109 config . image = < ExternalImageConfigFile > {
92110 ...image ,
@@ -125,7 +143,7 @@ export class ConfigProvider {
125143 }
126144 }
127145
128- private async fetchCustomConfig (
146+ protected async fetchCustomConfig (
129147 ctx : TraceContext ,
130148 user : User ,
131149 commit : CommitContext ,
@@ -136,7 +154,7 @@ export class ConfigProvider {
136154
137155 try {
138156 let customConfig : WorkspaceConfig | undefined ;
139- const configBasePath = "" ;
157+ let configBasePath = "" ;
140158 if ( AdditionalContentContext . is ( commit ) && commit . additionalFiles [ ".gitpod.yml" ] ) {
141159 customConfigString = commit . additionalFiles [ ".gitpod.yml" ] ;
142160 const parseResult = this . gitpodParser . parse ( customConfigString ) ;
@@ -165,6 +183,21 @@ export class ConfigProvider {
165183 customConfigString = await contextRepoConfig ;
166184 let origin : WorkspaceConfig [ "_origin" ] = "repo" ;
167185
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+
168201 if ( ! customConfigString ) {
169202 const inferredConfig = this . configurationService . guessRepositoryConfiguration (
170203 { span } ,
@@ -215,7 +248,7 @@ export class ConfigProvider {
215248 } ;
216249 }
217250
218- private async fetchWorkspaceImageSourceDocker (
251+ protected async fetchWorkspaceImageSourceDocker (
219252 ctx : TraceContext ,
220253 repository : Repository ,
221254 revisionOrTagOrBranch : string ,
@@ -254,7 +287,101 @@ export class ConfigProvider {
254287 }
255288 }
256289
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 > {
258385 // Make sure the projectRoot does not leave POD_PATH_WORKSPACE_BASE as that's a common
259386 // assumption throughout the code (e.g. ws-daemon)
260387 const checkoutLocation = config . checkoutLocation ;
@@ -280,7 +407,7 @@ export class ConfigProvider {
280407 }
281408 }
282409
283- private leavesWorkspaceBase ( normalizedPath : string ) {
410+ protected leavesWorkspaceBase ( normalizedPath : string ) {
284411 const pathSegments = normalizedPath . split ( path . sep ) ;
285412 return normalizedPath . includes ( ".." ) || pathSegments . slice ( 0 , 2 ) . join ( "/" ) != POD_PATH_WORKSPACE_BASE ;
286413 }
0 commit comments