/*
* The MIT License
*
* Copyright (c) 2014 Red Hat, Inc.
*
* 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 org.jenkinsci.tools.configcloner.handler;
import hudson.Util;
import hudson.cli.NoCheckTrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import org.jenkinsci.tools.configcloner.CommandResponse;
import org.jenkinsci.tools.configcloner.ConfigDestination;
import org.jenkinsci.tools.configcloner.ConfigTransfer;
import org.jenkinsci.tools.configcloner.UrlParser;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import org.unix4j.Unix4j;
import org.unix4j.builder.Unix4jCommandBuilder;
import difflib.DiffUtils;
import difflib.Patch;
public abstract class TransferHandler implements Handler {
@Argument(multiValued = true, usage = "[<SRC>] [<DST>...]", metaVar = "URLS")
private List<String> entities = new ArrayList<String>();
@Option(name = "-f", aliases = { "--force" }, usage = "Overwrite target configuration if exists")
protected boolean force = false;
@Option(name = "-e", aliases = { "--expression" }, usage = "Transform configuration")
protected List<String> expressions = new ArrayList<String>();
@Option(name = "-n", aliases = { "--dry-run" }, usage = "Do not perform any modifications to any instance")
protected boolean dryRun = false;
@Option(name = "-i", aliases = { "--insecure" }, usage = "Do not check SSL certificate")
private void setInsecure(boolean insecure) throws NoSuchAlgorithmException, KeyManagementException {
if (insecure == true) {
System.out.println("Skipping HTTPS certificate checks altogether. Note that this is not secure at all.");
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
// bypass host name check, too.
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
}
}
protected final ConfigTransfer config;
protected TransferHandler(final ConfigTransfer config) {
this.config = config;
}
/**
* Template method to fetch and sent to many.
*
* Expects subclasses override *CommandName methods. This can be overriden in case the template is not suitable.
*/
public CommandResponse run(final CommandResponse response) {
// Get both of these before doing any work to fail validation early.
final ConfigDestination source = this.source();
final List<ConfigDestination> destinations = this.destinations();
response.out().println("Fetching " + source);
final CommandResponse.Accumulator xml = config.execute(
source, "", this.getCommandName(), source.entity()
);
if (!xml.succeeded()) return response.merge(xml);
for (final ConfigDestination dest: destinations) {
response.out().println("Sending " + dest);
send(dest, response, xml);
}
return response;
}
private CommandResponse send(
final ConfigDestination destination,
final CommandResponse response,
final CommandResponse.Accumulator xml
) {
final String destJob = destination.entity();
final String xmlString = getXml(fixupConfig(xml.stdout(), destination), response);
if (dryRun) return response.returnCode(0);
if (force) {
final CommandResponse.Accumulator rsp = config.execute(
destination, xmlString, this.updateCommandName(), destJob
);
if (rsp.succeeded()) return response.returnCode(0);
}
return response.merge(
config.execute(destination, xmlString, this.createCommandName(), destJob)
);
}
private String getXml(String rawXml, CommandResponse response) {
if (expressions.isEmpty()) return rawXml;
Unix4jCommandBuilder builder = Unix4j.fromString(rawXml);
for (String expr: expressions) {
builder = builder.sed(expr);
}
final String newXml = builder.toStringResult();
if (dryRun) response.out().print(describeTransformation(rawXml, newXml));
return newXml;
}
private String describeTransformation(String rawXml, String newXml) {
final List<String> rawLines = Arrays.asList(rawXml.split("\\r?\\n|\\r"));
Patch patch = DiffUtils.diff(rawLines, Arrays.asList(newXml.split("\\r?\\n|\\r")));
List<String> diff = DiffUtils.generateUnifiedDiff("Original", "Transformed", rawLines, patch, 3);
return Util.join(diff, System.lineSeparator());
}
/**
* Transform configuration before sending it to destination
*/
protected String fixupConfig(String config, ConfigDestination destination) {
return config;
}
protected ConfigDestination source() {
if (entities == null || entities.size() < 2) throw new IllegalArgumentException(
"Expecting 2 or more positional arguments"
);
return urlParser().destination(entities.get(0));
}
protected List<ConfigDestination> destinations() {
if (entities == null || entities.size() < 2) throw new IllegalArgumentException(
"Expecting 2 or more positional arguments"
);
return urlParser().pair(source(), entities.subList(1, entities.size()));
}
protected abstract UrlParser urlParser();
protected abstract String getCommandName();
protected abstract String updateCommandName();
protected abstract String createCommandName();
protected abstract String deleteCommandName();
}