1 /* 2 * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.openjdk.skara.bot; 24 25 import org.openjdk.skara.census.*; 26 import org.openjdk.skara.host.*; 27 import org.openjdk.skara.host.network.URIBuilder; 28 import org.openjdk.skara.json.*; 29 import org.openjdk.skara.vcs.Repository; 30 31 import java.io.*; 32 import java.net.URI; 33 import java.nio.file.*; 34 import java.time.Duration; 35 import java.util.*; 36 import java.util.logging.*; 37 import java.util.regex.Pattern; 38 39 public class BotRunnerConfiguration { 40 private final Logger log; 41 private final JSONObject config; 42 private final Map<String, Host> hosts; 43 private final Map<String, HostedRepository> repositories; 44 45 private BotRunnerConfiguration(JSONObject config, Path cwd) throws ConfigurationError { 46 this.config = config; 47 log = Logger.getLogger("org.openjdk.skara.bot"); 48 49 hosts = parseHosts(config, cwd); 50 repositories = parseRepositories(config); 51 } 52 53 private Map<String, Host> parseHosts(JSONObject config, Path cwd) throws ConfigurationError { 54 Map<String, Host> ret = new HashMap<>(); 55 56 if (!config.contains("hosts")) { 57 return ret; 58 } 59 60 for (var entry : config.get("hosts").fields()) { 61 if (entry.value().contains("gitlab")) { 62 var gitlab = entry.value().get("gitlab"); 63 var uri = URIBuilder.base(gitlab.get("url").asString()).build(); 64 var pat = new PersonalAccessToken(gitlab.get("username").asString(), gitlab.get("pat").asString()); 65 ret.put(entry.name(), HostFactory.createGitLabHost(uri, pat)); 66 } else if (entry.value().contains("github")) { 67 var github = entry.value().get("github"); 68 URI uri; 69 if (github.contains("url")) { 70 uri = URIBuilder.base(github.get("url").asString()).build(); 71 } else { 72 uri = URIBuilder.base("https://github.com/").build(); 73 } 74 Pattern webUriPattern = null; 75 String webUriReplacement = null; 76 if (github.contains("weburl")) { 77 webUriPattern = Pattern.compile(github.get("weburl").get("pattern").asString()); 78 webUriReplacement = github.get("weburl").get("replacement").asString(); 79 } 80 81 if (github.contains("app")) { 82 var keyFile = cwd.resolve(github.get("app").get("key").asString()); 83 ret.put(entry.name(), HostFactory.createGitHubHost(uri, webUriPattern, webUriReplacement, keyFile.toString(), 84 github.get("app").get("id").asString(), 85 github.get("app").get("installation").asString())); 86 } else { 87 var pat = new PersonalAccessToken(github.get("username").asString(), github.get("pat").asString()); 88 ret.put(entry.name(), HostFactory.createGitHubHost(uri, pat)); 89 } 90 } else { 91 throw new ConfigurationError("Host " + entry.name()); 92 } 93 } 94 95 return ret; 96 } 97 98 private Map<String, HostedRepository> parseRepositories(JSONObject config) throws ConfigurationError { 99 Map<String, HostedRepository> ret = new HashMap<>(); 100 101 if (!config.contains("repositories")) { 102 return ret; 103 } 104 105 for (var entry : config.get("repositories").fields()) { 106 var hostName = entry.value().get("host").asString(); 107 if (!hosts.containsKey(hostName)) { 108 throw new ConfigurationError("Repository " + entry.name() + " uses undefined host '" + hostName + "'"); 109 } 110 var host = hosts.get(hostName); 111 var repo = host.getRepository(entry.value().get("repository").asString()); 112 ret.put(entry.name(), repo); 113 } 114 115 return ret; 116 } 117 118 private HostedRepository getRepository(String name) throws ConfigurationError { 119 if (!repositories.containsKey(name)) { 120 throw new ConfigurationError("Repository " + name + " is not defined!"); 121 } 122 return repositories.get(name); 123 } 124 125 public static BotRunnerConfiguration parse(JSONObject config, Path cwd) throws ConfigurationError { 126 return new BotRunnerConfiguration(config, cwd); 127 } 128 129 public static BotRunnerConfiguration parse(JSONObject config) throws ConfigurationError { 130 return parse(config, Paths.get(".")); 131 } 132 133 public BotConfiguration perBotConfiguration(String botName) throws ConfigurationError { 134 135 if (!config.contains(botName)) { 136 throw new ConfigurationError("No configuration for bot name: " + botName); 137 } 138 139 return new BotConfiguration() { 140 @Override 141 public Path storageFolder() { 142 if (!config.contains("storage") || !config.get("storage").contains("path")) { 143 try { 144 return Files.createTempDirectory("storage-" + botName); 145 } catch (IOException e) { 146 throw new UncheckedIOException(e); 147 } 148 } 149 return Paths.get(config.get("storage").get("path").asString()).resolve(botName); 150 } 151 152 @Override 153 public HostedRepository repository(String name) { 154 try { 155 return getRepository(name); 156 } catch (ConfigurationError configurationError) { 157 throw new RuntimeException("Couldn't find repository with name: " + name, configurationError); 158 } 159 } 160 161 @Override 162 public JSONObject specific() { 163 return config.get(botName).asObject(); 164 } 165 }; 166 } 167 168 /** 169 * The amount of time to wait between each invocation of Bot.getPeriodicItems. 170 * @return 171 */ 172 Duration scheduledExecutionPeriod() { 173 if (!config.contains("runner") || !config.get("runner").contains("interval")) { 174 log.info("No WorkItem invocation period defined, using default value"); 175 return Duration.ofSeconds(10); 176 } else { 177 return Duration.parse(config.get("runner").get("interval").asString()); 178 } 179 } 180 181 /** 182 * Number of WorkItems to execute in parallel. 183 * @return 184 */ 185 Integer concurrency() { 186 if (!config.contains("runner") || !config.get("runner").contains("concurrency")) { 187 log.info("WorkItem concurrency not defined, using default value"); 188 return 2; 189 } else { 190 return config.get("runner").get("concurrency").asInt(); 191 } 192 } 193 194 /** 195 * Folder that WorkItems may use to store temporary data. 196 * @return 197 */ 198 Path scratchFolder() { 199 if (!config.contains("scratch") || !config.get("scratch").contains("path")) { 200 try { 201 log.warning("No scratch folder defined, creating a temporary folder"); 202 return Files.createTempDirectory("botrunner"); 203 } catch (IOException e) { 204 throw new UncheckedIOException(e); 205 } 206 } 207 return Paths.get(config.get("scratch").get("path").asString()); 208 } 209 210 Optional<Integer> restReceiverPort() { 211 if (!config.contains("webhooks")) { 212 return Optional.empty(); 213 } 214 return Optional.of(config.get("webhooks").get("port").asInt()); 215 } 216 }