Skip to content

Commit d0be177

Browse files
decyjphrCopilot
andcommitted
Wire syncAppInstallations into syncSelectedRepos for delta processing
- Call enrichContextWithEnterprise in syncSelectedSettings (index.js) - Call syncAppInstallations with changedSubOrgs/changedRepos in syncSelectedRepos - Add _buildAppChangesFromDelta helper to extract affected apps from changed suborg/repo configs and compute repository_selection per app - syncAppInstallations now handles three modes: pre-computed delta, config-based delta (from changed files), and full sync Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8fee0dd commit d0be177

2 files changed

Lines changed: 119 additions & 3 deletions

File tree

index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
9292
const config = Object.assign({}, deploymentConfig, runtimeConfig)
9393
robot.log.debug(`config for ref ${ref} is ${JSON.stringify(config)}`)
9494

95+
// Enrich context with enterprise info for app installation management
96+
await enrichContextWithEnterprise(context)
97+
9598
// Load base branch config for NOP filtering (only show PR-introduced changes)
9699
let baseConfig = null
97100
if (nop && baseRef) {

lib/settings.js

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,15 @@ class Settings {
655655
await settings.loadConfigs()
656656
await settings.updateAll()
657657
}
658+
659+
// Sync app installations for affected apps (delta mode)
660+
await settings.syncAppInstallations({
661+
appGithub: context.appGithub,
662+
enterpriseSlug: context.enterpriseSlug,
663+
changedSubOrgs: subOrgs,
664+
changedRepos: repos
665+
})
666+
658667
await settings.handleResults()
659668
} catch (error) {
660669
settings.logError(error.message)
@@ -1423,10 +1432,14 @@ class Settings {
14231432
* @param {Array} [options.appChanges] - Pre-computed per-app changes (delta mode)
14241433
*/
14251434
async syncAppInstallations (options = {}) {
1426-
const { appGithub, enterpriseSlug, appChanges } = options
1435+
const { appGithub, enterpriseSlug, appChanges, changedSubOrgs, changedRepos } = options
14271436

14281437
const appInstallationsConfig = this.config.app_installations
1429-
if (!appInstallationsConfig || !Array.isArray(appInstallationsConfig) || appInstallationsConfig.length === 0) {
1438+
// Check if any layer has app_installations config (org, suborg, or repo)
1439+
const hasOrgConfig = appInstallationsConfig && Array.isArray(appInstallationsConfig) && appInstallationsConfig.length > 0
1440+
const hasChangedConfigs = (changedSubOrgs && changedSubOrgs.length > 0) || (changedRepos && changedRepos.length > 0)
1441+
1442+
if (!hasOrgConfig && !hasChangedConfigs && (!appChanges || appChanges.length === 0)) {
14301443
this.log.debug('No app_installations config found, skipping')
14311444
return
14321445
}
@@ -1462,8 +1475,16 @@ class Settings {
14621475

14631476
let results
14641477
if (appChanges && appChanges.length > 0) {
1465-
// Delta mode: process pre-computed changes
1478+
// Pre-computed delta mode
14661479
results = await plugin.syncDelta(appChanges)
1480+
} else if (hasChangedConfigs) {
1481+
// Delta mode: build app changes from changed suborg/repo configs
1482+
const deltaChanges = await this._buildAppChangesFromDelta(appGithub, enterpriseSlug, changedSubOrgs, changedRepos)
1483+
if (deltaChanges.length > 0) {
1484+
results = await plugin.syncDelta(deltaChanges)
1485+
} else {
1486+
results = []
1487+
}
14671488
} else {
14681489
// Full sync mode: compute desired state from all config layers
14691490
const desiredState = await this._computeFullAppDesiredState(appInstallationsConfig, appGithub, enterpriseSlug)
@@ -1476,6 +1497,98 @@ class Settings {
14761497
this.appendToResults(results)
14771498
}
14781499

1500+
/**
1501+
* Build delta-based app changes from changed suborg/repo config files.
1502+
* Extracts app_installations from each changed config and computes
1503+
* repository_selection / repository_unselection per app.
1504+
* @private
1505+
*/
1506+
async _buildAppChangesFromDelta (appGithub, enterpriseSlug, changedSubOrgs = [], changedRepos = []) {
1507+
const AppOctokitClient = require('./appOctokitClient')
1508+
const repoSelector = new RepoSelector(this.github, this.repo.owner, this.log)
1509+
const appChangeMap = new Map() // app_slug → { installation_id, repository_selection, repository_unselection }
1510+
1511+
// Get installation map (app_slug → installation_id)
1512+
let installationMap = new Map()
1513+
if (appGithub && enterpriseSlug) {
1514+
try {
1515+
const enterpriseClient = new AppOctokitClient({ github: appGithub, enterpriseSlug, log: this.log })
1516+
const orgInstallations = await enterpriseClient.listOrgInstallations(this.repo.owner)
1517+
for (const inst of orgInstallations) {
1518+
installationMap.set(inst.app_slug, inst.id)
1519+
}
1520+
} catch (e) {
1521+
this.log.error(`Failed to list org installations for delta: ${e.message}`)
1522+
return []
1523+
}
1524+
}
1525+
1526+
// Process changed suborg configs
1527+
for (const suborg of changedSubOrgs) {
1528+
const suborgConfig = this.subOrgConfigs && this.subOrgConfigs[suborg.repo]
1529+
if (!suborgConfig || !suborgConfig.app_installations) continue
1530+
1531+
// Resolve repos for this suborg's current targeting
1532+
const criteria = {}
1533+
if (suborgConfig.suborgrepos) criteria.names = suborgConfig.suborgrepos
1534+
if (suborgConfig.suborgteams) criteria.teams = suborgConfig.suborgteams
1535+
if (suborgConfig.suborgproperties) criteria.custom_properties = suborgConfig.suborgproperties
1536+
1537+
let suborgRepos = new Set()
1538+
try {
1539+
suborgRepos = await repoSelector.resolve(criteria)
1540+
} catch (e) {
1541+
this.log.debug(`Error resolving suborg repos for '${suborg.repo}': ${e.message}`)
1542+
}
1543+
1544+
for (const appConfig of suborgConfig.app_installations) {
1545+
const slug = appConfig.app_slug
1546+
if (!slug) continue
1547+
const installationId = installationMap.get(slug)
1548+
if (!installationId) continue
1549+
1550+
if (!appChangeMap.has(slug)) {
1551+
appChangeMap.set(slug, {
1552+
app_slug: slug,
1553+
installation_id: installationId,
1554+
repository_selection: new Set(),
1555+
repository_unselection: new Set()
1556+
})
1557+
}
1558+
const change = appChangeMap.get(slug)
1559+
for (const repo of suborgRepos) {
1560+
change.repository_selection.add(repo)
1561+
}
1562+
}
1563+
}
1564+
1565+
// Process changed repo configs
1566+
for (const repo of changedRepos) {
1567+
const repoConfig = this.repoConfigs &&
1568+
(this.repoConfigs[`${repo.repo}.yml`] || this.repoConfigs[`${repo.repo}.yaml`])
1569+
if (!repoConfig || !repoConfig.app_installations) continue
1570+
1571+
for (const appConfig of repoConfig.app_installations) {
1572+
const slug = appConfig.app_slug
1573+
if (!slug) continue
1574+
const installationId = installationMap.get(slug)
1575+
if (!installationId) continue
1576+
1577+
if (!appChangeMap.has(slug)) {
1578+
appChangeMap.set(slug, {
1579+
app_slug: slug,
1580+
installation_id: installationId,
1581+
repository_selection: new Set(),
1582+
repository_unselection: new Set()
1583+
})
1584+
}
1585+
appChangeMap.get(slug).repository_selection.add(repo.repo)
1586+
}
1587+
}
1588+
1589+
return [...appChangeMap.values()]
1590+
}
1591+
14791592
/**
14801593
* Compute the full desired state for all managed apps by merging
14811594
* org + suborg + repo level app_installations configs.

0 commit comments

Comments
 (0)