/* * Copyright 2016 ThoughtWorks, Inc. * * 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.thoughtworks.go.agent; import com.thoughtworks.go.agent.common.AgentBootstrapperBackwardCompatibility; import com.thoughtworks.go.agent.common.launcher.AgentProcessParent; import com.thoughtworks.go.agent.common.util.Downloader; import com.thoughtworks.go.agent.common.util.JarUtil; import com.thoughtworks.go.agent.launcher.DownloadableFile; import com.thoughtworks.go.agent.launcher.ServerBinaryDownloader; import com.thoughtworks.go.util.GoConstants; import com.thoughtworks.go.util.SslVerificationMode; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import static com.thoughtworks.go.agent.common.util.LoggingHelper.CONSOLE_NDC.STDERR; import static com.thoughtworks.go.agent.common.util.LoggingHelper.CONSOLE_NDC.STDOUT; import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.join; public class AgentProcessParentImpl implements AgentProcessParent { /* 40-50 for launcher error codes*/ private static final Log LOG = LogFactory.getLog(AgentProcessParentImpl.class); private static final int EXCEPTION_OCCURRED = -373; static final String AGENT_STARTUP_ARGS = "AGENT_STARTUP_ARGS"; static final String GO_AGENT_STDERR_LOG = "go-agent-stderr.log"; static final String GO_AGENT_STDOUT_LOG = "go-agent-stdout.log"; public int run(String launcherVersion, String launcherMd5, ServerUrlGenerator urlGenerator, Map<String, String> env, Map context) { int exitValue = 0; LOG.info("Agent is version: " + JarUtil.getGoVersion(Downloader.AGENT_BINARY)); String command[] = new String[]{}; try { AgentBootstrapperBackwardCompatibility backwardCompatibility = backwardCompatibility(context); File rootCertFile = backwardCompatibility.rootCertFile(); SslVerificationMode sslVerificationMode = backwardCompatibility.sslVerificationMode(); ServerBinaryDownloader agentDownloader = new ServerBinaryDownloader(urlGenerator, rootCertFile, sslVerificationMode); agentDownloader.downloadIfNecessary(DownloadableFile.AGENT); ServerBinaryDownloader pluginZipDownloader = new ServerBinaryDownloader(urlGenerator, rootCertFile, sslVerificationMode); pluginZipDownloader.downloadIfNecessary(DownloadableFile.AGENT_PLUGINS); ServerBinaryDownloader tfsImplDownloader = new ServerBinaryDownloader(urlGenerator, rootCertFile, sslVerificationMode); tfsImplDownloader.downloadIfNecessary(DownloadableFile.TFS_IMPL); command = agentInvocationCommand(agentDownloader.getMd5(), launcherMd5, pluginZipDownloader.getMd5(), tfsImplDownloader.getMd5(), env, context, agentDownloader.getSslPort()); LOG.info("Launching Agent with command: " + join(command, " ")); Process agent = invoke(command); // The next lines prevent the child process from blocking on Windows agent.getOutputStream().close(); AgentConsoleLogThread stdErrThd = new AgentConsoleLogThread(agent.getErrorStream(), STDERR, GO_AGENT_STDERR_LOG); stdErrThd.start(); AgentConsoleLogThread stdOutThd = new AgentConsoleLogThread(agent.getInputStream(), STDOUT, GO_AGENT_STDOUT_LOG); stdOutThd.start(); Shutdown shutdownHook = new Shutdown(agent); Runtime.getRuntime().addShutdownHook(shutdownHook); try { exitValue = agent.waitFor(); } catch (InterruptedException ie) { LOG.error("Agent was interrupted. Terminating agent and respawning. " + ie.toString()); agent.destroy(); } finally { removeShutdownHook(shutdownHook); stdErrThd.stopAndJoin(); stdOutThd.stopAndJoin(); } } catch (Exception e) { LOG.error("Exception while executing command: " + join(command, " ") + " - " + e.toString()); exitValue = EXCEPTION_OCCURRED; } return exitValue; } private AgentBootstrapperBackwardCompatibility backwardCompatibility(Map context) { return new AgentBootstrapperBackwardCompatibility(context); } private void removeShutdownHook(Shutdown shutdownHook) { try { Runtime.getRuntime().removeShutdownHook(shutdownHook); } catch (Exception e) { } } private String[] agentInvocationCommand(String agentMD5, String launcherMd5, String agentPluginsZipMd5, String tfsImplMd5, Map<String, String> env, Map context, @Deprecated String sslPort // the port is kept for backward compatibility to ensure that old bootstrappers are able to launch new agents ) { AgentBootstrapperBackwardCompatibility backwardCompatibility = backwardCompatibility(context); String startupArgsString = env.get(AGENT_STARTUP_ARGS); List<String> commandSnippets = new ArrayList<>(); commandSnippets.add(javaCmd()); if (!isEmpty(startupArgsString)) { String[] startupArgs = startupArgsString.split(" "); for (String startupArg : startupArgs) { String decodedStartupArg = startupArg.trim().replace("%20", " "); if (!isEmpty(decodedStartupArg)) { commandSnippets.add(decodedStartupArg); } } } commandSnippets.add(property(GoConstants.AGENT_PLUGINS_MD5, agentPluginsZipMd5)); commandSnippets.add(property(GoConstants.AGENT_JAR_MD5, agentMD5)); commandSnippets.add(property(GoConstants.GIVEN_AGENT_LAUNCHER_JAR_MD5, launcherMd5)); commandSnippets.add(property(GoConstants.TFS_IMPL_MD5, tfsImplMd5)); commandSnippets.add("-jar"); commandSnippets.add(Downloader.AGENT_BINARY); commandSnippets.add("-serverUrl"); commandSnippets.add(backwardCompatibility.sslServerUrl(sslPort)); if (backwardCompatibility.sslVerificationMode() != null) { commandSnippets.add("-sslVerificationMode"); commandSnippets.add(backwardCompatibility.sslVerificationMode().toString()); } if (backwardCompatibility.rootCertFileAsString() != null) { commandSnippets.add("-rootCertFile"); commandSnippets.add(backwardCompatibility.rootCertFileAsString()); } return commandSnippets.toArray(new String[]{}); } private String property(final String name, String value) { return "-D" + name + "=" + value; } private String javaCmd() { String javaHome = System.getProperty("java.home"); String pathSep = System.getProperty("file.separator"); return javaHome + pathSep + "bin" + pathSep + "java"; } Process invoke(String[] command) throws IOException { ProcessBuilder processBuilder = new ProcessBuilder(command); return processBuilder.start(); } private static class Shutdown extends Thread { private static final Log LOG = LogFactory.getLog(Shutdown.class); private final Process agent; public Shutdown(Process agent) { setName("Shutdown" + getName()); this.agent = agent; } public void run() { LOG.info("Shutdown hook invoked. Shutting down " + agent); agent.destroy(); } } }