View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.changes.github;
20  
21  import java.io.IOException;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.List;
27  
28  import org.apache.maven.plugin.logging.Log;
29  import org.apache.maven.plugins.changes.issues.Issue;
30  import org.apache.maven.project.MavenProject;
31  import org.apache.maven.settings.Server;
32  import org.apache.maven.settings.Settings;
33  import org.apache.maven.settings.building.SettingsProblem;
34  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
35  import org.apache.maven.settings.crypto.SettingsDecrypter;
36  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
37  import org.kohsuke.github.GHIssue;
38  import org.kohsuke.github.GHIssueState;
39  import org.kohsuke.github.GHLabel;
40  import org.kohsuke.github.GitHubBuilder;
41  
42  /**
43   * @since 2.8
44   */
45  public class GitHubDownloader {
46  
47      /**
48       * The github client.
49       */
50      private GitHubBuilder client;
51  
52      /**
53       * A boolean to indicate if we should include open issues as well
54       */
55      private boolean includeOpenIssues;
56  
57      /**
58       * A boolean to indicate if we should only include issues with milestones
59       */
60      private boolean onlyMilestoneIssues;
61  
62      /**
63       * The owner/organization of the github repo.
64       */
65      private String githubOwner;
66  
67      /**
68       * The name of the github repo.
69       */
70      private String githubRepo;
71  
72      /**
73       * The url to the github repo's issue management
74       */
75      private String githubIssueURL;
76  
77      public GitHubDownloader(MavenProject project, boolean includeOpenIssues, boolean onlyMilestoneIssues)
78              throws IOException {
79          this.includeOpenIssues = includeOpenIssues;
80          this.onlyMilestoneIssues = onlyMilestoneIssues;
81  
82          URL githubURL = new URL(project.getIssueManagement().getUrl());
83  
84          // The githubclient prefers to connect to 'github.com' using the api domain, unlike github enterprise
85          // which can connect fine using its domain, so for github.com use empty constructor
86          if (githubURL.getHost().equalsIgnoreCase("github.com")) {
87              this.client = new GitHubBuilder();
88          } else {
89              this.client = new GitHubBuilder()
90                      .withEndpoint(githubURL.getProtocol() + "://" + githubURL.getHost()
91                              + (githubURL.getPort() == -1 ? "" : ":" + githubURL.getPort()));
92          }
93  
94          this.githubIssueURL = project.getIssueManagement().getUrl();
95          if (!this.githubIssueURL.endsWith("/")) {
96              this.githubIssueURL = this.githubIssueURL + "/";
97          }
98  
99          String urlPath = githubURL.getPath();
100         if (urlPath.startsWith("/")) {
101             urlPath = urlPath.substring(1);
102         }
103 
104         if (urlPath.endsWith("/")) {
105             urlPath = urlPath.substring(0, urlPath.length() - 2);
106         }
107 
108         String[] urlPathParts = urlPath.split("/");
109 
110         if (urlPathParts.length != 3) {
111             throw new MalformedURLException(
112                     "GitHub issue management URL must look like, " + "[GITHUB_DOMAIN]/[OWNER]/[REPO]/issues");
113         }
114 
115         this.githubOwner = urlPathParts[0];
116         this.githubRepo = urlPathParts[1];
117     }
118 
119     protected Issue createIssue(GHIssue githubIssue) throws IOException {
120         Issue issue = new Issue();
121 
122         issue.setKey(String.valueOf(githubIssue.getNumber()));
123         issue.setId(String.valueOf(githubIssue.getNumber()));
124 
125         issue.setLink(this.githubIssueURL + githubIssue.getNumber());
126 
127         issue.setCreated(githubIssue.getCreatedAt());
128 
129         issue.setUpdated(githubIssue.getUpdatedAt());
130 
131         if (githubIssue.getAssignee() != null) {
132             if (githubIssue.getAssignee().getName() != null) {
133                 issue.setAssignee(githubIssue.getAssignee().getName());
134             } else {
135                 issue.setAssignee(githubIssue.getAssignee().getLogin());
136             }
137         }
138 
139         issue.setSummary(githubIssue.getTitle());
140 
141         if (githubIssue.getMilestone() != null) {
142             issue.addFixVersion(githubIssue.getMilestone().getTitle());
143         }
144 
145         issue.setReporter(githubIssue.getUser().getLogin());
146 
147         issue.setStatus(githubIssue.getState().name());
148 
149         final Collection<GHLabel> labels = githubIssue.getLabels();
150         if (labels != null && !labels.isEmpty()) {
151             issue.setType(labels.stream().findAny().get().getName());
152         }
153 
154         return issue;
155     }
156 
157     public List<Issue> getIssueList() throws IOException {
158         List<Issue> issueList = new ArrayList<>();
159 
160         if (includeOpenIssues) {
161             final List<GHIssue> openIssues =
162                     client.build().getRepository(githubOwner + "/" + githubRepo).getIssues(GHIssueState.OPEN);
163             for (final GHIssue issue : openIssues) {
164                 if (!onlyMilestoneIssues || issue.getMilestone() != null) {
165                     issueList.add(createIssue(issue));
166                 }
167             }
168         }
169 
170         final List<GHIssue> closedIssues =
171                 client.build().getRepository(githubOwner + "/" + githubRepo).getIssues(GHIssueState.CLOSED);
172 
173         for (final GHIssue issue : closedIssues) {
174             if (!onlyMilestoneIssues || issue.getMilestone() != null) {
175                 issueList.add(createIssue(issue));
176             }
177         }
178 
179         return issueList;
180     }
181 
182     public void configureAuthentication(
183             SettingsDecrypter decrypter, String githubAPIServerId, Settings settings, Log log) {
184         boolean configured = false;
185 
186         List<Server> servers = settings.getServers();
187 
188         for (Server server : servers) {
189             if (server.getId().equals(githubAPIServerId)) {
190                 SettingsDecryptionResult result = decrypter.decrypt(new DefaultSettingsDecryptionRequest(server));
191                 for (SettingsProblem problem : result.getProblems()) {
192                     log.error(problem.getMessage(), problem.getException());
193                 }
194                 server = result.getServer();
195                 String password = server.getPassword();
196                 client.withJwtToken(password);
197 
198                 configured = true;
199                 break;
200             }
201         }
202 
203         if (!configured) {
204             log.warn("Can't find server id [" + githubAPIServerId + "] configured in settings.xml");
205         }
206     }
207 }