/*
* The MIT License
*
* Copyright (c) 2010, Manufacture Française des Pneumatiques Michelin, Thomas Maurel
*
* 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.qc.qtpaddins;
import com.michelin.cio.hudson.plugins.qc.client.QualityCenterClientInstallation;
import com.michelin.cio.hudson.plugins.qc.Messages;
import com.michelin.cio.hudson.plugins.qc.QualityCenterUtils;
import groovy.text.GStringTemplateEngine;
import hudson.AbortException;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.ProxyConfiguration;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolInstaller;
import hudson.tools.ToolInstallerDescriptor;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
/**
* This class represents the installer for QuickTest Professional Add-in.
*
* <p>We provide two ways to get the installer:<ol>
* <li>The first one consists in downloading the right installer from <a
* href="http://update.external.hp.com/qualitycenter/qc90/mictools/qtp/index.html">
* HP Update Center</a>, so an Internet connection is required (don't forget to
* set the proxy parameters in Hudson if applicable);</li>
* <li>The second one consists in getting the installer from a directory on the
* Hudson master node's filesystem.</li>
* </ol></p>
*
* @author Thomas Maurel
*/
public class QualityCenterQTPAddinsInstaller extends ToolInstaller {
private static final String TEMPLATE_NAME = "installTemplate.iss";
private static final String GENERATED_ISS_NAME = "setup.iss";
private static final String REPORT_READER_EXE = "QTReport.exe";
private static final String BIN_FOLDER = "bin";
private final String version;
private final String localPathToQTPAddin;
private final boolean acceptLicense;
@DataBoundConstructor
public QualityCenterQTPAddinsInstaller(String version, String localPathToQTPAddin, boolean acceptLicense) {
super(null);
this.version = version;
this.localPathToQTPAddin = localPathToQTPAddin;
this.acceptLicense = acceptLicense;
}
public boolean isAcceptLicense() {
return acceptLicense;
}
public String getLocalPathToQTPAddin() {
return localPathToQTPAddin;
}
public String getVersion() {
return version;
}
@Override
public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws InterruptedException, AbortException, IOException {
FilePath expectedLocation = preferredLocation(tool, node);
PrintStream out = log.getLogger();
// Check if has been installed by hudson or manually
if(expectedLocation.child(".installedByHudson").exists() || expectedLocation.child(BIN_FOLDER).child(REPORT_READER_EXE).exists()) {
return expectedLocation;
}
// Create the directory tree
expectedLocation.mkdirs();
FilePath file = expectedLocation.child(GENERATED_ISS_NAME);
// Did he accept the license agreement?
if(!acceptLicense) {
log.fatalError(Messages.QualityCenterQTPAddinsInstaller_AcceptLicense());
throw new AbortException();
}
// Get the URL to the bundled InstallShield silent install script template
URL template = Hudson.getInstance().pluginManager.uberClassLoader.getResource(TEMPLATE_NAME);
// Note from the code reviewer: The following could clearly have been done
// with Velocity (and it would surely have been faster), but, well, let's
// let the kids play a little bit!
//
// Groovy fun
// To perform a silent installation, we need to have a setup.iss file in the same
// folder as the installer which will describe each step of the installation.
// Though we need to specify the installer key for each step, and the key varies for each
// version which is why we need to parse a template.
GStringTemplateEngine engine = new GStringTemplateEngine();
// Get the defined version of the QTPAddin
QTPVersion currentVersion = QTPVersion.valueOf("QTP" + this.version.replaceAll("\\.", ""));
if(version == null) {
log.fatalError(Messages.QualityCenterQTPAddinsInstaller_CouldntFindValidVersion());
throw new AbortException();
}
// Build a map to parse the template
Map<String, String> binding = new HashMap<String, String>();
binding.put("key", currentVersion.key);
binding.put("path", expectedLocation.absolutize().getRemote());
String instalIss;
out.println(Messages.QualityCenterQTPAddinsInstaller_GeneratingInstallerISS());
try {
// Parse the template and put the result in a string
instalIss = engine.createTemplate(template).make(binding).toString();
} catch (Exception e) {
log.fatalError(Messages.QualityCenterQTPAddinsInstaller_CouldntGenerateInstallerISS());
throw new AbortException();
}
// Put the parsed template in the project workspace
file.write(instalIss, "ISO-8859-1");
FilePath installer;
// If the installer is stocked on master, copy it from there
if(localPathToQTPAddin != null & localPathToQTPAddin.length() > 0) {
FilePath installerOnMaster = new FilePath(Hudson.MasterComputer.localChannel, localPathToQTPAddin);
if(!installerOnMaster.exists()) {
log.fatalError(Messages.QualityCenterClientInstaller_CannotFindInstaller());
throw new AbortException();
}
if(installerOnMaster.isDirectory()) {
log.fatalError(Messages.QualityCenterClientInstaller_ShouldBeAFile());
throw new AbortException();
}
installer = expectedLocation.child(installerOnMaster.getName());
out.println(Messages.QualityCenterClientInstaller_CopyingFromMaster(localPathToQTPAddin));
installer.copyFrom(installerOnMaster);
}
// Else, download it from HP Update Center
else {
URL installURL = new URL(currentVersion.url);
URLConnection cnx = ProxyConfiguration.open(installURL);
installer = expectedLocation.child(installURL.getFile());
out.println(Messages.QualityCenterClientInstaller_Downloading(installURL));
installer.copyFrom(cnx.getInputStream());
}
// Perform install
install(node.createLauncher(log), log, expectedLocation.absolutize().getRemote(), installer);
// Successfully installed
// Delete installer
installer.delete();
// Delete silent install script
file.delete();
// Does the bin folder exist after install ?
if(!expectedLocation.child(BIN_FOLDER).child(REPORT_READER_EXE).exists()) {
log.fatalError(Messages.QualityCenterQTPAddinsInstaller_CouldntFindExeAfterInstall(REPORT_READER_EXE));
throw new AbortException();
}
expectedLocation.child(".installedByHudson").touch(System.currentTimeMillis());
return expectedLocation;
}
public void install(Launcher launcher, TaskListener log, String expectedLocation, FilePath install) throws IOException, InterruptedException {
PrintStream out = log.getLogger();
String installName = install.getName();
FilePath absolutizedInstall = install.absolutize();
out.println(Messages.QualityCenterClientInstaller_Installing(installName));
FilePath logFilePath = absolutizedInstall.getParent().child("setup.log");
// Perform the silent install
ArgumentListBuilder args = new ArgumentListBuilder();
args.add(absolutizedInstall.getRemote());
args.add("/S");
args.add("/v/qn");
if(launcher.launch().cmds(args).stdout(out).pwd(expectedLocation).join() != 0) {
log.fatalError(Messages.QualityCenterClientInstaller_AbortedInstall());
out.println(logFilePath.readToString());
throw new AbortException();
}
out.println(Messages.QualityCenterQTPAddinsInstaller_InstallationSuccessfull());
}
@Extension
public static final class DescriptorImpl extends ToolInstallerDescriptor<QualityCenterQTPAddinsInstaller> {
public String getDisplayName() {
return Messages.QualityCenterQTPAddinsInstaller_DescriptorImpl_DisplayName();
}
/**
* Gets the supported versions of the QTP Addin.
*/
public QTPVersion[] getAddinsArray() throws MalformedURLException {
return QTPVersion.values();
}
@Override
public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
return toolType==QualityCenterClientInstallation.class;
}
public FormValidation doCheckLocalPathToQTPAddin(@QueryParameter String value) {
// This can be used to check the existence of a file on the server, so needs to be protected
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
return QualityCenterUtils.checkLocalPathToInstaller(value, true);
}
}
/**
* Currently supported versions of the QTP Addin.
*/
public enum QTPVersion {
QTP90(
"9.0",
"http://update.external.hp.com/qualitycenter/qc90/mictools/qtp/TDPlugInsSetup.exe",
"B7677BB4-7E32-4430-90AE-E37EE6FED55E"
),
QTP91(
"9.1",
"http://update.external.hp.com/qualitycenter/qc90/mictools/qtp/qtp_9_1/TDPlugInsSetup.exe",
"B7677BB4-7E32-4430-90AE-E37EE6FED55E"
),
QTP92(
"9.2",
"http://update.external.hp.com/qualitycenter/qc90/mictools/qtp/qtp_sp/TDPlugInsSetup.exe",
"051B35E4-5986-4AD5-9470-693558044699"
);
public final String version;
/**
* URL to download the addin from HP Update Center.
*/
public final String url;
/**
* The key is the ID of the installer used to generate the InstallShield silent install script.
*/
public final String key;
QTPVersion(String version, String url, String key) {
this.version = version;
this.url = url;
this.key = key;
}
public String getKey() {
return key;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
}
}