/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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 com.asakusafw.windgate.hadoopfs.ssh;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.windgate.core.WindGateLogger;
import com.asakusafw.windgate.core.resource.ResourceProfile;
import com.asakusafw.windgate.core.util.PropertiesUtil;
import com.asakusafw.windgate.hadoopfs.HadoopFsLogger;
/**
* A structured profile for {@link AbstractSshHadoopFsMirror}.
* @since 0.2.2
* @version 0.7.0
*/
public class SshProfile {
static final WindGateLogger WGLOG = new HadoopFsLogger(SshProfile.class);
static final Logger LOG = LoggerFactory.getLogger(SshProfile.class);
/**
* The remote base target.
* @since 0.4.0
*/
public static final String PATH_BASE_TARGET = "windgate-ssh";
/**
* The remote 'get' command path.
*/
public static final String COMMAND_GET = "libexec/get.sh";
/**
* The remote 'put' command path.
*/
public static final String COMMAND_PUT = "libexec/put.sh";
/**
* The remote 'delete' command path.
*/
public static final String COMMAND_DELETE = "libexec/delete.sh";
/**
* The key of remote target installed path.
*/
public static final String KEY_TARGET = "target";
/**
* The key of user name.
*/
public static final String KEY_USER = "user";
/**
* The key of host name.
*/
public static final String KEY_HOST = "host";
/**
* The key of port number.
*/
public static final String KEY_PORT = "port";
/**
* The key of path to the private key.
* This value can includes environment variables in form of <code>${VARIABLE-NAME}</code>.
*/
public static final String KEY_PRIVATE_KEY = "privateKey";
/**
* The key of passphrase.
*/
public static final String KEY_PASS_PHRASE = "passPhrase";
/**
* The key of {@link CompressionCodec} class name.
*/
public static final String KEY_COMPRESSION = "compression";
/**
* The key prefix of additional remote environment variables.
* @since 0.4.0
*/
public static final String PREFIX_ENV = "env.";
private final String resourceName;
private final String target;
private final String user;
private final String host;
private final int port;
private final String privateKey;
private final String passPhrase;
private final Map<String, String> environmentVariables;
/**
* Creates a new instance.
* @param name the resource name
* @param target the remote target installed path
* @param user the connection user name
* @param host the connection target host
* @param port the connection target port
* @param privateKey the path to the private key file
* @param passPhrase the passphrase of target private key
* @param compressionCodec the compression codec, or {@code null} if does not compress
* @param env environment variables
* @throws IllegalArgumentException if any parameter is {@code null}
* @deprecated Use {@link #SshProfile(String, String, String, String, int, String, String, Map)} instead
*/
@Deprecated
public SshProfile(
String name,
String target,
String user,
String host,
int port,
String privateKey,
String passPhrase,
CompressionCodec compressionCodec,
Map<String, String> env) {
this(name, target, user, host, port, privateKey, passPhrase, env);
}
/**
* Creates a new instance.
* @param name the resource name
* @param target the remote target installed path
* @param user the connection user name
* @param host the connection target host
* @param port the connection target port
* @param privateKey the path to the private key file
* @param passPhrase the passphrase of target private key
* @param env environment variables
* @throws IllegalArgumentException if any parameter is {@code null}
* @since 0.7.0
*/
public SshProfile(
String name,
String target,
String user,
String host,
int port,
String privateKey,
String passPhrase,
Map<String, String> env) {
if (name == null) {
throw new IllegalArgumentException("name must not be null"); //$NON-NLS-1$
}
if (target == null) {
throw new IllegalArgumentException("target must not be null"); //$NON-NLS-1$
}
if (user == null) {
throw new IllegalArgumentException("user must not be null"); //$NON-NLS-1$
}
if (host == null) {
throw new IllegalArgumentException("host must not be null"); //$NON-NLS-1$
}
if (privateKey == null) {
throw new IllegalArgumentException("privateKey must not be null"); //$NON-NLS-1$
}
if (passPhrase == null) {
throw new IllegalArgumentException("passPhrase must not be null"); //$NON-NLS-1$
}
if (env == null) {
throw new IllegalArgumentException("env must not be null"); //$NON-NLS-1$
}
this.resourceName = name;
this.target = target;
this.user = user;
this.host = host;
this.port = port;
this.privateKey = privateKey;
this.passPhrase = passPhrase;
this.environmentVariables = Collections.unmodifiableMap(env);
}
/**
* Converts {@link ResourceProfile} into {@link SshProfile}.
* @param configuration the current configuration
* @param profile target profile
* @return the converted profile
* @throws IllegalArgumentException if profile is not valid, or any parameter is {@code null}
*/
public static SshProfile convert(Configuration configuration, ResourceProfile profile) {
if (configuration == null) {
throw new IllegalArgumentException("configuration must not be null"); //$NON-NLS-1$
}
if (profile == null) {
throw new IllegalArgumentException("profile must not be null"); //$NON-NLS-1$
}
String name = profile.getName();
String target = extract(profile, KEY_TARGET, false);
String user = extract(profile, KEY_USER, true);
String host = extract(profile, KEY_HOST, true);
int port = extractPort(profile);
String privateKey = extract(profile, KEY_PRIVATE_KEY, true);
String passPhrase = extractPassPhrase(profile);
extractCompressionCodec(configuration, profile);
Map<String, String> env = extractEnv(profile);
if (target == null) {
String home = env.get("ASAKUSA_HOME");
if (home == null || home.isEmpty()) {
WGLOG.error("E10001",
profile.getName(),
PREFIX_ENV + "ASAKUSA_HOME",
null);
throw new IllegalArgumentException(MessageFormat.format(
"Resource \"{0}\" must declare \"{1}\"",
profile.getName(),
PREFIX_ENV + "ASAKUSA_HOME"));
}
if (home.endsWith("/") == false) {
home = home + "/";
}
target = home + PATH_BASE_TARGET;
}
return new SshProfile(
name,
target,
user,
host,
port,
privateKey,
passPhrase,
env);
}
private static String extract(ResourceProfile profile, String configKey, boolean mandatory) {
assert profile != null;
assert configKey != null;
String value = profile.getConfiguration().get(configKey);
if (value == null) {
if (mandatory == false) {
return null;
} else {
WGLOG.error("E10001",
profile.getName(),
configKey,
null);
throw new IllegalArgumentException(MessageFormat.format(
"Resource \"{0}\" must declare \"{1}\"",
profile.getName(),
configKey));
}
}
return resolve(profile, configKey, value.trim());
}
private static String resolve(ResourceProfile profile, String configKey, String value) {
assert profile != null;
assert configKey != null;
assert value != null;
try {
return profile.getContext().getContextParameters().replace(value, true);
} catch (IllegalArgumentException e) {
WGLOG.error(e, "E10001",
profile.getName(),
configKey,
value);
throw new IllegalArgumentException(MessageFormat.format(
"Failed to resolve environment variables: {2} (resource={0}, property={1})",
profile.getName(),
configKey,
value), e);
}
}
private static int extractPort(ResourceProfile profile) {
assert profile != null;
String portString = extract(profile, KEY_PORT, true);
try {
return Integer.parseInt(portString);
} catch (NumberFormatException e) {
WGLOG.error("E10001",
profile.getName(),
KEY_PORT,
portString);
throw new IllegalArgumentException(MessageFormat.format(
"The \"{1}\" must be a valid port number: {2} (resource={0})",
profile.getName(),
KEY_PORT,
portString));
}
}
private static Map<String, String> extractEnv(ResourceProfile profile) {
assert profile != null;
Map<String, String> map = PropertiesUtil.createPrefixMap(profile.getConfiguration(), PREFIX_ENV);
Map<String, String> results = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
String resolved = resolve(profile, PREFIX_ENV + entry.getKey(), entry.getValue());
results.put(entry.getKey(), resolved);
}
LOG.debug("Remote Env ({}): {}", profile.getName(), results);
return results;
}
private static String extractPassPhrase(ResourceProfile profile) {
assert profile != null;
String passPhrase = extract(profile, KEY_PASS_PHRASE, false);
passPhrase = passPhrase == null ? "" : passPhrase;
return passPhrase;
}
private static void extractCompressionCodec(Configuration configuration, ResourceProfile profile) {
assert configuration != null;
assert profile != null;
String compressionCodecString = extract(profile, KEY_COMPRESSION, false);
if (compressionCodecString != null) {
WGLOG.warn("W10001",
profile.getName(),
KEY_COMPRESSION,
compressionCodecString);
}
}
/**
* Returns the resource name.
* @return the resource name
*/
public String getResourceName() {
return resourceName;
}
/**
* Return the remote installation path.
* @return the remote installation path
*/
public String getTarget() {
return target;
}
/**
* Returns the get command.
* @return the get command
*/
public String getGetCommand() {
return getCommand(COMMAND_GET);
}
/**
* Returns the put command.
* @return the put command
*/
public String getPutCommand() {
return getCommand(COMMAND_PUT);
}
/**
* Returns the delete command.
* @return the delete command
*/
public String getDeleteCommand() {
return getCommand(COMMAND_DELETE);
}
private String getCommand(String command) {
assert command != null;
StringBuilder buf = new StringBuilder();
buf.append(target);
if (target.endsWith("/") == false) {
buf.append('/');
}
buf.append(command);
return buf.toString();
}
/**
* Returns the user name.
* @return the user name
*/
public String getUser() {
return user;
}
/**
* Returns the target host name.
* @return the target host
*/
public String getHost() {
return host;
}
/**
* Return the target port.
* @return the target port
*/
public int getPort() {
return port;
}
/**
* Return the path to the private key file.
* @return the path to the private key file
*/
public String getPrivateKey() {
return privateKey;
}
/**
* Returns the pass phrase of the {@link #getPrivateKey()}.
* @return the pass phrase
*/
public String getPassPhrase() {
return passPhrase;
}
/**
* Returns the additional remote environment variables.
* @return the remote environment variables
* @since 0.4.0
*/
public Map<String, String> getEnvironmentVariables() {
return environmentVariables;
}
/**
* Returns the compression codec of putting sequence files.
* @return the compression codec, or {@code null} if does not compress
* @deprecated from {@code 0.7.0}, WindGate does not use sequence files
*/
@Deprecated
public CompressionCodec getCompressionCodec() {
return null;
}
}