@@ -25,7 +25,7 @@ class Settings {
2525 return settings
2626 }
2727
28- static async syncSubOrgs ( nop , context , suborg , repo , config , ref ) {
28+ static async syncSubOrgs ( nop , context , suborg , repo , config , ref ) {
2929 const settings = new Settings ( nop , context , repo , config , ref , suborg )
3030 try {
3131 await settings . loadConfigs ( )
@@ -37,7 +37,7 @@ class Settings {
3737 }
3838 }
3939
40- static async sync ( nop , context , repo , config , ref ) {
40+ static async sync ( nop , context , repo , config , ref ) {
4141 const settings = new Settings ( nop , context , repo , config , ref )
4242 try {
4343 await settings . loadConfigs ( repo )
@@ -52,13 +52,13 @@ class Settings {
5252 }
5353 }
5454
55- static async handleError ( nop , context , repo , config , ref , nopcommand ) {
55+ static async handleError ( nop , context , repo , config , ref , nopcommand ) {
5656 const settings = new Settings ( nop , context , repo , config , ref )
5757 settings . appendToResults ( [ nopcommand ] )
5858 await settings . handleResults ( )
5959 }
6060
61- constructor ( nop , context , repo , config , ref , suborg ) {
61+ constructor ( nop , context , repo , config , ref , suborg ) {
6262 this . ref = ref
6363 this . context = context
6464 this . installation_id = context . payload . installation . id
@@ -97,7 +97,7 @@ class Settings {
9797 }
9898
9999 // Create a check in the Admin repo for safe-settings.
100- async createCheckRun ( ) {
100+ async createCheckRun ( ) {
101101 const startTime = new Date ( )
102102 let conclusion = 'success'
103103 let details = `Run on: \`${ new Date ( ) . toISOString ( ) } \``
@@ -143,7 +143,7 @@ class Settings {
143143 } )
144144 }
145145
146- logError ( msg ) {
146+ logError ( msg ) {
147147 this . log . error ( msg )
148148 this . errors . push ( {
149149 owner : this . repo . owner ,
@@ -153,7 +153,7 @@ class Settings {
153153 } )
154154 }
155155
156- async handleResults ( ) {
156+ async handleResults ( ) {
157157 const { payload } = this . context
158158
159159 // Create a checkrun if not in nop mode
@@ -163,6 +163,13 @@ class Settings {
163163 return
164164 }
165165
166+ //remove duplicate rows in this.results
167+ this . results = this . results . filter ( ( thing , index , self ) => {
168+ return index === self . findIndex ( ( t ) => {
169+ return t . type === thing . type && t . repo === thing . repo && t . plugin === thing . plugin
170+ } )
171+ } )
172+
166173 let error = false
167174 // Different logic
168175 const stats = {
@@ -227,23 +234,23 @@ class Settings {
227234#### :robot: Safe-Settings config changes detected:
228235
229236${ this . results . reduce ( ( x , y ) => {
230- if ( ! y ) {
231- return x
232- }
233- if ( y . type === 'ERROR' ) {
234- error = true
235- return `${ x }
237+ if ( ! y ) {
238+ return x
239+ }
240+ if ( y . type === 'ERROR' ) {
241+ error = true
242+ return `${ x }
236243<tr><td> ❗ ${ y . action . msg } </td><td> ${ y . plugin } </td><td> ${ prettify ( y . repo ) } </td><td> ${ prettify ( y . action . additions ) } </td><td> ${ prettify ( y . action . deletions ) } </td><td> ${ prettify ( y . action . modifications ) } </td><tr>`
237- } else if ( y . action . additions === null && y . action . deletions === null && y . action . modifications === null ) {
238- return `${ x } `
239- } else {
240- if ( y . action === undefined ) {
241- return `${ x } `
242- }
243- return `${ x }
244+ } else if ( y . action . additions === null && y . action . deletions === null && y . action . modifications === null ) {
245+ return `${ x } `
246+ } else {
247+ if ( y . action === undefined ) {
248+ return `${ x } `
249+ }
250+ return `${ x }
244251<tr><td> ✋ </td><td> ${ y . plugin } </td><td> ${ prettify ( y . repo ) } </td><td> ${ prettify ( y . action . additions ) } </td><td> ${ prettify ( y . action . deletions ) } </td><td> ${ prettify ( y . action . modifications ) } </td><tr>`
245- }
246- } , table ) }
252+ }
253+ } , table ) }
247254`
248255
249256 const pullRequest = payload . check_run . check_suite . pull_requests [ 0 ]
@@ -273,12 +280,12 @@ ${this.results.reduce((x, y) => {
273280 await this . github . checks . update ( params )
274281 }
275282
276- async loadConfigs ( repo ) {
283+ async loadConfigs ( repo ) {
277284 this . subOrgConfigs = await this . getSubOrgConfigs ( )
278285 this . repoConfigs = await this . getRepoConfigs ( repo )
279286 }
280287
281- async updateOrg ( ) {
288+ async updateOrg ( ) {
282289 const rulesetsConfig = this . config . rulesets
283290 if ( rulesetsConfig ) {
284291 const RulesetsPlugin = Settings . PLUGINS . rulesets
@@ -288,7 +295,7 @@ ${this.results.reduce((x, y) => {
288295 }
289296 }
290297
291- async updateRepos ( repo ) {
298+ async updateRepos ( repo ) {
292299 this . subOrgConfigs = this . subOrgConfigs || await this . getSubOrgConfigs ( )
293300 let repoConfig = this . config . repository
294301 if ( repoConfig ) {
@@ -354,15 +361,15 @@ ${this.results.reduce((x, y) => {
354361 }
355362 }
356363
357- async updateAll ( ) {
364+ async updateAll ( ) {
358365 // this.subOrgConfigs = this.subOrgConfigs || await this.getSubOrgConfigs(this.github, this.repo, this.log)
359366 // this.repoConfigs = this.repoConfigs || await this.getRepoConfigs(this.github, this.repo, this.log)
360367 return this . eachRepositoryRepos ( this . github , this . config . restrictedRepos , this . log ) . then ( res => {
361368 this . appendToResults ( res )
362369 } )
363370 }
364371
365- getSubOrgConfig ( repoName ) {
372+ getSubOrgConfig ( repoName ) {
366373 if ( this . subOrgConfigs ) {
367374 for ( const k of Object . keys ( this . subOrgConfigs ) ) {
368375 const repoPattern = new Glob ( k )
@@ -375,13 +382,13 @@ ${this.results.reduce((x, y) => {
375382 }
376383
377384 // Remove Org specific configs from the repo config
378- returnRepoSpecificConfigs ( config ) {
385+ returnRepoSpecificConfigs ( config ) {
379386 const newConfig = Object . assign ( { } , config ) // clone
380387 delete newConfig . rulesets
381388 return newConfig
382389 }
383390
384- childPluginsList ( repo ) {
391+ childPluginsList ( repo ) {
385392 const repoName = repo . repo
386393 const subOrgOverrideConfig = this . getSubOrgConfig ( repoName )
387394 this . log . debug ( `suborg config for ${ repoName } is ${ JSON . stringify ( subOrgOverrideConfig ) } ` )
@@ -413,7 +420,7 @@ ${this.results.reduce((x, y) => {
413420 return childPlugins
414421 }
415422
416- validate ( section , baseConfig , overrideConfig ) {
423+ validate ( section , baseConfig , overrideConfig ) {
417424 const configValidator = this . configvalidators [ section ]
418425 if ( configValidator ) {
419426 this . log . debug ( `Calling configvalidator for key ${ section } ` )
@@ -432,7 +439,7 @@ ${this.results.reduce((x, y) => {
432439 }
433440 }
434441
435- isRestricted ( repoName ) {
442+ isRestricted ( repoName ) {
436443 const restrictedRepos = this . config . restrictedRepos
437444 // Skip configuring any restricted repos
438445 if ( Array . isArray ( restrictedRepos ) ) {
@@ -464,11 +471,11 @@ ${this.results.reduce((x, y) => {
464471 return false
465472 }
466473
467- includesRepo ( repoName , restrictedRepos ) {
474+ includesRepo ( repoName , restrictedRepos ) {
468475 return restrictedRepos . filter ( ( restrictedRepo ) => { return RegExp ( restrictedRepo ) . test ( repoName ) } ) . length > 0
469476 }
470477
471- async eachRepositoryRepos ( github , restrictedRepos , log ) {
478+ async eachRepositoryRepos ( github , restrictedRepos , log ) {
472479 log . debug ( 'Fetching repositories' )
473480 return github . paginate ( 'GET /installation/repositories' ) . then ( repositories => {
474481 return Promise . all ( repositories . map ( repository => {
@@ -489,7 +496,7 @@ ${this.results.reduce((x, y) => {
489496 * @param params Params to fetch the file with
490497 * @return The parsed YAML file
491498 */
492- async loadConfigMap ( params ) {
499+ async loadConfigMap ( params ) {
493500 try {
494501 this . log . debug ( ` In loadConfigMap ${ JSON . stringify ( params ) } ` )
495502 const response = await this . github . repos . getContent ( params ) . catch ( e => {
@@ -536,7 +543,7 @@ ${this.results.reduce((x, y) => {
536543 * @param params Params to fetch the file with
537544 * @return The parsed YAML file
538545 */
539- async getRepoConfigMap ( ) {
546+ async getRepoConfigMap ( ) {
540547 try {
541548 this . log . debug ( ` In getRepoConfigMap ${ JSON . stringify ( this . repo ) } ` )
542549 // GitHub getContent api has a hard limit of returning 1000 entries without
@@ -603,7 +610,7 @@ ${this.results.reduce((x, y) => {
603610 * @param params Params to fetch the file with
604611 * @return The parsed YAML file
605612 */
606- async getSubOrgConfigMap ( ) {
613+ async getSubOrgConfigMap ( ) {
607614 try {
608615 this . log . debug ( ` In getSubOrgConfigMap ${ JSON . stringify ( this . repo ) } ` )
609616 const repo = { owner : this . repo . owner , repo : env . ADMIN_REPO }
@@ -630,7 +637,7 @@ ${this.results.reduce((x, y) => {
630637 * @param {* } repo repo param
631638 * @returns repoConfigs object
632639 */
633- async getRepoConfigs ( repo ) {
640+ async getRepoConfigs ( repo ) {
634641 try {
635642 const overridePaths = await this . getRepoConfigMap ( )
636643 const repoConfigs = { }
@@ -682,12 +689,11 @@ ${this.results.reduce((x, y) => {
682689 * @param params Params to fetch the file with
683690 * @return The parsed YAML file
684691 */
685- async getSubOrgConfigs ( ) {
692+ async getSubOrgConfigs ( ) {
686693 try {
687- if ( this . subOrgConfigMap ) {
688- this . log . debug ( `SubOrg config was changed and the associated overridePaths is = ${ JSON . stringify ( this . subOrgConfigMap ) } ` )
689- }
690- const overridePaths = this . subOrgConfigMap || await this . getSubOrgConfigMap ( )
694+ // Get all suborg configs even though we might be here becuase of a suborg config change
695+ // we will filter them out if request is due to a suborg config change
696+ const overridePaths = await this . getSubOrgConfigMap ( )
691697 const subOrgConfigs = { }
692698
693699 for ( const override of overridePaths ) {
@@ -699,7 +705,19 @@ ${this.results.reduce((x, y) => {
699705 subOrgConfigs [ override . name ] = data
700706 if ( data . suborgrepos ) {
701707 data . suborgrepos . forEach ( repository => {
702- subOrgConfigs [ repository ] = data
708+ this . storeSubOrgConfigIfNoConflicts ( subOrgConfigs , override . path , repository , data )
709+
710+ // In case support for multiple suborg configs for the same repo is required, merge the configs.
711+ //
712+ // Planned for the future to support multiple suborgrepos for the same repo
713+ //
714+ // if (existingConfigForRepo) {
715+ // subOrgConfigs[repository] = this.mergeDeep.mergeDeep({}, existingConfigForRepo, data)
716+ // } else {
717+ // subOrgConfigs[repository] = data
718+ // }
719+
720+ subOrgConfigs [ repository ] = Object . assign ( { } , data , { source : override . path } )
703721 } )
704722 }
705723 if ( data . suborgteams ) {
@@ -709,7 +727,7 @@ ${this.results.reduce((x, y) => {
709727 await Promise . all ( promises ) . then ( res => {
710728 res . forEach ( r => {
711729 r . forEach ( e => {
712- subOrgConfigs [ e . name ] = data
730+ this . storeSubOrgConfigIfNoConflicts ( subOrgConfigs , override . path , e . name , data )
713731 } )
714732 } )
715733 } )
@@ -721,12 +739,26 @@ ${this.results.reduce((x, y) => {
721739 await Promise . all ( promises ) . then ( res => {
722740 res . forEach ( r => {
723741 r . forEach ( e => {
724- subOrgConfigs [ e . repository_name ] = data
742+ this . storeSubOrgConfigIfNoConflicts ( subOrgConfigs , override . path , e . repository_name , data )
725743 } )
726744 } )
727745 } )
728746 }
729747 }
748+
749+ // If this was result of a suborg config change, only return the repos that are part of the suborg config
750+ if ( this . subOrgConfigMap ) {
751+ this . log . debug ( `SubOrg config was changed and the associated overridePaths is = ${ JSON . stringify ( this . subOrgConfigMap ) } ` )
752+ // enumerate the properties of the subOrgConfigs object and delete the ones that are not part of the suborg
753+ for ( const [ key , value ] of Object . entries ( subOrgConfigs ) ) {
754+ if ( ! this . subOrgConfigMap . some ( ( overridePath ) => {
755+ return overridePath . path === value . source
756+ }
757+ ) ) {
758+ delete subOrgConfigs [ key ]
759+ }
760+ }
761+ }
730762 return subOrgConfigs
731763 } catch ( e ) {
732764 if ( this . nop ) {
@@ -740,13 +772,21 @@ ${this.results.reduce((x, y) => {
740772 }
741773 }
742774
775+ storeSubOrgConfigIfNoConflicts ( subOrgConfigs , overridePath , repoName , data ) {
776+ const existingConfigForRepo = subOrgConfigs [ repoName ]
777+ if ( existingConfigForRepo && existingConfigForRepo . source !== overridePath ) {
778+ throw new Error ( `Multiple suborg configs for ${ repoName } in ${ overridePath } and ${ existingConfigForRepo ?. source } ` )
779+ }
780+ subOrgConfigs [ repoName ] = Object . assign ( { } , data , { source : overridePath } )
781+ }
782+
743783 /**
744784 * Loads a file from GitHub
745785 *
746786 * @param params Params to fetch the file with
747787 * @return The parsed YAML file
748788 */
749- async loadYaml ( filePath ) {
789+ async loadYaml ( filePath ) {
750790 try {
751791 const repo = { owner : this . repo . owner , repo : env . ADMIN_REPO }
752792 const params = Object . assign ( repo , { path : filePath , ref : this . ref } )
@@ -783,13 +823,16 @@ ${this.results.reduce((x, y) => {
783823 }
784824 }
785825
786- appendToResults ( res ) {
826+ appendToResults ( res ) {
787827 if ( this . nop ) {
788- this . results = this . results . concat ( res . flat ( 3 ) )
828+ //Remove nulls and undefined from the results
829+ const results = res . flat ( 3 ) . filter ( r => r )
830+
831+ this . results = this . results . concat ( results )
789832 }
790833 }
791834
792- async getReposForTeam ( teamslug ) {
835+ async getReposForTeam ( teamslug ) {
793836 const options = this . github . rest . teams . listReposInOrg . endpoint . merge ( {
794837 org : this . repo . owner ,
795838 team_slug : teamslug ,
@@ -798,20 +841,19 @@ ${this.results.reduce((x, y) => {
798841 return this . github . paginate ( options )
799842 }
800843
801- async getReposForCustomProperty ( customPropertyTuple ) {
802- const name = Object . keys ( customPropertyTuple ) [ 0 ]
844+ async getReposForCustomProperty ( customPropertyTuple ) {
845+ const name = Object . keys ( customPropertyTuple ) [ 0 ]
803846 let q = `props.${ name } :${ customPropertyTuple [ name ] } `
804847 q = encodeURIComponent ( q )
805848 const options = this . github . request . endpoint ( ( `/orgs/${ this . repo . owner } /properties/values?repository_query=${ q } ` ) )
806849 return this . github . paginate ( options )
807850 }
808851
809-
810- isObject ( item ) {
852+ isObject ( item ) {
811853 return ( item && typeof item === 'object' && ! Array . isArray ( item ) )
812854 }
813855
814- isIterable ( obj ) {
856+ isIterable ( obj ) {
815857 // checks for null and undefined
816858 if ( obj == null ) {
817859 return false
0 commit comments