Skip to content

Commit 67c29c5

Browse files
PendaGTPdecyjphr
andauthored
fix: handle missing repo on archive plugin (#751)
Co-authored-by: Yadhav Jayaraman <57544838+decyjphr@users.noreply.github.com>
1 parent 861d70f commit 67c29c5

File tree

5 files changed

+278
-157
lines changed

5 files changed

+278
-157
lines changed

index.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,32 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
624624
return syncSettings(false, context)
625625
})
626626

627+
robot.on('repository.archived', async context => {
628+
const { payload } = context
629+
const { sender } = payload
630+
631+
if (sender.type === 'Bot') {
632+
robot.log.debug('Repository Archived by a Bot')
633+
return
634+
}
635+
robot.log.debug('Repository Archived by a Human')
636+
637+
return syncSettings(false, context)
638+
})
639+
640+
robot.on('repository.unarchived', async context => {
641+
const { payload } = context
642+
const { sender } = payload
643+
644+
if (sender.type === 'Bot') {
645+
robot.log.debug('Repository Unarchived by a Bot')
646+
return
647+
}
648+
robot.log.debug('Repository Unarchived by a Human')
649+
650+
return syncSettings(false, context)
651+
})
652+
627653
if (process.env.CRON) {
628654
/*
629655
# ┌────────────── second (optional)

lib/plugins/archive.js

Lines changed: 99 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,108 @@
1-
const NopCommand = require('../nopcommand');
2-
3-
function returnValue(shouldContinue, nop) {
4-
return { shouldContinue, nopCommands: nop };
5-
}
1+
const NopCommand = require('../nopcommand')
62

73
module.exports = class Archive {
8-
constructor(nop, github, repo, settings, log) {
9-
this.github = github;
10-
this.repo = repo;
11-
this.settings = settings;
12-
this.log = log;
13-
this.nop = nop;
4+
constructor (nop, github, repo, settings, log) {
5+
this.github = github
6+
this.repo = repo
7+
this.settings = settings
8+
this.log = log
9+
this.nop = nop
1410
}
1511

16-
// Returns true if plugin application should continue, false otherwise
17-
async sync() {
18-
// Fetch repository details using REST API
19-
const { data: repoDetails } = await this.github.repos.get({
12+
async getRepo () {
13+
try {
14+
const { data } = await this.github.repos.get({
2015
owner: this.repo.owner,
2116
repo: this.repo.repo
22-
});
23-
if (typeof this.settings?.archived !== 'undefined') {
24-
this.log.debug(`Checking if ${this.repo.owner}/${this.repo.repo} is archived`);
25-
26-
this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is ${repoDetails.archived ? 'archived' : 'not archived'}`);
27-
28-
if (repoDetails.archived) {
29-
if (this.settings.archived) {
30-
this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} already archived, inform other plugins should not run.`);
31-
return returnValue(false);
32-
}
33-
else {
34-
this.log.debug(`Unarchiving ${this.repo.owner}/${this.repo.repo}`);
35-
if (this.nop) {
36-
return returnValue(true, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will unarchive')]);
37-
}
38-
else {
39-
// Unarchive the repository using REST API
40-
const updateResponse = await this.github.repos.update({
41-
owner: this.repo.owner,
42-
repo: this.repo.repo,
43-
archived: false
44-
});
45-
this.log.debug(`Unarchive result ${JSON.stringify(updateResponse)}`);
46-
47-
return returnValue(true);
48-
}
49-
}
50-
}
51-
else {
52-
if (this.settings.archived) {
53-
this.log.debug(`Archiving ${this.repo.owner}/${this.repo.repo}`);
54-
if (this.nop) {
55-
return returnValue(false, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will archive')]);
56-
}
57-
else {
58-
// Archive the repository using REST API
59-
const updateResponse = await this.github.repos.update({
60-
owner: this.repo.owner,
61-
repo: this.repo.repo,
62-
archived: true
63-
});
64-
this.log.debug(`Archive result ${JSON.stringify(updateResponse)}`);
65-
66-
return returnValue(false);
67-
}
68-
}
69-
else {
70-
this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, ignoring.`);
71-
return returnValue(true);
72-
}
17+
})
18+
return data
19+
} catch (error) {
20+
if (error.status === 404 && !this.getDesiredArchiveState()) {
21+
return null
7322
}
74-
}
75-
else {
76-
if (repoDetails.archived) {
77-
this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is archived, ignoring.`);
78-
return returnValue(false);
79-
}
80-
else {
81-
this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, proceed as usual.`);
82-
return returnValue(true);
83-
}
23+
throw error
8424
}
8525
}
86-
};
26+
27+
async updateRepoArchiveStatus (archived) {
28+
const action = archived ? 'archive' : 'unarchive'
29+
30+
if (this.nop) {
31+
const change = { msg: 'Change found', additions: {}, modifications: { archived: action }, deletions: {} }
32+
return new NopCommand(
33+
this.constructor.name,
34+
this.repo,
35+
this.github.repos.update.endpoint(this.settings),
36+
change,
37+
'INFO'
38+
)
39+
}
40+
41+
const { data } = await this.github.repos.update({
42+
owner: this.repo.owner,
43+
repo: this.repo.repo,
44+
archived
45+
})
46+
47+
this.log.debug({ result: data }, `Repo ${this.repo.owner}/${this.repo.repo} ${action}d`)
48+
}
49+
50+
getDesiredArchiveState () {
51+
if (typeof this.settings?.archived === 'undefined') {
52+
return null
53+
}
54+
return typeof this.settings.archived === 'boolean'
55+
? this.settings.archived
56+
: this.settings.archived === 'true'
57+
}
58+
59+
shouldArchive (repository = this.repository) {
60+
const desiredState = this.getDesiredArchiveState()
61+
if (desiredState === null) return false
62+
return !repository.archived && desiredState
63+
}
64+
65+
shouldUnarchive (repository = this.repository) {
66+
const desiredState = this.getDesiredArchiveState()
67+
if (desiredState === null) return false
68+
return repository.archived && !desiredState
69+
}
70+
71+
isArchived () {
72+
return this.repository?.archived
73+
}
74+
75+
async getState () {
76+
this.repository = await this.getRepo()
77+
78+
return {
79+
isArchived: this.isArchived(),
80+
shouldArchive: this.shouldArchive(),
81+
shouldUnarchive: this.shouldUnarchive()
82+
}
83+
}
84+
85+
async sync () {
86+
this.repository = await this.getRepo()
87+
88+
const results = []
89+
90+
if (!this.repository) {
91+
this.log.warn(`Repo ${this.repo.owner}/${this.repo.repo} not found, skipping archive sync`)
92+
return results
93+
}
94+
95+
const shouldArchive = this.shouldArchive()
96+
const shouldUnarchive = this.shouldUnarchive()
97+
98+
if (!shouldArchive && !shouldUnarchive) {
99+
this.log.debug(`No archive changes needed for ${this.repo.owner}/${this.repo.repo}`)
100+
return results
101+
}
102+
103+
const archived = shouldArchive
104+
results.push(await this.updateRepoArchiveStatus(archived))
105+
106+
return results
107+
}
108+
}

lib/plugins/repository.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const ignorableFields = [
3636
'org',
3737
'force_create',
3838
'auto_init',
39-
'repo'
39+
'repo',
40+
'archived'
4041
]
4142

4243
module.exports = class Repository extends ErrorStash {

lib/settings.js

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ const env = require('./env')
1010
const CONFIG_PATH = env.CONFIG_PATH
1111
const eta = new Eta({ views: path.join(__dirname) })
1212
const SCOPE = { ORG: 'org', REPO: 'repo' } // Determine if the setting is a org setting or repo setting
13-
const yaml = require('js-yaml');
13+
const yaml = require('js-yaml')
1414

1515
class Settings {
16-
static fileCache = {};
16+
static fileCache = {}
1717

1818
static async syncAll (nop, context, repo, config, ref) {
1919
const settings = new Settings(nop, context, repo, config, ref)
@@ -170,10 +170,10 @@ class Settings {
170170

171171
// remove duplicate rows in this.results
172172
this.results = this.results.filter((thing, index, self) => {
173-
return index === self.findIndex((t) => {
174-
return t.type === thing.type && t.repo === thing.repo && t.plugin === thing.plugin
175-
})
173+
return index === self.findIndex((t) => {
174+
return t.type === thing.type && t.repo === thing.repo && t.plugin === thing.plugin
176175
})
176+
})
177177

178178
let error = false
179179
// Different logic
@@ -300,7 +300,7 @@ ${this.results.reduce((x, y) => {
300300
}
301301
}
302302

303-
async updateRepos(repo) {
303+
async updateRepos (repo) {
304304
this.subOrgConfigs = this.subOrgConfigs || await this.getSubOrgConfigs()
305305
// Keeping this as is instead of doing an object assign as that would cause `Cannot read properties of undefined (reading 'startsWith')` error
306306
// Copilot code review would recoommend using object assign but that would cause the error
@@ -336,17 +336,34 @@ ${this.results.reduce((x, y) => {
336336
if (repoConfig) {
337337
try {
338338
this.log.debug(`found a matching repoconfig for this repo ${JSON.stringify(repoConfig)}`)
339+
339340
const childPlugins = this.childPluginsList(repo)
340341
const RepoPlugin = Settings.PLUGINS.repository
341-
return new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync().then(res => {
342-
this.appendToResults(res)
343-
return Promise.all(
344-
childPlugins.map(([Plugin, config]) => {
345-
return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync()
346-
}))
347-
}).then(res => {
348-
this.appendToResults(res)
349-
})
342+
343+
const archivePlugin = new Archive(this.nop, this.github, repo, repoConfig, this.log)
344+
const { shouldArchive, shouldUnarchive } = await archivePlugin.getState()
345+
346+
if (shouldUnarchive) {
347+
this.log.debug(`Unarchiving repo ${repo.repo}`)
348+
const unArchiveResults = await archivePlugin.sync()
349+
this.appendToResults(unArchiveResults)
350+
}
351+
352+
const repoResults = await new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync()
353+
this.appendToResults(repoResults)
354+
355+
const childResults = await Promise.all(
356+
childPlugins.map(([Plugin, config]) => {
357+
return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync()
358+
})
359+
)
360+
this.appendToResults(childResults)
361+
362+
if (shouldArchive) {
363+
this.log.debug(`Archiving repo ${repo.repo}`)
364+
const archiveResults = await archivePlugin.sync()
365+
this.appendToResults(archiveResults)
366+
}
350367
} catch (e) {
351368
if (this.nop) {
352369
const nopcommand = new NopCommand(this.constructor.name, this.repo, null, `${e}`, 'ERROR')
@@ -368,7 +385,6 @@ ${this.results.reduce((x, y) => {
368385
}
369386
}
370387

371-
372388
async updateAll () {
373389
// this.subOrgConfigs = this.subOrgConfigs || await this.getSubOrgConfigs(this.github, this.repo, this.log)
374390
// this.repoConfigs = this.repoConfigs || await this.getRepoConfigs(this.github, this.repo, this.log)
@@ -791,14 +807,14 @@ ${this.results.reduce((x, y) => {
791807
* @param params Params to fetch the file with
792808
* @return The parsed YAML file
793809
*/
794-
async loadYaml(filePath) {
810+
async loadYaml (filePath) {
795811
try {
796812
const repo = { owner: this.repo.owner, repo: env.ADMIN_REPO }
797813
const params = Object.assign(repo, {
798814
path: filePath,
799815
ref: this.ref
800816
})
801-
const namespacedFilepath = `${this.repo.owner}/${filePath}`;
817+
const namespacedFilepath = `${this.repo.owner}/${filePath}`
802818

803819
// If the filepath already exists in the fileCache, add the etag to the params
804820
// to check if the file has changed
@@ -898,7 +914,6 @@ ${this.results.reduce((x, y) => {
898914
}
899915
}
900916

901-
902917
async getSubOrgRepositories (subOrgProperties) {
903918
const organizationName = this.repo.owner
904919
try {

0 commit comments

Comments
 (0)