/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package se.kth.karamel.backend.github.util; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import se.kth.karamel.backend.Experiment; import se.kth.karamel.backend.Experiment.Code; import se.kth.karamel.backend.github.GithubApi; import se.kth.karamel.common.util.Settings; import se.kth.karamel.common.exception.KaramelException; /** * How to use. Invoke methods in this order: (1) @see ChefExperimentExtractor#parseAttributesAddToGit() (2) @see * ChefExperimentExtractor#parseRecipesAddToGit() * */ public class ChefExperimentExtractor { private static final String YAML_DEPENDENCY_PREFIX = " - "; private static final String YAML_RECIPE_PREFIX = " - recipe: "; // <AttrName, AttrValue> pair added to attributes/default.rb private static final SortedMap<String, String> attrs = new TreeMap<>(); private static final Map<String, Map<String, String>> configFiles = new HashMap<>(); /** * Parses all scripts and config files and outputs to metadata.rb and attributes/default.rb the configuration values * found. * * @param owner org/user on github * @param repoName name of github repository * @param experiment input scripts/config filenames and content * @throws KaramelException */ public static void parseAttributesAddToGit(String owner, String repoName, Experiment experiment) throws KaramelException { attrs.clear(); configFiles.clear(); StringBuilder recipeDescriptions = new StringBuilder(); List<Code> experiments = experiment.getCode(); // Extract all the configFileNames: write them to metadata.rb later // Extract all the from the configFile contents: write them to attributes/default.rb later // No conflict detection for duplicate key-value pairs yet. Should be done in Javascript in Browser. for (Code code : experiments) { String configFileName = code.getConfigFileName(); Map<String, String> cfs = configFiles.get(configFileName); if (cfs == null) { cfs = new HashMap<>(); configFiles.put(configFileName, cfs); } recipeDescriptions.append("recipe \"").append(repoName).append(Settings.COOKBOOK_DELIMITER). append(code.getName()).append("\", \"configFile=").append(configFileName) .append("; Experiment name: ").append(code.getName()).append("\"").append(System.lineSeparator()); String str = code.getConfigFileContents(); Pattern p = Pattern.compile("\\s*(.*)\\s*=\\s*(.*)\\s*"); Matcher m = p.matcher(str); while (m.find()) { String name = m.group(1); String value = m.group(2); if (!name.isEmpty()) { cfs.put(name, value); // attrs.put(name, value); } } } String email = (GithubApi.getEmail() == null) ? "karamel@karamel.io" : GithubApi.getEmail(); try { StringBuilder defaults_rb = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_ATTRIBUTES_DEFAULT // , "name", repoName, // "user", experiment.getUser(), // "group", experiment.getGroup(), // "http_binaries", experiment.getUrlBinary() ); String str = experiment.getDefaultAttributes(); Pattern p = Pattern.compile("\\s*(.*)\\s*=\\s*(.*)\\s*"); Matcher m = p.matcher(str); while (m.find()) { String name = m.group(1); String value = m.group(2); if (!name.isEmpty()) { attrs.put(name, value); } } // Add all key-value pairs from the config files to the default attributes for (String key : attrs.keySet()) { String entry = "default[:" + repoName + "][:" + key + "] = \"" + attrs.get(key) + "\""; defaults_rb.append(entry).append(System.lineSeparator()); } StringBuilder metadata_rb = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_METADATA, "name", repoName, "user", experiment.getUser(), "email", email, "depends", "", "resolve_ips", "", "build_command", experiment.getMavenCommand(), "url_binary", experiment.getUrlBinary(), "url_gitclone", experiment.getUrlGitClone(), "build_command", experiment.getMavenCommand(), "ip_params", "", "more_recipes", recipeDescriptions.toString() ); for (String key : attrs.keySet()) { String entry = "attribute \"" + repoName + "/" + key + "\"," + System.lineSeparator() + ":description => \"" + key + " parameter value\"," + System.lineSeparator() + ":type => \"string\""; metadata_rb.append(entry).append(System.lineSeparator()).append(System.lineSeparator()); } // 3. write them to files and push to github GithubApi.addFile(owner, repoName, "attributes/default.rb", defaults_rb.toString()); GithubApi.addFile(owner, repoName, "metadata.rb", metadata_rb.toString()); } catch (IOException ex) { throw new KaramelException("Problem parsing attributes from GitHub: " + ex.getMessage()); } } /** * Parses the user-defined script files and for each script, a recipe file is generated and added to the git repo. * * @param owner * @param repoName * @param experimentContext * @throws se.kth.karamel.common.exception.KaramelException * @throws KaramelExceptionntents.toString()); // Update Karamel */ public static void parseRecipesAddToGit(String owner, String repoName, Experiment experimentContext) throws KaramelException { try { StringBuilder kitchenContents = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_KITCHEN_YML, "name", repoName ); GithubApi.addFile(owner, repoName, ".kitchen.yml", kitchenContents.toString()); List<Code> experiments = experimentContext.getCode(); String localDependencies = repoName + Settings.COOKBOOK_DELIMITER + "install" + System.lineSeparator() + experimentContext.getLocalDependencies(); String[] lDeps = localDependencies.split(System.lineSeparator()); Set<String> lDepsRemoveDuplicates = new HashSet<>(); StringBuilder lDepsFinal = new StringBuilder(); for (String s : lDeps) { s = s.trim(); if (!s.isEmpty()) { lDepsRemoveDuplicates.add(s); } } for (String s : lDepsRemoveDuplicates) { lDepsFinal.append(YAML_DEPENDENCY_PREFIX).append(s).append(System.lineSeparator()); } String globalDependencies = experimentContext.getGlobalDependencies(); String[] gDeps = globalDependencies.split(System.lineSeparator()); Set<String> gDepsRemoveDuplicates = new HashSet<>(); StringBuilder gDepsFinal = new StringBuilder(); for (String s : gDeps) { s = s.trim(); if (!s.isEmpty()) { gDepsRemoveDuplicates.add(s); } } for (String s : gDepsRemoveDuplicates) { gDepsFinal.append(YAML_DEPENDENCY_PREFIX).append(s).append(System.lineSeparator()); } StringBuilder recipeDepsKaramelfile = new StringBuilder(); for (Code experiment : experiments) { String recipeName = experiment.getName(); recipeDepsKaramelfile.append(YAML_RECIPE_PREFIX).append(repoName).append(Settings.COOKBOOK_DELIMITER).append( recipeName) .append(System.lineSeparator()); recipeDepsKaramelfile.append(" local:").append(System.lineSeparator()); recipeDepsKaramelfile.append(lDepsFinal.toString()); recipeDepsKaramelfile.append(" global:").append(System.lineSeparator()); recipeDepsKaramelfile.append(gDepsFinal.toString()); } StringBuilder karamelContents = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_KARAMELFILE, "name", repoName, "next_recipes", recipeDepsKaramelfile.toString() ); // TODO - integration with cluster defn file // String ymlString = experimentContext.getClusterDefinition(); String berksfile = experimentContext.getBerksfile(); StringBuilder berksContents = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_BERKSFILE, "berks_dependencies", berksfile ); GithubApi.addFile(owner, repoName, "Berksfile", berksContents.toString()); Map<String, String> expConfigFileNames = new HashMap<>(); Map<String, String> expConfigFilePaths = new HashMap<>(); // 2. write them to recipes/default.rb and metadata.rb for (Code experiment : experiments) { String experimentName = experiment.getName(); String configFilePath = experiment.getConfigFileName(); String configFileContents = experiment.getConfigFileContents(); String configFileName = configFilePath; int filePos = configFileName.lastIndexOf("/"); if (filePos != -1) { configFileName = configFileName.substring(filePos + 1); } String email = (GithubApi.getEmail() == null) ? "karamel@karamel.io" : GithubApi.getEmail(); String username = attrs.containsKey("user") ? attrs.get("user") : experimentContext.getUser(); String groupname = attrs.containsKey("group") ? attrs.get("group") : experimentContext.getUser(); StringBuilder recipe_rb = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_RECIPE_EXPERIMENT, "cookbook", repoName, "name", experimentName, "interpreter", experiment.getScriptType(), "user", username, "group", groupname, "script_contents", experiment.getScriptContents() ); String recipeContents = recipe_rb.toString(); // Replace all parameters with chef attribute values for (String attr : attrs.keySet()) { recipeContents = recipeContents.replaceAll("%%" + attr + "%%", "#{node[:" + repoName + "][:" + attr + "]}"); } for (String attr : attrs.keySet()) { configFileContents = configFileContents.replaceAll("%%" + attr + "%%", "<%= node[:" + repoName + "][:" + attr + "] =>"); } if (!configFilePath.isEmpty()) { expConfigFileNames.put(experimentName, configFileName); expConfigFilePaths.put(experimentName, configFilePath); } // 3. write them to files and push to github GithubApi.addFile(owner, repoName, "recipes" + File.separator + experimentName + ".rb", recipeContents); if (!configFileName.isEmpty()) { GithubApi.addFile(owner, repoName, "templates" + File.separator + "defaults" + File.separator + configFileName + ".erb", configFileContents); } } StringBuilder configFilesTemplateDefns = new StringBuilder(); for (String expName : expConfigFileNames.keySet()) { String configFilePath = expConfigFileNames.get(expName); String configFileName = expConfigFileNames.get(expName); StringBuilder configProps = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_CONFIG_PROPS, "name", expName, "configFileName", configFileName, "configFilePath", configFilePath, "ip_params", "" ); configFilesTemplateDefns.append(configProps).append(System.lineSeparator()); } StringBuilder install_rb = CookbookGenerator.instantiateFromTemplate( Settings.CB_TEMPLATE_RECIPE_INSTALL, "name", repoName, "cookbook", repoName, "checksum", "", "resolve_ips", "", "setup_code", experimentContext.getExperimentSetupCode(), "config_files", configFilesTemplateDefns.toString() ); GithubApi.addFile(owner, repoName, "recipes/install.rb", install_rb.toString()); GithubApi.addFile(owner, repoName, "Karamelfile", karamelContents.toString()); } catch (IOException ex) { throw new KaramelException("Problem parsing recipes from GitHub: " + ex.getMessage()); } } }