From 9640d966b034584fc4e4487b3678fe8606adc195 Mon Sep 17 00:00:00 2001 From: kohsuke Date: Sat, 27 Sep 2008 17:21:40 +0000 Subject: [PATCH] implemented a menu item to install a slave as a windows service git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@12406 71c3de6d-444a-0410-be80-ed276b4c234a --- .../lifecycle/WindowsInstallerLink.java | 2 +- .../lifecycle/WindowsSlaveInstaller.java | 141 ++++++++++++++++++ .../java/hudson/slaves/SlaveComputer.java | 3 +- .../hudson/lifecycle/Messages.properties | 1 + .../SlaveComputer/slave-agent.jnlp.jelly | 1 + .../windows-service/hudson-slave.xml | 17 +++ .../main/resources/windows-service/hudson.xml | 1 - .../java/hudson/remoting/jnlp/MainDialog.java | 13 ++ .../java/hudson/remoting/jnlp/MainMenu.java | 5 +- 9 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java create mode 100644 core/src/main/resources/hudson/lifecycle/Messages.properties create mode 100644 core/src/main/resources/windows-service/hudson-slave.xml diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java index b1295029a3..967f04b8b8 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java +++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java @@ -54,7 +54,7 @@ public class WindowsInstallerLink extends ManagementLink { } public String getDisplayName() { - return "Install as Windows Service"; + return Messages.WindowsInstallerLink_DisplayName(); } public String getDescription() { diff --git a/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java b/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java new file mode 100644 index 0000000000..75e22b1544 --- /dev/null +++ b/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java @@ -0,0 +1,141 @@ +package hudson.lifecycle; + +import hudson.FilePath; +import hudson.Launcher.LocalLauncher; +import hudson.remoting.Callable; +import hudson.remoting.Engine; +import hudson.remoting.jnlp.MainDialog; +import hudson.remoting.jnlp.MainMenu; +import hudson.util.StreamTaskListener; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.ByteArrayOutputStream; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; + +/** + * @author Kohsuke Kawaguchi + */ +public class WindowsSlaveInstaller implements Callable, ActionListener { + /** + * Root directory of this slave. + * String, not File because the platform can be different. + */ + private final String rootDir; + + private transient Engine engine; + private transient MainDialog dialog; + + public WindowsSlaveInstaller(String rootDir) { + this.rootDir = rootDir; + } + + public Void call() { + if(File.separatorChar=='/') return null; // not Windows + if(System.getProperty("hudson.showWindowsServiceInstallLink")==null) + return null; // only show this when it makes sense, which is when we run from JNLP + + dialog = MainDialog.get(); + if(dialog==null) return null; // can't find the main window. Maybe not running with GUI + + // capture the engine + engine = Engine.current(); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + MainMenu mainMenu = dialog.getMainMenu(); + JMenu m = mainMenu.getFileMenu(); + JMenuItem menu = new JMenuItem(Messages.WindowsInstallerLink_DisplayName(), KeyEvent.VK_W); + menu.addActionListener(WindowsSlaveInstaller.this); + m.add(menu); + mainMenu.commit(); + } + }); + + return null; + } + + /** + * Called when the install menu is selected + */ + public void actionPerformed(ActionEvent e) { + int r = JOptionPane.showConfirmDialog(dialog, + "This will install a slave agent as a Windows service,\n" + + "so that this slave will connect to Hudson as soon as the machine boots.\n" + + "Do you want to proceed with installation?", + Messages.WindowsInstallerLink_DisplayName(), + JOptionPane.OK_CANCEL_OPTION); + if(r!=JOptionPane.OK_OPTION) return; + + final File dir = new File(rootDir); + + + try { + final File slaveExe = new File(dir, "hudson-slave.exe"); + FileUtils.copyURLToFile(getClass().getResource("/windows-service/hudson.exe"), slaveExe); + + // write out the descriptor + String xml = IOUtils.toString(getClass().getResourceAsStream("/windows-service/hudson-slave.xml"), "UTF-8"); + xml = xml.replace("@JAVA@",System.getProperty("java.home")+"\\bin\\java.exe"); + URL jnlp = new URL(new URL(engine.hudsonUrl),"../computer/"+engine.slaveName+"/slave-agent.jnlp"); + xml = xml.replace("@URL@",jnlp.toExternalForm()); + FileUtils.writeStringToFile(new File(dir, "hudson-slave.xml"),xml,"UTF-8"); + + // copy slave.jar + URL slaveJar = new URL(new URL(engine.hudsonUrl),"../jnlpJars/remoting.jar"); + File dstSlaveJar = new File(dir,"slave.jar").getCanonicalFile(); + if(!dstSlaveJar.exists()) // perhaps slave.jar is already there? + FileUtils.copyURLToFile(slaveJar,dstSlaveJar); + + // install as a service + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + StreamTaskListener task = new StreamTaskListener(baos); + r = new LocalLauncher(task).launch(new String[]{slaveExe.getPath(), "install"}, new String[0], task.getLogger(), new FilePath(dir)).join(); + if(r!=0) { + JOptionPane.showMessageDialog( + dialog,baos.toString(),"Error", + JOptionPane.ERROR_MESSAGE); + return; + } + + r = JOptionPane.showConfirmDialog(dialog, + "Installation was successful. Would you like to\n" + + "Stop this slave agent and start the newly installed service?", + Messages.WindowsInstallerLink_DisplayName(), + JOptionPane.OK_CANCEL_OPTION); + if(r!=JOptionPane.OK_OPTION) return; + + // let the service start after we close our connection, to avoid conflicts + Runtime.getRuntime().addShutdownHook(new Thread("service starter") { + public void run() { + try { + StreamTaskListener task = new StreamTaskListener(System.out); + int r = new LocalLauncher(task).launch(new String[]{slaveExe.getPath(), "start"}, new String[0], task.getLogger(), new FilePath(dir)).join(); + task.getLogger().println(r==0?"Successfully started":"start service failed. Exit code="+r); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + System.exit(0); + } catch (Exception t) { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + JOptionPane.showMessageDialog( + dialog,sw.toString(),"Error", + JOptionPane.ERROR_MESSAGE); + } + } + + private static final long serialVersionUID = 1L; +} diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index a664ac9482..b1e5ee767d 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -9,6 +9,7 @@ import hudson.util.StreamTaskListener; import hudson.util.NullStream; import hudson.util.RingBufferLogHandler; import hudson.FilePath; +import hudson.lifecycle.WindowsSlaveInstaller; import hudson.maven.agent.Main; import hudson.maven.agent.PluginManagerInterceptor; @@ -201,8 +202,8 @@ public final class SlaveComputer extends Computer { Boolean _isUnix = channel.call(new DetectOS()); log.println(_isUnix? hudson.model.Messages.Slave_UnixSlave():hudson.model.Messages.Slave_WindowsSlave()); - // install log handler channel.call(new LogInstaller()); + channel.call(new WindowsSlaveInstaller(getNode().getRemoteFS())); // update the data structure atomically to prevent others from seeing a channel that's not properly initialized yet synchronized(channelLock) { diff --git a/core/src/main/resources/hudson/lifecycle/Messages.properties b/core/src/main/resources/hudson/lifecycle/Messages.properties new file mode 100644 index 0000000000..adbcdae0d2 --- /dev/null +++ b/core/src/main/resources/hudson/lifecycle/Messages.properties @@ -0,0 +1 @@ +WindowsInstallerLink.DisplayName=Install as Windows Service \ No newline at end of file diff --git a/core/src/main/resources/hudson/slaves/SlaveComputer/slave-agent.jnlp.jelly b/core/src/main/resources/hudson/slaves/SlaveComputer/slave-agent.jnlp.jelly index 221b8fdd31..1dbe282fd8 100644 --- a/core/src/main/resources/hudson/slaves/SlaveComputer/slave-agent.jnlp.jelly +++ b/core/src/main/resources/hudson/slaves/SlaveComputer/slave-agent.jnlp.jelly @@ -33,6 +33,7 @@ + diff --git a/core/src/main/resources/windows-service/hudson-slave.xml b/core/src/main/resources/windows-service/hudson-slave.xml new file mode 100644 index 0000000000..c0ff8f03cf --- /dev/null +++ b/core/src/main/resources/windows-service/hudson-slave.xml @@ -0,0 +1,17 @@ + + + hudsonslave + Hudson Slave + This service runs a slave for Hudson continous integration system. + + @JAVA@ + -jar "%BASE%\slave.jar" -jnlpUrl @URL@ + diff --git a/core/src/main/resources/windows-service/hudson.xml b/core/src/main/resources/windows-service/hudson.xml index 39abf06cfb..edfe91ff1b 100644 --- a/core/src/main/resources/windows-service/hudson.xml +++ b/core/src/main/resources/windows-service/hudson.xml @@ -15,5 +15,4 @@ --> java -Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar "%BASE%\hudson.war" --httpPort=8080 - diff --git a/remoting/src/main/java/hudson/remoting/jnlp/MainDialog.java b/remoting/src/main/java/hudson/remoting/jnlp/MainDialog.java index bcbb22a370..b0891f3e14 100644 --- a/remoting/src/main/java/hudson/remoting/jnlp/MainDialog.java +++ b/remoting/src/main/java/hudson/remoting/jnlp/MainDialog.java @@ -1,5 +1,7 @@ package hudson.remoting.jnlp; +import hudson.remoting.Engine; + import javax.swing.*; import java.awt.*; @@ -62,4 +64,15 @@ public class MainDialog extends JFrame { public void status(String msg) { statusLabel.setText(msg); } + + /** + * If the current JVM runs a {@link MainDialog} as a JNLP slave agent, + * return its reference, otherwise null. + */ + public static MainDialog get() { + Engine e = Engine.current(); + if(e==null) return null; + if (!(e.listener instanceof GuiListener)) return null; + return ((GuiListener) e.listener).frame; + } } diff --git a/remoting/src/main/java/hudson/remoting/jnlp/MainMenu.java b/remoting/src/main/java/hudson/remoting/jnlp/MainMenu.java index 74edfb26b4..9a8f087b51 100644 --- a/remoting/src/main/java/hudson/remoting/jnlp/MainMenu.java +++ b/remoting/src/main/java/hudson/remoting/jnlp/MainMenu.java @@ -37,8 +37,9 @@ public final class MainMenu extends JMenuBar { repaint(); if(getComponentCount()>0) { owner.setJMenuBar(this); - owner.invalidate(); - owner.repaint(); + // work around for paint problem. see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4949810 + if(owner.isVisible()) + owner.setVisible(true); } } } -- GitLab