Skip to content

Commit e604ebb

Browse files
authored
Skip DFIU PRs for repos onboarded to Renovate Enterprise (#1128)
* Skip DFIU PRs for renovate onboarded repos
1 parent a21d7b6 commit e604ebb

File tree

6 files changed

+177
-7
lines changed

6 files changed

+177
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*.ipr
44
*.iws
55
*.iml
6+
*.DS_Store
67

78
# Vim files
89
*.swp

dockerfile-image-update/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@
6969

7070

7171
<dependencies>
72+
<dependency>
73+
<groupId>org.json</groupId>
74+
<artifactId>json</artifactId>
75+
<version>20240205</version>
76+
</dependency>
7277
<dependency>
7378
<groupId>org.slf4j</groupId>
7479
<artifactId>slf4j-simple</artifactId>

dockerfile-image-update/src/main/java/com/salesforce/dockerfileimageupdate/CommandLine.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ static ArgumentParser getArgumentParser() {
6565
ArgumentParsers.newFor("dockerfile-image-update").addHelp(true).build()
6666
.description("Image Updates through Pull Request Automator");
6767

68+
parser.addArgument("-R", "--" + CHECK_FOR_RENOVATE)
69+
.type(Boolean.class)
70+
.setDefault(false) //To prevent null from being returned by the argument
71+
.help("Check if Renovate app is being used to receive remediation PRs.");
6872
parser.addArgument("-l", "--" + GIT_API_SEARCH_LIMIT)
6973
.type(Integer.class)
7074
.setDefault(1000)

dockerfile-image-update/src/main/java/com/salesforce/dockerfileimageupdate/utils/Constants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111

1212
import java.time.Duration;
13+
import java.util.*;
1314

1415
/**
1516
* @author minho-park
@@ -51,4 +52,7 @@ private Constants() {
5152
public static final Duration DEFAULT_TOKEN_ADDING_RATE = Duration.ofMinutes(DEFAULT_CONSUMING_TOKEN_RATE);
5253
public static final String FILENAME_DOCKERFILE = "dockerfile";
5354
public static final String FILENAME_DOCKER_COMPOSE = "docker-compose";
55+
public static final String CHECK_FOR_RENOVATE = "checkforrenovate";
56+
//The Renovate configuration file can be in any one of the following locations. Refer to https://docs.renovatebot.com/configuration-options/
57+
public static final List<String> RENOVATE_CONFIG_FILEPATHS = Arrays.asList("renovate.json", "renovate.json5", ".github/renovate.json", ".github/renovate.json5", ".gitlab/renovate.json", ".gitlab/renovate.json5", ".renovaterc", ".renovaterc.json", ".renovaterc.json5");
5458
}

dockerfile-image-update/src/main/java/com/salesforce/dockerfileimageupdate/utils/PullRequests.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.salesforce.dockerfileimageupdate.model.*;
55
import com.salesforce.dockerfileimageupdate.process.*;
66
import net.sourceforge.argparse4j.inf.*;
7+
import org.json.JSONObject;
8+
import org.json.JSONTokener;
79
import org.kohsuke.github.*;
810
import org.slf4j.*;
911

@@ -27,10 +29,16 @@ public void prepareToCreate(final Namespace ns,
2729
pathToDockerfilesInParentRepo.get(currUserRepo).stream().findFirst();
2830
if (forkWithContentPaths.isPresent()) {
2931
try {
30-
dockerfileGitHubUtil.changeDockerfiles(ns,
31-
pathToDockerfilesInParentRepo,
32-
forkWithContentPaths.get(), skippedRepos,
33-
gitForkBranch, rateLimiter);
32+
//If the repository has been onboarded to renovate enterprise, skip sending the DFIU PR
33+
if(ns.getBoolean(Constants.CHECK_FOR_RENOVATE)
34+
&& (isRenovateEnabled(Constants.RENOVATE_CONFIG_FILEPATHS, forkWithContentPaths.get()))) {
35+
log.info("Found a renovate configuration file in the repo %s. Skip sending DFIU PRs to this repository.", forkWithContentPaths.get().getParent().getFullName());
36+
} else {
37+
dockerfileGitHubUtil.changeDockerfiles(ns,
38+
pathToDockerfilesInParentRepo,
39+
forkWithContentPaths.get(), skippedRepos,
40+
gitForkBranch, rateLimiter);
41+
}
3442
} catch (IOException | InterruptedException e) {
3543
log.error(String.format("Error changing Dockerfile for %s", forkWithContentPaths.get().getParent().getFullName()), e);
3644
exceptions.add((IOException) e);
@@ -41,5 +49,39 @@ public void prepareToCreate(final Namespace ns,
4149
}
4250
ResultsProcessor.processResults(skippedRepos, exceptions, log);
4351
}
52+
53+
/**
54+
* Check if the repository is onboarded to Renovate. The signal we are looking for are
55+
* (1) The presence of a file where renovate configurations are stored in the repository
56+
* (2) Ensuring that the file does not have the key "enabled" set to "false"
57+
* @param filePaths the list that contains all the names of the files that we are searching for in the repo
58+
* @param fork A GitHubContentToProcess object that contains the fork repository that is under process
59+
* @return true if the file is found in the path specified and is not disabled, false otherwise
60+
*/
61+
protected boolean isRenovateEnabled(List<String> filePaths, GitHubContentToProcess fork) throws IOException {
62+
for (String filePath : filePaths) {
63+
try {
64+
//If the file has the key 'enabled' set to false, it indicates that while the repo has been onboarded to renovate, it has been disabled for some reason
65+
return readJsonFromContent(fork.getParent().getFileContent(filePath)).optBoolean("enabled", true);
66+
} catch (FileNotFoundException e) {
67+
log.debug("The file with name %s not found in the repository. Exception: %s", filePath, e.getMessage());
68+
} catch (IOException e) {
69+
log.debug("Exception while trying to close a resource. Exception: %s", e.getMessage());
70+
}
71+
}
72+
return false;
73+
}
74+
75+
/**
76+
* Read the content of a fle from a repository and convert it into a JSON object
77+
* @param content The GHContent object for the content in the repository
78+
* @return json object for the content read from the repository
79+
*/
80+
private JSONObject readJsonFromContent(GHContent content) throws IOException {
81+
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(content.read()))) {
82+
JSONTokener tokener = new JSONTokener(bufferedReader);
83+
return new JSONObject(tokener);
84+
}
85+
}
4486
}
4587

dockerfile-image-update/src/test/java/com/salesforce/dockerfileimageupdate/utils/PullRequestsTest.java

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import net.sourceforge.argparse4j.inf.*;
77
import org.kohsuke.github.*;
88
import org.mockito.*;
9+
import org.testng.*;
910
import org.testng.annotations.*;
1011

1112
import java.io.*;
@@ -21,7 +22,7 @@ public void testPullRequestsPrepareToCreateSuccessful() throws Exception {
2122
"image", Constants.TAG,
2223
"tag", Constants.STORE,
2324
"store", Constants.SKIP_PR_CREATION,
24-
false);
25+
false, Constants.CHECK_FOR_RENOVATE, false);
2526
Namespace ns = new Namespace(nsMap);
2627
PullRequests pullRequests = new PullRequests();
2728
GitHubPullRequestSender pullRequestSender = mock(GitHubPullRequestSender.class);
@@ -51,7 +52,7 @@ public void testPullRequestsPrepareThrowsException() throws Exception {
5152
"image", Constants.TAG,
5253
"tag", Constants.STORE,
5354
"store", Constants.SKIP_PR_CREATION,
54-
false);
55+
false, Constants.CHECK_FOR_RENOVATE, false);
5556
Namespace ns = new Namespace(nsMap);
5657
PullRequests pullRequests = new PullRequests();
5758
GitHubPullRequestSender pullRequestSender = mock(GitHubPullRequestSender.class);
@@ -89,7 +90,7 @@ public void testPullRequestsPrepareToCreateWhenNoDockerfileFound() throws Except
8990
"image", Constants.TAG,
9091
"tag", Constants.STORE,
9192
"store", Constants.SKIP_PR_CREATION,
92-
false);
93+
false, Constants.CHECK_FOR_RENOVATE, false);
9394
Namespace ns = new Namespace(nsMap);
9495
PullRequests pullRequests = new PullRequests();
9596
GitHubPullRequestSender pullRequestSender = mock(GitHubPullRequestSender.class);
@@ -110,4 +111,117 @@ public void testPullRequestsPrepareToCreateWhenNoDockerfileFound() throws Except
110111
eq(pathToDockerfilesInParentRepo),
111112
eq(gitHubContentToProcess), anyList(), eq(gitForkBranch),eq(rateLimiter));
112113
}
114+
115+
@Test
116+
public void testPullRequestsPrepareSkipsSendingPRIfRepoOnboardedToRenovate() throws Exception {
117+
Map<String, Object> nsMap = ImmutableMap.of(
118+
Constants.IMG, "image",
119+
Constants.TAG, "tag",
120+
Constants.STORE,"store",
121+
Constants.SKIP_PR_CREATION,false,
122+
Constants.CHECK_FOR_RENOVATE, true);
123+
124+
125+
Namespace ns = new Namespace(nsMap);
126+
PullRequests pullRequests = new PullRequests();
127+
GitHubPullRequestSender pullRequestSender = mock(GitHubPullRequestSender.class);
128+
PagedSearchIterable<GHContent> contentsFoundWithImage = mock(PagedSearchIterable.class);
129+
GitForkBranch gitForkBranch = mock(GitForkBranch.class);
130+
DockerfileGitHubUtil dockerfileGitHubUtil = mock(DockerfileGitHubUtil.class);
131+
RateLimiter rateLimiter = Mockito.spy(new RateLimiter());
132+
Multimap<String, GitHubContentToProcess> pathToDockerfilesInParentRepo = ArrayListMultimap.create();
133+
GitHubContentToProcess gitHubContentToProcess = mock(GitHubContentToProcess.class);
134+
pathToDockerfilesInParentRepo.put("repo1", gitHubContentToProcess);
135+
pathToDockerfilesInParentRepo.put("repo2", gitHubContentToProcess);
136+
pathToDockerfilesInParentRepo.put("repo3", gitHubContentToProcess);
137+
GHContent content = mock(GHContent.class);
138+
InputStream inputStream1 = new ByteArrayInputStream("{someKey:someValue}".getBytes());
139+
InputStream inputStream2 = new ByteArrayInputStream("{enabled:false}".getBytes());
140+
GHRepository ghRepository = mock(GHRepository.class);
141+
142+
when(pullRequestSender.forkRepositoriesFoundAndGetPathToDockerfiles(contentsFoundWithImage, gitForkBranch)).thenReturn(pathToDockerfilesInParentRepo);
143+
when(gitHubContentToProcess.getParent()).thenReturn(ghRepository);
144+
//Fetch the content of the renovate.json file for the 3 repos.
145+
// The first one returns a file with regular json content.
146+
// The second one returns a file with the key 'enabled' set to 'false' to replicate a repo that has been onboarded to renovate but has it disabled
147+
// The third repo does not have the renovate.json file
148+
when(ghRepository.getFileContent(anyString())).thenReturn(content).thenReturn(content).thenThrow(new FileNotFoundException());
149+
when(ghRepository.getFullName()).thenReturn("org/repo");
150+
when(content.read()).thenReturn(inputStream1).thenReturn(inputStream2);
151+
152+
pullRequests.prepareToCreate(ns, pullRequestSender, contentsFoundWithImage,
153+
gitForkBranch, dockerfileGitHubUtil, rateLimiter);
154+
155+
//Verify that the DFIU PR is skipped for the first repo, but is sent to the other two repos
156+
verify(dockerfileGitHubUtil, times(2)).changeDockerfiles(eq(ns),
157+
eq(pathToDockerfilesInParentRepo),
158+
eq(gitHubContentToProcess), anyList(), eq(gitForkBranch),
159+
eq(rateLimiter));
160+
}
161+
162+
@Test
163+
public void testisRenovateEnabledReturnsFalseIfRenovateConfigFileNotFound() throws IOException {
164+
PullRequests pullRequests = new PullRequests();
165+
List<String> filePaths = Collections.singletonList("renovate.json");
166+
GitHubContentToProcess gitHubContentToProcess = mock(GitHubContentToProcess.class);
167+
GHRepository ghRepository = mock(GHRepository.class);
168+
when(gitHubContentToProcess.getParent()).thenReturn(ghRepository);
169+
when(ghRepository.getFileContent(anyString())).thenThrow(new FileNotFoundException());
170+
Assert.assertFalse(pullRequests.isRenovateEnabled(filePaths, gitHubContentToProcess));
171+
}
172+
173+
@Test
174+
public void testisRenovateEnabledReturnsFalseIfRenovateConfigFileFoundButIsDisabled() throws IOException {
175+
PullRequests pullRequests = new PullRequests();
176+
List<String> filePaths = Collections.singletonList("renovate.json");
177+
GitHubContentToProcess gitHubContentToProcess = mock(GitHubContentToProcess.class);
178+
GHRepository ghRepository = mock(GHRepository.class);
179+
GHContent content = mock(GHContent.class);
180+
InputStream inputStream = new ByteArrayInputStream("{enabled:false}".getBytes());
181+
when(gitHubContentToProcess.getParent()).thenReturn(ghRepository);
182+
when(ghRepository.getFileContent(anyString())).thenReturn(content);
183+
when(content.read()).thenReturn(inputStream);
184+
Assert.assertFalse(pullRequests.isRenovateEnabled(filePaths, gitHubContentToProcess));
185+
}
186+
187+
@Test
188+
public void testisRenovateEnabledReturnsTrueIfRenovateConfigFileFoundButEnabledKeyNotFound() throws IOException {
189+
PullRequests pullRequests = new PullRequests();
190+
List<String> filePaths = Collections.singletonList("renovate.json");
191+
GitHubContentToProcess gitHubContentToProcess = mock(GitHubContentToProcess.class);
192+
GHRepository ghRepository = mock(GHRepository.class);
193+
GHContent content = mock(GHContent.class);
194+
InputStream inputStream = new ByteArrayInputStream("{someKey:someValue}".getBytes());
195+
when(gitHubContentToProcess.getParent()).thenReturn(ghRepository);
196+
when(ghRepository.getFileContent(anyString())).thenReturn(content);
197+
when(content.read()).thenReturn(inputStream);
198+
Assert.assertTrue(pullRequests.isRenovateEnabled(filePaths, gitHubContentToProcess));
199+
}
200+
201+
@Test
202+
public void testisRenovateEnabledReturnsTrueIfRenovateConfigFileFoundAndResourcesThrowAnException() throws IOException {
203+
PullRequests pullRequests = new PullRequests();
204+
List<String> filePaths = Collections.singletonList("renovate.json");
205+
GitHubContentToProcess gitHubContentToProcess = mock(GitHubContentToProcess.class);
206+
GHRepository ghRepository = mock(GHRepository.class);
207+
GHContent content = mock(GHContent.class);
208+
when(gitHubContentToProcess.getParent()).thenReturn(ghRepository);
209+
when(ghRepository.getFileContent(anyString())).thenReturn(content);
210+
when(content.read()).thenThrow(new IOException());
211+
Assert.assertFalse(pullRequests.isRenovateEnabled(filePaths, gitHubContentToProcess));
212+
}
213+
214+
@Test
215+
public void testisRenovateEnabledReturnsTrueIfRenovateConfigFileFoundAndEnabledKeySetToTrue() throws IOException {
216+
PullRequests pullRequests = new PullRequests();
217+
List<String> filePaths = Collections.singletonList("renovate.json");
218+
GitHubContentToProcess gitHubContentToProcess = mock(GitHubContentToProcess.class);
219+
GHRepository ghRepository = mock(GHRepository.class);
220+
GHContent content = mock(GHContent.class);
221+
InputStream inputStream = new ByteArrayInputStream("{enabled:true}".getBytes());
222+
when(gitHubContentToProcess.getParent()).thenReturn(ghRepository);
223+
when(ghRepository.getFileContent(anyString())).thenReturn(content);
224+
when(content.read()).thenReturn(inputStream);
225+
Assert.assertTrue(pullRequests.isRenovateEnabled(filePaths, gitHubContentToProcess));
226+
}
113227
}

0 commit comments

Comments
 (0)