View Javadoc

1   /**
2    * Copyright (C) 2012 White Source Ltd.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.whitesource.teamcity.agent;
17  
18  import com.intellij.openapi.diagnostic.Logger;
19  import jetbrains.buildServer.ExtensionHolder;
20  import jetbrains.buildServer.agent.*;
21  import jetbrains.buildServer.log.Loggers;
22  import jetbrains.buildServer.util.EventDispatcher;
23  import jetbrains.buildServer.util.StringUtil;
24  import org.apache.commons.lang.StringUtils;
25  import org.jetbrains.annotations.NotNull;
26  import org.springframework.util.CollectionUtils;
27  import org.whitesource.agent.api.dispatch.CheckPoliciesResult;
28  import org.whitesource.agent.api.dispatch.UpdateInventoryResult;
29  import org.whitesource.agent.api.model.AgentProjectInfo;
30  import org.whitesource.agent.api.model.DependencyInfo;
31  import org.whitesource.agent.client.WhitesourceService;
32  import org.whitesource.agent.client.WssServiceException;
33  import org.whitesource.agent.report.PolicyCheckReport;
34  import org.whitesource.teamcity.common.Constants;
35  import org.whitesource.teamcity.common.WssUtils;
36  
37  import java.io.File;
38  import java.io.IOException;
39  import java.util.Collection;
40  import java.util.HashMap;
41  import java.util.Map;
42  
43  /**
44   * @author Edo.Shor
45   */
46  public class WhitesourceLifeCycleListener extends AgentLifeCycleAdapter {
47  
48      /* --- Static members --- */
49  
50      private static final String LOG_COMPONENT = "LifeCycleListener";
51  
52      private ExtensionHolder extensionHolder;
53  
54      /* --- Constructors --- */
55  
56      /**
57       * Constructor
58       *
59       * @param eventDispatcher
60       * @param extensionHolder
61       */
62      public WhitesourceLifeCycleListener(@NotNull final EventDispatcher<AgentLifeCycleListener> eventDispatcher,
63                                          @NotNull final ExtensionHolder extensionHolder) {
64          this.extensionHolder = extensionHolder;
65          eventDispatcher.addListener(this);
66      }
67  
68      @Override
69      public void agentInitialized(@NotNull BuildAgent agent) {
70          super.agentInitialized(agent);
71          Loggers.AGENT.info(WssUtils.logMsg(LOG_COMPONENT, "initialized"));
72      }
73  
74      /* --- Interface implementation methods --- */
75  
76      @Override
77      public void beforeRunnerStart(@NotNull BuildRunnerContext runner) {
78          super.beforeRunnerStart(runner);
79  
80          if (shouldUpdate(runner)) {
81              Loggers.AGENT.info(WssUtils.logMsg(LOG_COMPONENT, "before runner start "
82                      + runner.getBuild().getProjectName() + " type " + runner.getName()));
83          }
84      }
85  
86      @Override
87      public void runnerFinished(@NotNull BuildRunnerContext runner, @NotNull BuildFinishedStatus status) {
88          super.runnerFinished(runner, status);
89  
90          AgentRunningBuild build = runner.getBuild();
91  
92          Loggers.AGENT.info(WssUtils.logMsg(LOG_COMPONENT, "runner finished "
93                  + build.getProjectName() + " type " + runner.getName()));
94  
95          if (!shouldUpdate(runner)) {
96              return; // no need to update white source...
97          }
98  
99          final BuildProgressLogger buildLogger = build.getBuildLogger();
100         buildLogger.message("Updating White Source");
101 
102         // make sure we have an organization token
103         Map<String, String> runnerParameters = runner.getRunnerParameters();
104         String orgToken = runnerParameters.get(Constants.RUNNER_OVERRIDE_ORGANIZATION_TOKEN);
105         if (StringUtil.isEmptyOrSpaces(orgToken)) {
106             orgToken = runnerParameters.get(Constants.RUNNER_ORGANIZATION_TOKEN);
107         }
108         if (StringUtil.isEmptyOrSpaces(orgToken)) {
109             stopBuildOnError((AgentRunningBuildEx) build,
110                     new IllegalStateException("Empty organization token. " +
111                             "Please make sure an organization token is defined for this runner"));
112             return;
113         }
114 
115         // should we check policies first ?
116         boolean shouldCheckPolicies = false;
117         String overrideCheckPolicies = runnerParameters.get(Constants.RUNNER_OVERRIDE_CHECK_POLICIES);
118         if (StringUtils.isBlank(overrideCheckPolicies) ||
119                 "global".equals(overrideCheckPolicies)) {
120             shouldCheckPolicies = Boolean.parseBoolean(runnerParameters.get(Constants.RUNNER_CHECK_POLICIES));
121         } else {
122             shouldCheckPolicies = "enabled".equals(overrideCheckPolicies);
123         }
124 
125         // collect OSS usage information
126         buildLogger.message("Collecting OSS usage information");
127         BaseOssInfoExtractor extractor = null;
128         if (WssUtils.isMavenRunType(runner.getRunType())) {
129             extractor = new MavenOssInfoExtractor(runner);
130         } else {
131             extractor = new GenericOssInfoExtractor(runner);
132         }
133         Collection<AgentProjectInfo> projectInfos = extractor.extract();
134         debugAgentProjectInfos(projectInfos);
135 
136         // send to white source
137         if (CollectionUtils.isEmpty(projectInfos)) {
138             buildLogger.message("No open source information found.");
139         } else {
140             WhitesourceService service = createServiceClient(runner);
141             try{
142                 if (shouldCheckPolicies) {
143                     buildLogger.message("Checking policies");
144                     CheckPoliciesResult result = service.checkPolicies(orgToken, projectInfos);
145                     policyCheckReport(runner, result);
146                     if (result.hasRejections()) {
147                         stopBuild((AgentRunningBuildEx) build, "Open source rejected by organization policies.");
148                     } else {
149                         buildLogger.message("All dependencies conform with open source policies.");
150                         sendUpdate(orgToken, projectInfos, service, buildLogger);
151                     }
152                 } else {
153                     sendUpdate(orgToken, projectInfos, service, buildLogger);
154                 }
155             } catch (WssServiceException e) {
156                 stopBuildOnError((AgentRunningBuildEx) build, e);
157             } catch (IOException e) {
158                 stopBuildOnError((AgentRunningBuildEx) build, e);
159             } catch (RuntimeException e) {
160                 Loggers.AGENT.error(WssUtils.logMsg(LOG_COMPONENT, "Runtime Error"), e);
161                 stopBuildOnError((AgentRunningBuildEx) build, e);
162             } finally {
163                 service.shutdown();
164             }
165         }
166     }
167 
168     private void policyCheckReport(BuildRunnerContext runner, CheckPoliciesResult result) throws IOException {
169         AgentRunningBuild build = runner.getBuild();
170 
171        PolicyCheckReport report = new PolicyCheckReport(result, build.getProjectName(), build.getBuildNumber());
172        File reportArchive = report.generate(build.getBuildTempDirectory(), true);
173 
174        ArtifactsPublisher publisher = extensionHolder.getExtensions(ArtifactsPublisher.class).iterator().next();
175        Map<File, String> artifactsToPublish = new HashMap<File, String>();
176        artifactsToPublish.put(reportArchive, "");
177        publisher.publishFiles(artifactsToPublish);
178     }
179 
180     /* --- Private methods --- */
181 
182     private boolean shouldUpdate(BuildRunnerContext runner) {
183         String shouldUpdate = runner.getRunnerParameters().get(Constants.RUNNER_DO_UPDATE);
184         return !StringUtil.isEmptyOrSpaces(shouldUpdate) && Boolean.parseBoolean(shouldUpdate);
185     }
186 
187     private WhitesourceService createServiceClient(BuildRunnerContext runner) {
188         String serviceUrl = runner.getRunnerParameters().get(Constants.RUNNER_SERVICE_URL);
189         WhitesourceService service = new WhitesourceService(Constants.AGENT_TYPE, Constants.AGENT_VERSION, serviceUrl);
190 
191         String proxyHost = runner.getRunnerParameters().get(Constants.RUNNER_PROXY_HOST);
192         if (!StringUtil.isEmptyOrSpaces(proxyHost)) {
193             int port = Integer.parseInt(runner.getRunnerParameters().get(Constants.RUNNER_PROXY_PORT));
194             String username = runner.getRunnerParameters().get(Constants.RUNNER_PROXY_USERNAME);
195             String password = runner.getRunnerParameters().get(Constants.RUNNER_PROXY_PASSWORD);
196             service.getClient().setProxy(proxyHost, port, username, password);
197         }
198 
199         return service;
200     }
201 
202     private void sendUpdate(String orgToken, Collection<AgentProjectInfo> projectInfos,
203                             WhitesourceService service, BuildProgressLogger buildLogger)
204             throws WssServiceException {
205 
206         buildLogger.message("Sending to White Source");
207         UpdateInventoryResult updateResult = service.update(orgToken, projectInfos);
208         logUpdateResult(updateResult, buildLogger);
209     }
210 
211     private void logUpdateResult(UpdateInventoryResult result, BuildProgressLogger logger) {
212         Loggers.AGENT.info(WssUtils.logMsg(LOG_COMPONENT, "update success"));
213 
214         logger.message("White Source update results: ");
215         logger.message("White Source organization: " + result.getOrganization());
216         logger.message(result.getCreatedProjects().size() + " Newly created projects:");
217         logger.message(StringUtil.join(result.getCreatedProjects(), ","));
218         logger.message(result.getUpdatedProjects().size() + " existing projects were updated:");
219         logger.message(StringUtil.join(result.getUpdatedProjects(), ","));
220     }
221 
222     private void stopBuildOnError(AgentRunningBuildEx build, Exception e) {
223         Loggers.AGENT.warn(WssUtils.logMsg(LOG_COMPONENT, "Stopping build"), e);
224 
225         BuildProgressLogger logger = build.getBuildLogger();
226         String errorMessage = e.getLocalizedMessage();
227         logger.buildFailureDescription(errorMessage);
228         logger.exception(e);
229         logger.flush();
230         build.stopBuild(errorMessage);
231     }
232 
233     private void stopBuild(AgentRunningBuildEx build, String message) {
234         Loggers.AGENT.warn(WssUtils.logMsg(LOG_COMPONENT, "Stopping build: + message"));
235 
236         BuildProgressLogger logger = build.getBuildLogger();
237         logger.buildFailureDescription(message);
238         logger.flush();
239         build.stopBuild(message);
240     }
241 
242     private void debugAgentProjectInfos(Collection<AgentProjectInfo> projectInfos) {
243         final Logger log = Loggers.AGENT;
244 
245         log.info("----------------- dumping projectInfos -----------------");
246         log.info("Total number of projects : " + projectInfos.size());
247         for (AgentProjectInfo projectInfo : projectInfos) {
248             log.info("Project coordiantes: " + projectInfo.getCoordinates());
249             log.info("Project parent coordiantes: " + projectInfo.getParentCoordinates());
250 
251             Collection<DependencyInfo> dependencies = projectInfo.getDependencies();
252             log.info("total # of dependencies: " + dependencies.size());
253             for (DependencyInfo info : dependencies) {
254                 log.info(info + " SHA-1: " + info.getSha1());
255             }
256         }
257         log.info("----------------- dump finished -----------------");
258 
259     }
260 }