/*
* Copyright (C) 2010 White Source Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whitesource.jenkins;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher;
import hudson.ProxyConfiguration;
import hudson.maven.MavenBuild;
import hudson.maven.MavenModule;
import hudson.maven.MavenModuleSetBuild;
import hudson.maven.reporters.MavenArtifactRecord;
import hudson.model.*;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.FormValidation;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.whitesource.agent.api.dispatch.UpdateInventoryResult;
import org.whitesource.agent.api.model.AgentProjectInfo;
import org.whitesource.agent.api.model.Coordinates;
import org.whitesource.agent.api.model.DependencyInfo;
import org.whitesource.api.client.ClientConstants;
import org.whitesource.api.client.WhitesourceService;
import org.whitesource.api.client.WssServiceException;
import org.whitesource.jenkins.maven.MavenDependenciesRecord;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map.Entry;
/**
* @author ramakrishna
* @author Edo.Shor
*/
public class WhiteSourcePublisher extends Recorder {
/* --- Members --- */
private final String orgToken;
private final String projectToken;
private final String libIncludes;
private final String libExcludes;
/* --- Constructors --- */
/**
* Constructor
*
* @param orgToken
* @param libIncludes
* @param libExcludes
*/
@DataBoundConstructor
public WhiteSourcePublisher(String orgToken, String projectToken, String libIncludes, String libExcludes) {
this.orgToken = orgToken;
this.projectToken = projectToken;
this.libIncludes = libIncludes;
this.libExcludes = libExcludes;
}
/* --- Interface implementation methods --- */
@SuppressWarnings("rawtypes")
@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
listener.getLogger().println("Starting White Source publisher");
if (build.getResult().isWorseThan(Result.SUCCESS)) {
listener.getLogger().println("Build failed. Skipping update.");
return true;
}
if (WssUtils.isFreeStyleMaven(build.getProject())) {
listener.getLogger().println("Free style maven jobs are not supported in this version. See plugin documentation.");
return true;
}
// prepare all OSS information to send
listener.getLogger().println("\nPreparing OSS information");
Collection<AgentProjectInfo> projectInfos = new ArrayList<AgentProjectInfo>();
if ((build instanceof MavenModuleSetBuild)) {
projectInfos = doMaven((MavenModuleSetBuild) build, listener);
} else if ((build instanceof FreeStyleBuild)) {
AgentProjectInfo projectInfo = doFreeStyle((FreeStyleBuild) build, listener);
projectInfo.setProjectToken(projectToken);
projectInfos.add(projectInfo);
} else {
listener.getLogger().println("Unrecognized build type " + build.getClass().getName());
}
// update White Source
listener.getLogger().println("\nSending OSS information to White Source service");
WhitesourceService service = createWhtiesourceService();
try {
UpdateInventoryResult result = service.update(orgToken, projectInfos);
logUpdateResult(result, listener);
listener.getLogger().println("Update successful");
return true;
} catch (WssServiceException e) {
listener.getLogger().println("Update fail: " + e.getMessage());
} finally {
service.shutdown();
}
// failed
build.setResult(Result.FAILURE);
return true;
}
/* --- Public methods --- */
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
/* --- Private methods --- */
private Collection<AgentProjectInfo> doMaven(MavenModuleSetBuild build, BuildListener listener) {
Collection<AgentProjectInfo> projectInfos = new ArrayList<AgentProjectInfo>();
for (Entry<MavenModule, MavenBuild> entry : build.getModuleLastBuilds().entrySet()) {
AgentProjectInfo projectInfo = new AgentProjectInfo();
MavenBuild moduleBuild = entry.getValue();
// module information
MavenArtifactRecord action = moduleBuild.getAction(MavenArtifactRecord.class);
if (action != null) {
listener.getLogger().println(action.pomArtifact.canonicalName);
projectInfo.setCoordinates(new Coordinates(action.pomArtifact.groupId,
action.pomArtifact.artifactId, action.pomArtifact.version));
}
// dependencies
Collection<DependencyInfo> dependencyInfos = projectInfo.getDependencies();
MavenDependenciesRecord dependenciesAction = moduleBuild.getAction(MavenDependenciesRecord.class);
if (dependenciesAction == null) {
listener.getLogger().println("No dependncies found !");
} else {
dependencyInfos.addAll(dependenciesAction.getDependencies());
listener.getLogger().println("Found " + dependenciesAction.getDependencies().size() + " dependencies (transitive included)");
}
projectInfos.add(projectInfo);
}
return projectInfos;
}
private AgentProjectInfo doFreeStyle(FreeStyleBuild freeStyleBuild, BuildListener listener)
throws IOException, InterruptedException {
AgentProjectInfo projectInfo = null;
if (StringUtils.isBlank(libIncludes)) {
listener.getLogger().println("No include pattern defined. Skipping update.");
} else {
LibFolderScanner libScanner = new LibFolderScanner(libIncludes, libExcludes, listener.getLogger());
Collection<DependencyInfo> folderDependencies = freeStyleBuild.getWorkspace().act(libScanner);
projectInfo = new AgentProjectInfo();
projectInfo.getDependencies().addAll(folderDependencies);
}
return projectInfo;
}
private WhitesourceService createWhtiesourceService() {
String serviceUrl = EnvVars.masterEnvVars.get(ClientConstants.SERVICE_URL_KEYWORD);
WhitesourceService service = new WhitesourceService(Constants.AGENT_TYPE, Constants.AGENT_VERSION, serviceUrl);
// setup proxy configuration
ProxyConfiguration proxy = Jenkins.getInstance() !=null ? Jenkins.getInstance().proxy : null;
if (proxy != null) {
if(proxy.getUserName() == null) {
service.getClient().setProxy(proxy.name, proxy.port, null, null);
} else {
service.getClient().setProxy(proxy.name, proxy.port, proxy.getUserName(), proxy.getPassword());
}
}
return service;
}
private void logUpdateResult(UpdateInventoryResult result, BuildListener listener) {
listener.getLogger().println("White Source update results: ");
listener.getLogger().println("White Source organization: " + result.getOrganization());
listener.getLogger().println(result.getCreatedProjects().size() + " Newly created projects:");
StringUtils.join(result.getCreatedProjects(), ",");
listener.getLogger().println(result.getUpdatedProjects().size() + " existing projects were updated:");
StringUtils.join(result.getUpdatedProjects(), ",");
}
/* --- Nested classes --- */
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
/* --- Members--- */
private String orgToken;
/* --- Constructors--- */
/**
* Default constructor
*/
public DescriptorImpl() {
super(WhiteSourcePublisher.class);
load();
}
/* --- Overridden methods --- */
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
@Override
public String getDisplayName() {
return "White Source Publisher";
}
@Override
public String getHelpFile() {
return "/plugin/whitesource/help/help.html";
}
@Override
public WhiteSourcePublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException {
String projectToken = formData.optString("projectToken");
String libIncludes = formData.optString("libIncludes");
String libExcludes = formData.optString("libExcludes");
JSONObject overridingTokenJSON = formData.getJSONObject("overridingOrgToken");
if (!overridingTokenJSON.isNullObject()) {
String overridingOrgToken = overridingTokenJSON.getString("overridingOrgToken");
orgToken = overridingOrgToken;
}
return new WhiteSourcePublisher(orgToken, projectToken, libIncludes, libExcludes);
}
@Override
public boolean configure(StaplerRequest req, JSONObject json)
throws FormException {
orgToken = json.getString("orgToken");
save();
return super.configure(req, json);
}
/* --- Public methods --- */
public FormValidation doCheckOrgToken(@QueryParameter String orgToken) {
return FormValidation.validateRequired(orgToken);
}
public FormValidation doCheckOverridingOrgToken(@QueryParameter String overridingOrgToken) {
return FormValidation.validateRequired(overridingOrgToken);
}
public FormValidation doCheckLibIncludes(@QueryParameter String libIncludes) {
return FormValidation.validateRequired(libIncludes);
}
public FormValidation doCheckProjectToken(@QueryParameter String projectToken) {
return FormValidation.validateRequired(projectToken);
}
/* --- Getters / Setters --- */
public String getOrgToken() {
return orgToken;
}
public void setOrgToken(String orgToken) {
this.orgToken = orgToken;
}
}
/* --- Getters --- */
public String getOrgToken() {
return orgToken;
}
public String getProjectToken() {
return projectToken;
}
public String getLibIncludes() {
return libIncludes;
}
public String getLibExcludes() {
return libExcludes;
}
}