package org.ovirt.engine.exttool.core;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.TreeMap;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.api.extensions.Base;
import org.ovirt.engine.api.extensions.ExtMap;
import org.ovirt.engine.core.extensions.mgr.ExtensionsManager;
import org.ovirt.engine.core.uutils.cli.parser.ArgumentsParser;
import org.slf4j.LoggerFactory;
public class ExtensionsToolExecutor {
private static String PROGRAM_NAME = System.getProperty("org.ovirt.engine.exttool.core.programName");
private static String PACKAGE_NAME = System.getProperty("org.ovirt.engine.exttool.core.packageName");
private static String PACKAGE_VERSION = System.getProperty("org.ovirt.engine.exttool.core.packageVersion");
private static String PACKAGE_DISPLAY_NAME = System.getProperty("org.ovirt.engine.exttool.core.packageDisplayName");
private static String ENGINE_ETC = System.getProperty("org.ovirt.engine.exttool.core.engineEtc");
private static String AAA_JAAS_USE_TICKET_CACHE = System.getProperty("org.ovirt.engine.exttool.core.useTicketCache");
private static String AAA_JAAS_TICKET_CACHE_FILE = System.getProperty("org.ovirt.engine.exttool.core.ticketCacheFile");
private static String AAA_JAAS_USE_KEYTAB = System.getProperty("org.ovirt.engine.exttool.core.useKeytab");
private static String AAA_JAAS_KEYTAB_FILE = System.getProperty("org.ovirt.engine.exttool.core.keytabFile");
private static String AAA_JAAS_PRINCIPAL_NAME = System.getProperty("org.ovirt.engine.exttool.core.principalName");
private static String JAAS_CONF = System.getProperty("java.security.auth.login.config");
private static final org.slf4j.Logger log = LoggerFactory.getLogger(ExtensionsToolExecutor.class);
private static final Logger OVIRT_LOGGER = Logger.getLogger("org.ovirt");
private static final ExtMap context = new ExtMap();
public static void main(String... args) {
int exitStatus = 1;
Path tempJAASConf = null;
List<String> cmdArgs = new ArrayList<>(Arrays.asList(args));
try {
tempJAASConf = createTemporaryJAASconfiguration();
final Map<String, String> contextSubstitutions = new HashMap<>();
contextSubstitutions.put("@ENGINE_ETC@", ENGINE_ETC);
contextSubstitutions.put("@PROGRAM_NAME@", PROGRAM_NAME);
context.put(ModuleService.ContextKeys.CLI_PARSER_SUBSTITUTIONS, contextSubstitutions);
setupLogger();
ArgumentsParser parser;
Map<String, ModuleService> moduleServices = loadModules(ModuleService.class);
context.put(ModuleService.ContextKeys.MODULES, moduleServices);
final Map<String, String> substitutions = new HashMap<>(contextSubstitutions);
substitutions.put("@MODULE_LIST@", getModules(moduleServices));
try (InputStream stream = ExtensionsToolExecutor.class.getResourceAsStream("arguments.properties")) {
parser = new ArgumentsParser(stream, "core");
parser.getSubstitutions().putAll(substitutions);
}
parser.parse(cmdArgs);
Map<String, Object> argMap = parser.getParsedArgs();
setupLogger(argMap);
log.debug("Version: {}-{} ({})", PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DISPLAY_NAME);
if((Boolean)argMap.get("help")) {
System.out.format("Usage: %s", parser.getUsage());
throw new ExitException("Help", 0);
} else if((Boolean)argMap.get("version")) {
System.out.format("%s-%s (%s)%n", PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DISPLAY_NAME);
throw new ExitException("Version", 0);
}
if(!parser.getErrors().isEmpty()) {
for(Throwable t : parser.getErrors()) {
log.error(t.getMessage());
log.debug(t.getMessage(), t);
}
throw new ExitException("Parsing error", 1);
}
if (cmdArgs.size() < 1) {
log.error("Please provide module.");
throw new ExitException("Module not provided", 1);
}
String module = cmdArgs.get(0);
ModuleService moduleService = moduleServices.get(module);
if (moduleService == null) {
log.error("No such '{}' module exists.", module);
throw new ExitException(1);
}
moduleService.parseArguments(cmdArgs);
log.info("========================================================================");
log.info("============================ Initialization ============================");
log.info("========================================================================");
ExtensionsManager extensionsManager = new ExtensionsManager();
extensionsManager.getGlobalContext().put(
Base.GlobalContextKeys.APPLICATION_NAME,
Base.ApplicationNames.OVIRT_ENGINE_EXTENSIONS_TOOL
);
context.put(ModuleService.ContextKeys.EXTENSION_MANAGER, extensionsManager);
loadExtensions(extensionsManager, moduleService, argMap);
log.info("========================================================================");
log.info("============================== Execution ===============================");
log.info("========================================================================");
moduleService.run();
exitStatus = 0;
} catch(ExitException e) {
log.debug(e.getMessage(), e);
exitStatus = e.getExitCode();
} catch (Throwable t) {
log.error(t.getMessage() != null ? t.getMessage() : t.getClass().getName());
log.debug("Exception:", t);
} finally {
if (tempJAASConf != null) {
try {
Files.delete(tempJAASConf);
} catch (IOException ex) {
log.warn("Failed to delete temporary file '{}'", tempJAASConf.toString());
}
}
}
log.debug("Exiting with status '{}'", exitStatus);
System.exit(exitStatus);
}
/**
* This method creates a temporary JAAS configuration file, which reflect configuration of Wildfly picketbox
* framework used in ovirt-engine. Then change system property to use this new configuration.
*
* @return path to temporary JAAS confiugration file
* @throws IOException is thrown when temporary JAAS configuration fails to create
*/
private static Path createTemporaryJAASconfiguration() throws IOException {
// Copy original JAAS configuration to new temporary file:
Path tempJAASConf = Files.createTempFile("jaas", ".conf");
Files.copy(new File(JAAS_CONF).toPath(), tempJAASConf, StandardCopyOption.REPLACE_EXISTING);
// Append additional JAAS configuraion which reflect Wildfly picketbox configuration:
try (FileWriter fw = new FileWriter(tempJAASConf.toFile(), true)) {
String ticketCacheFile = "";
if (StringUtils.isNotEmpty(AAA_JAAS_TICKET_CACHE_FILE)) {
ticketCacheFile = String.format("ticketCache=\"%s\"", AAA_JAAS_TICKET_CACHE_FILE);
}
String keytabFile = "";
if (StringUtils.isNotEmpty(AAA_JAAS_KEYTAB_FILE)) {
keytabFile = String.format("keyTab=\"%s\"", AAA_JAAS_KEYTAB_FILE);
}
String principalName = "";
if (StringUtils.isNotEmpty(AAA_JAAS_PRINCIPAL_NAME)) {
principalName = String.format("principal=\"%s\"", AAA_JAAS_PRINCIPAL_NAME);
}
fw.write(
String.format(
"oVirtKerbAAA {%n" +
"com.sun.security.auth.module.Krb5LoginModule%n" +
"required%n" +
"client=true%n" +
"useTicketCache=%s%n" +
"%s%n" + // ticket cache path
"useKeyTab=%s%n" +
"%s%n" + // keytab path
"%s%n" + // principal name
"doNotPrompt=true%n" +
";%n" +
"};%n",
AAA_JAAS_USE_TICKET_CACHE,
ticketCacheFile,
AAA_JAAS_USE_KEYTAB,
keytabFile,
principalName
)
);
}
// Run tool with temporary JAAS configuration:
System.setProperty("java.security.auth.login.config", tempJAASConf.toString());
return tempJAASConf;
}
private static void loadExtensions(
ExtensionsManager extensionsManager,
ModuleService moduleService,
Map<String, Object> argMap
) {
List<File> files = (List<File>)argMap.get("extension-file");
if(files == null) {
files = listFiles(
(String) argMap.get("extensions-dir"),
"properties"
);
}
List<String> loadedExtensions = new LinkedList<>();
for(File f : files) {
log.debug("Loading extension file '{}'", f.getName());
try {
String name = extensionsManager.load(f);
if (name == null) {
log.debug("Extension file '{}' disabled", f.getName());
} else {
loadedExtensions.add(name);
}
} catch (Exception ex) {
log.error("Extension '{}' load failed (ignored): {}", f.getName(), ex.getMessage());
log.debug("Exception:", ex);
}
}
for(String extension : loadedExtensions) {
try {
extensionsManager.initialize(extension);
log.debug("Extension '{}' initialized", extension);
} catch (Exception ex) {
log.error("Extension '{}' initialization failed (ignored): {}", extension, ex.getMessage());
log.debug("Exception:", ex);
}
}
extensionsManager.dump();
}
private static Map<String, ModuleService> loadModules(Class cls) {
Map<String, ModuleService> modules = new HashMap<>();
if(cls != null) {
ServiceLoader<ModuleService> loader = ServiceLoader.load(cls);
for (ModuleService module : loader) {
modules.put(module.getName(), module);
module.setContext(context);
}
}
log.debug("Loaded modules: {}", modules.keySet());
return modules;
}
private static void setupLogger() {
String logLevel = System.getenv("OVIRT_LOGGING_LEVEL");
OVIRT_LOGGER.setLevel(
logLevel != null ? Level.parse(logLevel) : Level.INFO
);
}
private static void setupLogger(Map<String, Object> args) throws IOException {
Logger log = Logger.getLogger("");
String logfile = (String)args.get("log-file");
if(logfile != null) {
FileHandler fh = new FileHandler(logfile, true);
fh.setFormatter(new SimpleFormatter());
log.addHandler(fh);
}
OVIRT_LOGGER.setLevel((Level)args.get("log-level"));
}
private static List<File> listFiles(String directory, String suffix) {
File dir = new File(directory);
List<File> propFiles = new ArrayList<>();
File[] dirFiles = dir.listFiles();
if (dirFiles != null) {
for (File file : dirFiles) {
if (file.getName().endsWith(suffix)) {
propFiles.add(file);
}
}
}
return propFiles;
}
private static String getModules(Map<String, ModuleService> modules) {
StringBuilder sb = new StringBuilder();
for(Map.Entry<String, ModuleService> entry : new TreeMap<>(modules).entrySet()) {
sb.append(
String.format(" %-10s - %s%n", entry.getKey(), entry.getValue().getDescription())
);
}
return sb.toString();
}
}