diff --git a/lib/plugins/unarchiver.js b/lib/plugins/unarchiver.js new file mode 100644 index 00000000..4a8cc239 --- /dev/null +++ b/lib/plugins/unarchiver.js @@ -0,0 +1,67 @@ +// THIS IS A SPECIAL PLUGIN, DON'T ADD IT TO THE LIST OF PLUGINS IN SETTINGS +const NopCommand = require('../nopcommand') + +const UnlockRepoMutation = ` +mutation($repoId: ID!) { + unarchiveRepository(input:{clientMutationId:"true",repositoryId: $repoId}) { + repository { + isArchived + } + } +}` +const GetRepoQuery = `query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo){ + id, + isArchived, + } +}` + +function returnValue (shouldContinue, nop) { + return { shouldContinue, nopCommands: nop } +} + +/// Unarchives repos before the other plugins. +/// This is optimized for the case where safe-settings is the source of truth when it cames to the archive config bit. + +/// If the archived config is not set then we don't even read the archived status for the repo (it saves one call) +/// This has the implication that if a repo is archived outside safe settings, other plugins may attempt to change the data and fail (it's their responsability to check if the repo is archived) +/// However if the archive config is set, we will return false to indicate other plugins shouldn't be executed if the repo is archived (and archive config is true) +module.exports = class Unarchiver { + constructor (nop, github, repo, settings, log) { + this.github = github + this.repo = repo + this.settings = settings + this.log = log + this.nop = nop + } + + // Returns true if should proceed false otherwise + async sync () { + if (typeof (this.settings?.archived) !== 'undefined') { + this.log.debug(`Checking if ${this.repo.owner}/${this.repo.repo} is archived`) + const graphQLResponse = await this.github.graphql(GetRepoQuery, { owner: this.repo.owner, repo: this.repo.repo }) + this.log(`Repo ${this.repo.owner}/${this.repo.repo} is ${graphQLResponse.repository.isArchived ? 'archived' : 'not archived'}`) + + if (graphQLResponse.repository.isArchived) { + if (this.settings.archived) { + this.log(`Repo ${this.repo.owner}/${this.repo.repo} already archived, inform other plugins should not run.`) + return returnValue(false) + } else { + this.log(`Unarchiving ${this.repo.owner}/${this.repo.repo} ${graphQLResponse.repository.id}`) + if (this.nop) { + return returnValue(true, [new NopCommand('Unarchiver', this.repo, null, 'will unarchive')]) + } else { + const graphQLUnlockResponse = await this.github.graphql(UnlockRepoMutation, { repoId: graphQLResponse.repository.id }) + this.log.debug(`Unarchived result ${JSON.stringify(graphQLUnlockResponse)}`) + + return returnValue(true) + } + } + } + this.log(`Repo ${this.repo.owner}/${this.repo.repo} not archived, ignoring.`) + } else { + this.log(`Repo ${this.repo.owner}/${this.repo.repo} archived config not set, ignoring.`) + } + return returnValue(true) + } +} diff --git a/lib/settings.js b/lib/settings.js index 6a97aad0..8bd2d0c1 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -3,6 +3,7 @@ const fs = require('fs') const Glob = require('./glob') const NopCommand = require('./nopcommand') const { throws } = require('assert') +const Unarchiver = require('./plugins/unarchiver') class Settings { static async syncAll(nop, context, repo, config, ref) { @@ -152,39 +153,49 @@ ${this.results.reduce((x,y) => { if (overrideRepoConfig) { repoConfig = this.mergeDeep({}, repoConfig, overrideRepoConfig) } - if (repoConfig) { - try { - this.log.debug(`found a matching repoconfig for this repo ${JSON.stringify(repoConfig)}`) + + // Although this is a plugin, it must be handled differently since it cannot run in parallel with the other plugins + // Because if the repo is archived we need to unarchived before all other plugins. + // If the repo is archived but there is no config to unarchive we don't run any plugins since they would fail anyway (and uncessary calls as well) + const {shouldContinue, nopCommands} = await new Unarchiver(this.nop, this.github, repo, repoConfig ?? config, this.log).sync() + + if (nopCommands) this.appendToResults(nopCommands) + + if(shouldContinue) { + if (repoConfig) { + try { + this.log.debug(`found a matching repoconfig for this repo ${JSON.stringify(repoConfig)}`) + const childPlugins = this.childPluginsList(repo.repo) + const RepoPlugin = Settings.PLUGINS.repository + return new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log).sync().then( res => { + this.appendToResults(res) + return Promise.all( + childPlugins.map(([Plugin, config]) => { + return new Plugin(this.nop, this.github, repo, config, this.log).sync() + })) + }).then( res => { + this.appendToResults(res) + }) + } catch(e) { + if (this.nop) { + const nopcommand = new NopCommand(this.constructor.name, this.repo, null,e, "ERROR") + console.error(`NOPCOMMAND ${JSON.stringify(nopcommand)}`) + this.appendToResults([nopcommand]) + //throw e + } else { + throw e + } + } + + } else { + this.log.debug(`Didnt find any a matching repoconfig for this repo ${JSON.stringify(repo)} in ${JSON.stringify(this.repoConfigs)}`) const childPlugins = this.childPluginsList(repo.repo) - const RepoPlugin = Settings.PLUGINS.repository - return new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log).sync().then( res => { - this.appendToResults(res) - return Promise.all( - childPlugins.map(([Plugin, config]) => { - return new Plugin(this.nop, this.github, repo, config, this.log).sync() - })) - }).then( res => { + return Promise.all(childPlugins.map(([Plugin, config]) => { + return new Plugin(this.nop, this.github, repo, config, this.log).sync().then( res => { this.appendToResults(res) }) - } catch(e) { - if (this.nop) { - const nopcommand = new NopCommand(this.constructor.name, this.repo, null,e, "ERROR") - console.error(`NOPCOMMAND ${JSON.stringify(nopcommand)}`) - this.appendToResults([nopcommand]) - //throw e - } else { - throw e - } + })) } - - } else { - this.log.debug(`Didnt find any a matching repoconfig for this repo ${JSON.stringify(repo)} in ${JSON.stringify(this.repoConfigs)}`) - const childPlugins = this.childPluginsList(repo.repo) - return Promise.all(childPlugins.map(([Plugin, config]) => { - return new Plugin(this.nop, this.github, repo, config, this.log).sync().then( res => { - this.appendToResults(res) - }) - })) } }