Skip to content

Commit eb269bd

Browse files
Do not populate repo list in case of installation:deleted event (#1690)
* Do not populate repo list in case of installation:deleted event * Implement discussed way to handle the situation * Fix codestyle * Fix build (I hope?) * Update GHEventPayload.java * Update GHEventPayload.java * Fix missing import * Update src/test/java/org/kohsuke/github/GHEventPayloadTest.java * Update src/test/java/org/kohsuke/github/GHEventPayloadTest.java * Apply suggestions from code review * Update test data --------- Co-authored-by: Liam Newman <[email protected]>
1 parent 0fac2a9 commit eb269bd

File tree

12 files changed

+766
-22
lines changed

12 files changed

+766
-22
lines changed

src/main/java/org/kohsuke/github/GHEventPayload.java

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.kohsuke.github;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
34
import com.fasterxml.jackson.annotation.JsonSetter;
45
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
56

67
import java.io.IOException;
78
import java.io.Reader;
9+
import java.util.ArrayList;
810
import java.util.Collections;
911
import java.util.Date;
1012
import java.util.List;
@@ -265,16 +267,47 @@ void lateBind() {
265267
* @see <a href="https://docs.github.com/en/rest/reference/apps#installations">GitHub App Installation</a>
266268
*/
267269
public static class Installation extends GHEventPayload {
268-
private List<GHRepository> repositories;
270+
271+
private List<Repository> repositories;
272+
private List<GHRepository> ghRepositories = null;
269273

270274
/**
271-
* Gets repositories.
275+
* Gets repositories. For the "deleted" action please rather call {@link #getRawRepositories()}
272276
*
273277
* @return the repositories
274278
*/
275279
public List<GHRepository> getRepositories() {
280+
if ("deleted".equalsIgnoreCase(getAction())) {
281+
throw new IllegalStateException("Can't call #getRepositories() on Installation event "
282+
+ "with 'deleted' action. Call #getRawRepositories() instead.");
283+
}
284+
285+
if (ghRepositories == null) {
286+
ghRepositories = new ArrayList<>(repositories.size());
287+
try {
288+
for (Repository singleRepo : repositories) {
289+
// populate each repository
290+
// the repository information provided here is so limited
291+
// as to be unusable without populating, so we do it eagerly
292+
ghRepositories.add(this.root().getRepositoryById(singleRepo.getId()));
293+
}
294+
} catch (IOException e) {
295+
throw new GHException("Failed to refresh repositories", e);
296+
}
297+
}
298+
299+
return Collections.unmodifiableList(ghRepositories);
300+
}
301+
302+
/**
303+
* Returns a list of raw, unpopulated repositories. Useful when calling from within Installation event with
304+
* action "deleted". You can't fetch the info for repositories of an already deleted installation.
305+
*
306+
* @return the list of raw Repository records
307+
*/
308+
public List<Repository> getRawRepositories() {
276309
return Collections.unmodifiableList(repositories);
277-
};
310+
}
278311

279312
/**
280313
* Late bind.
@@ -286,17 +319,64 @@ void lateBind() {
286319
"Expected installation payload, but got something else. Maybe we've got another type of event?");
287320
}
288321
super.lateBind();
289-
if (repositories != null && !repositories.isEmpty()) {
290-
try {
291-
for (GHRepository singleRepo : repositories) {
292-
// populate each repository
293-
// the repository information provided here is so limited
294-
// as to be unusable without populating, so we do it eagerly
295-
singleRepo.populate();
296-
}
297-
} catch (IOException e) {
298-
throw new GHException("Failed to refresh repositories", e);
299-
}
322+
}
323+
324+
/**
325+
* A special minimal implementation of a {@link GHRepository} which contains only fields from "Properties of
326+
* repositories" from <a href=
327+
* "https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#installation">here</a>
328+
*/
329+
public static class Repository {
330+
private long id;
331+
private String fullName;
332+
private String name;
333+
private String nodeId;
334+
@JsonProperty(value = "private")
335+
private boolean isPrivate;
336+
337+
/**
338+
* Get the id.
339+
*
340+
* @return the id
341+
*/
342+
public long getId() {
343+
return id;
344+
}
345+
346+
/**
347+
* Gets the full name.
348+
*
349+
* @return the full name
350+
*/
351+
public String getFullName() {
352+
return fullName;
353+
}
354+
355+
/**
356+
* Gets the name.
357+
*
358+
* @return the name
359+
*/
360+
public String getName() {
361+
return name;
362+
}
363+
364+
/**
365+
* Gets the node id.
366+
*
367+
* @return the node id
368+
*/
369+
public String getNodeId() {
370+
return nodeId;
371+
}
372+
373+
/**
374+
* Gets the repository private flag.
375+
*
376+
* @return whether the repository is private
377+
*/
378+
public boolean isPrivate() {
379+
return isPrivate;
300380
}
301381
}
302382
}

src/test/java/org/kohsuke/github/GHEventPayloadTest.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -990,8 +990,33 @@ public void InstallationRepositoriesEvent() throws Exception {
990990
* the exception
991991
*/
992992
@Test
993-
@Payload("installation")
994-
public void InstallationEvent() throws Exception {
993+
@Payload("installation_created")
994+
public void InstallationCreatedEvent() throws Exception {
995+
final GHEventPayload.Installation event = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl())
996+
.build()
997+
.parseEventPayload(payload.asReader(), GHEventPayload.Installation.class);
998+
999+
assertThat(event.getAction(), is("created"));
1000+
assertThat(event.getInstallation().getId(), is(43898337L));
1001+
assertThat(event.getInstallation().getAccount().getLogin(), is("CronFire"));
1002+
1003+
assertThat(event.getRepositories().isEmpty(), is(false));
1004+
assertThat(event.getRepositories().get(0).getId(), is(1296269L));
1005+
assertThat(event.getRawRepositories().isEmpty(), is(false));
1006+
assertThat(event.getRawRepositories().get(0).getId(), is(1296269L));
1007+
1008+
assertThat(event.getSender().getLogin(), is("Haarolean"));
1009+
}
1010+
1011+
/**
1012+
* Installation event.
1013+
*
1014+
* @throws Exception
1015+
* the exception
1016+
*/
1017+
@Test
1018+
@Payload("installation_deleted")
1019+
public void InstallationDeletedEvent() throws Exception {
9951020
final GHEventPayload.Installation event = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl())
9961021
.build()
9971022
.parseEventPayload(payload.asReader(), GHEventPayload.Installation.class);
@@ -1000,12 +1025,13 @@ public void InstallationEvent() throws Exception {
10001025
assertThat(event.getInstallation().getId(), is(2L));
10011026
assertThat(event.getInstallation().getAccount().getLogin(), is("octocat"));
10021027

1003-
assertThat(event.getRepositories().get(0).getId(), is(1296269L));
1004-
assertThat(event.getRepositories().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"));
1005-
assertThat(event.getRepositories().get(0).getName(), is("Hello-World"));
1006-
assertThat(event.getRepositories().get(0).getFullName(), is("octocat/Hello-World"));
1007-
assertThat(event.getRepositories().get(0).isPrivate(), is(false));
1008-
assertThat(event.getRepositories().get(0).getOwner().getLogin(), is("octocat"));
1028+
assertThrows(IllegalStateException.class, () -> event.getRepositories().isEmpty());
1029+
assertThat(event.getRawRepositories().isEmpty(), is(false));
1030+
assertThat(event.getRawRepositories().get(0).getId(), is(1296269L));
1031+
assertThat(event.getRawRepositories().get(0).getNodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"));
1032+
assertThat(event.getRawRepositories().get(0).getName(), is("Hello-World"));
1033+
assertThat(event.getRawRepositories().get(0).getFullName(), is("octocat/Hello-World"));
1034+
assertThat(event.getRawRepositories().get(0).isPrivate(), is(false));
10091035

10101036
assertThat(event.getSender().getLogin(), is("octocat"));
10111037
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"action": "created",
3+
"installation": {
4+
"id": 43898337,
5+
"account": {
6+
"login": "CronFire",
7+
"id": 68755481,
8+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY4NzU1NDgx",
9+
"avatar_url": "https://avatars.githubusercontent.com/u/68755481?v=4",
10+
"gravatar_id": "",
11+
"url": "https://api.github.com/users/CronFire",
12+
"html_url": "https://github.com/CronFire",
13+
"followers_url": "https://api.github.com/users/CronFire/followers",
14+
"following_url": "https://api.github.com/users/CronFire/following{/other_user}",
15+
"gists_url": "https://api.github.com/users/CronFire/gists{/gist_id}",
16+
"starred_url": "https://api.github.com/users/CronFire/starred{/owner}{/repo}",
17+
"subscriptions_url": "https://api.github.com/users/CronFire/subscriptions",
18+
"organizations_url": "https://api.github.com/users/CronFire/orgs",
19+
"repos_url": "https://api.github.com/users/CronFire/repos",
20+
"events_url": "https://api.github.com/users/CronFire/events{/privacy}",
21+
"received_events_url": "https://api.github.com/users/CronFire/received_events",
22+
"type": "Organization",
23+
"site_admin": false
24+
},
25+
"repository_selection": "selected",
26+
"access_tokens_url": "https://api.github.com/app/installations/43898337/access_tokens",
27+
"repositories_url": "https://api.github.com/installation/repositories",
28+
"html_url": "https://github.com/organizations/CronFire/settings/installations/43898337",
29+
"app_id": 421464,
30+
"app_slug": "kapybro-dev",
31+
"target_id": 68755481,
32+
"target_type": "Organization",
33+
"permissions": {
34+
"checks": "write",
35+
"issues": "write",
36+
"actions": "read",
37+
"members": "read",
38+
"contents": "write",
39+
"metadata": "read",
40+
"statuses": "write",
41+
"single_file": "read",
42+
"pull_requests": "write",
43+
"administration": "read"
44+
},
45+
"events": [
46+
"issues",
47+
"issue_comment",
48+
"organization",
49+
"public",
50+
"pull_request",
51+
"pull_request_review",
52+
"pull_request_review_comment",
53+
"push",
54+
"repository",
55+
"status"
56+
],
57+
"created_at": "2023-11-11T10:55:06.000+08:00",
58+
"updated_at": "2023-11-11T10:55:06.000+08:00",
59+
"single_file_name": ".github/kapybro/config.yml",
60+
"has_multiple_single_files": true,
61+
"single_file_paths": [
62+
".github/kapybro/config.yml",
63+
".github/kapybro/rules.yml"
64+
],
65+
"suspended_by": null,
66+
"suspended_at": null
67+
},
68+
"repositories": [
69+
{
70+
"id": 1296269,
71+
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
72+
"name": "Hello-World",
73+
"full_name": "octocat/Hello-World",
74+
"private": false
75+
}
76+
],
77+
"requester": null,
78+
"sender": {
79+
"login": "Haarolean",
80+
"id": 1494347,
81+
"node_id": "MDQ6VXNlcjE0OTQzNDc=",
82+
"avatar_url": "https://avatars.githubusercontent.com/u/1494347?v=4",
83+
"gravatar_id": "",
84+
"url": "https://api.github.com/users/Haarolean",
85+
"html_url": "https://github.com/Haarolean",
86+
"followers_url": "https://api.github.com/users/Haarolean/followers",
87+
"following_url": "https://api.github.com/users/Haarolean/following{/other_user}",
88+
"gists_url": "https://api.github.com/users/Haarolean/gists{/gist_id}",
89+
"starred_url": "https://api.github.com/users/Haarolean/starred{/owner}{/repo}",
90+
"subscriptions_url": "https://api.github.com/users/Haarolean/subscriptions",
91+
"organizations_url": "https://api.github.com/users/Haarolean/orgs",
92+
"repos_url": "https://api.github.com/users/Haarolean/repos",
93+
"events_url": "https://api.github.com/users/Haarolean/events{/privacy}",
94+
"received_events_url": "https://api.github.com/users/Haarolean/received_events",
95+
"type": "User",
96+
"site_admin": false
97+
}
98+
}

0 commit comments

Comments
 (0)