/*
* The MIT License
*
* Copyright (c) 2010, Manufacture Française des Pneumatiques Michelin, Romain Seguy,
* Amadeus SAS, Vincent Latombe
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.michelin.cio.hudson.plugins.clearcaseucmbaseline;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.TaskListener;
import hudson.plugins.clearcase.PluginImpl;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jvnet.localizer.ResourceBundleHolder;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
/**
* Defines a new {@link ParameterDefinition} to be displayed at the top of the
* configuration page of {@link AbstractProject}s.
*
* <p>For this parameter to actually work, it is mandatory for the {@link AbstractProject}s
* using it to set their SCM to be {@link ClearCaseUcmBaselineSCM}. If not set,
* the build will be aborted (see {@link ClearCaseUcmBaselineParameterValue#createBuildWrapper}
* which does this check).</p>
*
* <p>When used, this parameter will request the user to select a ClearCase UCM
* baseline at run-time by displaying a drop-down list. See {@link ClearCaseUcmBaselineParameterValue}.
* </p>
*
* <p>This parameter consists in a set of attributes to be set at config-time:<ul>
* <li>The ClearCase UCM PVOB name;</li>
* <li>The ClearCase UCM component name;</li>
* <li>The ClearCase UCM promotion level (e.g. RELEASED);</li>
* <li>The ClearCase UCM stream;</li>
* <li>The ClearCase UCM view to create name.</li>
* </ul></p>
* <p>The following attribute is then asked at run-time:<ul>
* <li>The ClearCase UCM baseline.</li>
* </ul></p>
*
* @author Romain Seguy (http://davadoc.deviantart.com)
*/
public class ClearCaseUcmBaselineParameterDefinition extends ParameterDefinition implements Comparable<ClearCaseUcmBaselineParameterDefinition> {
public final static String PARAMETER_NAME = "ClearCase UCM baseline";
private final String component;
/**
* Allows excluding the "element * CHECKEDOUT" rule from the config spec (cf.
* HUDSON-6411)
*/
private final boolean excludeElementCheckedout;
private final boolean forceRmview;
private final String mkviewOptionalParam;
/**
* The promotion level is optional: If not is set, then the user will be
* offered with all the baselines of the ClearCase UCM component.
*/
private final String promotionLevel;
private final String pvob;
/**
* List of folders to be actually retrieved from CC. If no restriction is
* defined, then the whole data will be downloaded. If some restrictions are
* defined, then only the corresponding folders will be downloaded.
*/
private final String restrictions;
private final boolean snapshotView;
/**
* The stream is optional: If not set, the user will be proposed with all
* baselines from the Clearcase UCM component.
*/
private final String stream;
private final boolean useUpdate;
private final String viewName;
/**
* We use a UUID to uniquely identify each use of this parameter: We need this
* to find the project and the node using this parameter in the getBaselines()
* method (which is called before the build takes place).
*/
private final UUID uuid;
@DataBoundConstructor
public ClearCaseUcmBaselineParameterDefinition(String pvob, String component, String promotionLevel, String stream, String restrictions, String viewName, String mkviewOptionalParam, boolean snapshotView, boolean useUpdate, boolean forceRmview, boolean excludeElementCheckedout, String uuid) {
super(PARAMETER_NAME); // we keep the name of the parameter not
// internationalized, it will save many
// issues when updating system settings
// pvob should be prefixed with slave OS separator but at this stage, we
// can't determine it.
this.pvob = ClearCaseUcmBaselineUtils.prefixWithSeparator(pvob);
this.component = component;
this.promotionLevel = promotionLevel;
this.stream = stream;
this.restrictions = restrictions;
this.viewName = viewName;
this.mkviewOptionalParam = mkviewOptionalParam;
this.snapshotView = snapshotView;
this.useUpdate = useUpdate;
this.forceRmview = forceRmview;
this.excludeElementCheckedout = excludeElementCheckedout;
if(uuid == null || uuid.length() == 0) {
this.uuid = UUID.randomUUID();
}
else {
this.uuid = UUID.fromString(uuid);
}
}
// This method is invoked from a GET or POST HTTP request
@Override
public ParameterValue createValue(StaplerRequest req) {
String[] values = req.getParameterValues(getName());
if(values == null || values.length != 1) {
// the "ClearCase UCM baseline" parameter is mandatory, the build
// has to fail if it's not there (we can't assume a default value)
return null;
}
else {
return new ClearCaseUcmBaselineParameterValue(
getName(), getPvob(), getComponent(), getPromotionLevel(),
getStream(), getViewName(), getMkviewOptionalParam(),
values[0], getUseUpdate(), getForceRmview(), getSnapshotView(),
getExcludeElementCheckedout());
}
}
// This method is invoked when the user clicks on the "Build" button of Hudon's GUI
@Override
public ParameterValue createValue(StaplerRequest req, JSONObject formData) {
// bindJSON() uses the @DataBoundConstructor constructor
ClearCaseUcmBaselineParameterValue value = req.bindJSON(ClearCaseUcmBaselineParameterValue.class, formData);
value.setPvob(pvob);
value.setComponent(component);
value.setPromotionLevel(promotionLevel);
value.setRestrictions(getRestrictionsAsList());
value.setViewName(viewName);
value.setMkviewOptionalParam(mkviewOptionalParam);
// we don't set forceRmview: we use the value which is set by the user
// (so it is in formData) to allow overriding the setting ==> the value
// was set when invoking req.bindJSON()
//value.setForceRmview(forceRmview);
value.setSnapshotView(snapshotView);
value.setUseUpdate(useUpdate);
value.setExcludeElementCheckedout(excludeElementCheckedout);
return value;
}
/**
* Returns an array of ClearCase UCM baselines to be displayed in
* {@code ClearCaseUcmBaselineParameterDefinition/index.jelly} (or {@code null}
* if something wrong happens).
*/
public String[] getBaselines() throws IOException, InterruptedException {
// cleartool lsbl -fmt "%[name]p " -level <promotion level> -stream <stream>@<pvob> -component <component>@<vob>
ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(PluginImpl.getDescriptor().getCleartoolExe());
cmd.add("lsbl");
cmd.add("-fmt");
cmd.add("%[name]p ");
if(StringUtils.isNotEmpty(promotionLevel)) {
cmd.add("-level");
cmd.add(promotionLevel);
}
if(StringUtils.isNotEmpty(stream)) {
cmd.add("-stream");
cmd.add(stream + '@' + pvob);
}
cmd.add("-component");
cmd.add(component + '@' + pvob);
// we have to find the node the job is assigned to so that we run the
// cleartool command at the right place
Node nodeRunningThisJob = Hudson.getInstance();
for(Node node: Hudson.getInstance().getNodes()) {
Computer computer = node.toComputer();
if(computer != null) {
List<AbstractProject> jobs = computer.getTiedJobs();
if(jobs == null) {
continue;
}
boolean nodeRunningThisJobFound = false;
for(AbstractProject project : jobs) {
ParametersDefinitionProperty property = (ParametersDefinitionProperty) project.getProperty(ParametersDefinitionProperty.class);
if(property != null) {
ClearCaseUcmBaselineParameterDefinition pd = (ClearCaseUcmBaselineParameterDefinition) property.getParameterDefinition(PARAMETER_NAME);
if(pd != null) {
if(pd.compareTo(this) == 0) {
nodeRunningThisJob = node;
nodeRunningThisJobFound = true;
break;
}
}
}
}
if(nodeRunningThisJobFound) {
break;
}
}
}
// HUDSON-6057: we have to start the node if it's offline
Computer computerRunnningThisJob = nodeRunningThisJob.toComputer();
if(computerRunnningThisJob.isOffline()) {
if(computerRunnningThisJob.isLaunchSupported()) {
LOGGER.info(nodeRunningThisJob.getDisplayName() + " is offline. Trying to launch it...");
try {
computerRunnningThisJob.connect(false).get();
LOGGER.info("Waiting 10 seconds for " + nodeRunningThisJob.getDisplayName() + " to be launched...");
Thread.sleep(10000);
do {
Thread.sleep(1000);
} while(computerRunnningThisJob.isConnecting());
} catch(Exception e) {
LOGGER.log(Level.SEVERE, "An exception occurred while launching " + nodeRunningThisJob.getDisplayName(), e);
return null;
}
}
else {
LOGGER.severe(nodeRunningThisJob.getDisplayName() + " can't be automatically launched. You must start it before trying to run this job.");
return null;
}
}
if(computerRunnningThisJob.isOnline()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
nodeRunningThisJob.createLauncher(TaskListener.NULL).launch().cmds(cmd).stdout(baos).join();
String cleartoolOutput = ClearCaseUcmBaselineUtils.processCleartoolOuput(baos);
baos.close();
if(cleartoolOutput.toString().contains("cleartool: Error")) {
LOGGER.warning("An error occurred while gathering ClearCase UCM baselines: " + cleartoolOutput);
return null;
}
else {
return cleartoolOutput.toString().split(" ");
}
}
else {
LOGGER.log(
Level.SEVERE,
nodeRunningThisJob.getDisplayName() + " is offline and couldn't be launched.");
return null;
}
}
public String getComponent() {
return component;
}
public boolean getExcludeElementCheckedout() {
return excludeElementCheckedout;
}
public boolean getForceRmview() {
return forceRmview;
}
public String getMkviewOptionalParam() {
return mkviewOptionalParam;
}
public String getPromotionLevel() {
return promotionLevel;
}
public String getPvob() {
return pvob;
}
public String getRestrictions() {
return restrictions;
}
public List<String> getRestrictionsAsList() {
ArrayList<String> restrictionsAsList = new ArrayList<String>();
if(getRestrictions() != null && getRestrictions().length() > 0) {
for(String restriction: Util.tokenize(getRestrictions(), "\n\r\f")) {
restrictionsAsList.add(restriction);
}
}
return restrictionsAsList;
}
public boolean getSnapshotView() {
return snapshotView;
}
public String getStream() {
return stream;
}
public boolean getUseUpdate() {
return useUpdate;
}
public String getViewName() {
return viewName;
}
public int compareTo(ClearCaseUcmBaselineParameterDefinition pd) {
if(pd.uuid.equals(uuid)) {
return 0;
}
return -1;
}
@Extension
public static class DescriptorImpl extends ParameterDescriptor {
public FormValidation doCheckComponent(@QueryParameter String value) {
if(value == null || value.length() == 0) {
return FormValidation.error(ResourceBundleHolder.get(ClearCaseUcmBaselineParameterDefinition.class).format("ComponentMustBeSet"));
}
return FormValidation.ok();
}
public FormValidation doCheckPromotionLevel(@QueryParameter String value) {
if(value == null || value.length() == 0) {
return FormValidation.warning(ResourceBundleHolder.get(ClearCaseUcmBaselineParameterDefinition.class).format("PromotionLevelShouldBeSet"));
}
return FormValidation.ok();
}
public FormValidation doCheckPvob(@QueryParameter String value) {
if(value == null || value.length() == 0) {
return FormValidation.error(ResourceBundleHolder.get(ClearCaseUcmBaselineParameterDefinition.class).format("PVOBMustBeSet"));
}
return FormValidation.ok();
}
public FormValidation doCheckStream(@QueryParameter String value) {
if(StringUtils.isEmpty(value)) {
return FormValidation.warning(ResourceBundleHolder.get(ClearCaseUcmBaselineParameterDefinition.class).format("StreamShouldBeSet"));
}
return FormValidation.ok();
}
public FormValidation doCheckViewName(@QueryParameter String value) {
if(value == null || value.length() == 0) {
return FormValidation.error(ResourceBundleHolder.get(ClearCaseUcmBaselineParameterDefinition.class).format("ViewNameMustBeSet"));
}
return FormValidation.ok();
}
@Override
public String getDisplayName() {
return ResourceBundleHolder.get(ClearCaseUcmBaselineParameterDefinition.class).format("DisplayName");
}
}
private final static Logger LOGGER = Logger.getLogger(ClearCaseUcmBaselineParameterDefinition.class.getName());
}