modules/cli-tests/src/test/resources/test-ant-bundle-v1.xml | 4 modules/cli-tests/src/test/resources/test-ant-bundle-v2.xml | 4 modules/common/ant-bundle/pom.xml | 6 modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/AntLauncher.java | 13 modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/BundleTask.java | 2 modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/InstallSystemServiceTask.java | 333 ++++++++++ modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml | 4 modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java | 8 modules/common/ant-bundle/src/test/resources/foo-config | 3 modules/common/ant-bundle/src/test/resources/foo-script | 24 modules/common/ant-bundle/src/test/resources/test-bundle-v1.xml | 9 modules/common/ant-bundle/src/test/resources/test-bundle-v2.xml | 2 modules/plugins/ant-bundle/src/test/resources/test-build.xml | 4 13 files changed, 398 insertions(+), 18 deletions(-)
New commits: commit a40874a5be03f72f9099b6e8a3215083d24b512b Author: Ian P. Springer <ips@jetengine.(none)> Date: Wed Apr 28 15:33:02 2010 -0400
add a new rhq:install-system-service task for installing Unix init scripts (currently only supports RH Linux); rename rhq:inputProperty task to rhq-input-property to follow our task naming convention
diff --git a/modules/cli-tests/src/test/resources/test-ant-bundle-v1.xml b/modules/cli-tests/src/test/resources/test-ant-bundle-v1.xml index ce580b6..e874c63 100644 --- a/modules/cli-tests/src/test/resources/test-ant-bundle-v1.xml +++ b/modules/cli-tests/src/test/resources/test-ant-bundle-v1.xml @@ -4,13 +4,13 @@ <rhq:bundle name="example.com (JBoss EAP 4.3)" version="1.0" description="example.com corporate website hosted on JBoss EAP 4.3"/>
- <rhq:inputProperty + <rhq:input-property name="http.port" description="the HTTP port the JBoss EAP server should listen on" required="true" type="integer"/>
- <rhq:inputProperty + <rhq:input-property name="https.port" description="the HTTPS port the JBoss EAP server should listen on" required="true" diff --git a/modules/cli-tests/src/test/resources/test-ant-bundle-v2.xml b/modules/cli-tests/src/test/resources/test-ant-bundle-v2.xml index db23396..b825a0f 100644 --- a/modules/cli-tests/src/test/resources/test-ant-bundle-v2.xml +++ b/modules/cli-tests/src/test/resources/test-ant-bundle-v2.xml @@ -4,13 +4,13 @@ <rhq:bundle name="example.com (JBoss EAP 4.3)" version="2.0" description="example.com corporate website hosted on JBoss EAP 4.3"/>
- <rhq:inputProperty + <rhq:input-property name="http.port" description="the HTTP port the JBoss EAP server should listen on" required="true" type="integer"/>
- <rhq:inputProperty + <rhq:input-property name="https.port" description="the HTTPS port the JBoss EAP server should listen on" required="true" diff --git a/modules/common/ant-bundle/pom.xml b/modules/common/ant-bundle/pom.xml index 6349d81..d7b9e91 100644 --- a/modules/common/ant-bundle/pom.xml +++ b/modules/common/ant-bundle/pom.xml @@ -74,6 +74,12 @@ <version>1.8.0</version> </dependency>
+ <dependency> + <groupId>org.apache.ant</groupId> + <artifactId>ant-nodeps</artifactId> + <version>1.8.0</version> + </dependency> + <!-- include some optional ant tasks for users to be able to use --> <dependency> <groupId>ant-contrib</groupId> diff --git a/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/AntLauncher.java b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/AntLauncher.java index b29e5e9..e8391b7 100644 --- a/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/AntLauncher.java +++ b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/AntLauncher.java @@ -43,6 +43,8 @@ import org.rhq.bundle.ant.task.InputPropertyTask; * @author Ian Springer */ public class AntLauncher { + private final Log log = LogFactory.getLog(this.getClass()); + // "out of box" we will provide the ant contrib optional tasks (from ant-contrib.jar) private static final String ANTCONTRIB_ANT_TASKS = "net/sf/antcontrib/antcontrib.properties";
@@ -52,7 +54,10 @@ public class AntLauncher { // private constant ProjectHelper2.REFID_CONTEXT value private static final String REFID_CONTEXT = "ant.parsing.context";
- private final Log log = LogFactory.getLog(AntLauncher.class); + // TODO (ips, 04/28/10): Figure out a way to avoid assuming the prefix is "rhq". + private static final String BUNDLE_TASK_NAME = "rhq:bundle"; + private static final String INPUT_PROPERTY_TASK_NAME = "rhq:input-property"; + private static final String DEPLOY_TASK_NAME = "rhq:deploy";
/** * Executes the specified bundle deploy Ant build file (i.e. rhq-deploy.xml). @@ -190,14 +195,14 @@ public class AntLauncher { Task[] tasks = target.getTasks(); for (Task task : tasks) { // NOTE: For rhq:inputProperty tasks, the below call will add propDefs to the project configDef. - if (task.getTaskName().equals("rhq:bundle")) { + if (task.getTaskName().equals(BUNDLE_TASK_NAME)) { abortIfTaskWithinTarget(target, task); bundleTaskCount++; unconfiguredBundleTask = task; - } else if (task.getTaskName().equals("rhq:inputProperty")) { + } else if (task.getTaskName().equals(INPUT_PROPERTY_TASK_NAME)) { abortIfTaskWithinTarget(target, task); InputPropertyTask inputPropertyTask = (InputPropertyTask) preconfigureTask(task); - } else if (task.getTaskName().equals("rhq:deploy")) { + } else if (task.getTaskName().equals(DEPLOY_TASK_NAME)) { DeployTask deployTask = (DeployTask) preconfigureTask(task); Map<File, File> files = deployTask.getFiles(); for (File file : files.values()) { diff --git a/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/BundleTask.java b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/BundleTask.java index 3d863ab..61d8898 100644 --- a/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/BundleTask.java +++ b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/BundleTask.java @@ -28,7 +28,7 @@ import java.util.Hashtable; /** * @author Ian Springer */ -public class BundleTask extends AbstractBundleTask { +public class BundleTask extends AbstractBundleTask { private String name; private String version; private String description; diff --git a/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/InstallSystemServiceTask.java b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/InstallSystemServiceTask.java new file mode 100644 index 0000000..828c39d --- /dev/null +++ b/modules/common/ant-bundle/src/main/java/org/rhq/bundle/ant/task/InstallSystemServiceTask.java @@ -0,0 +1,333 @@ +/* + * RHQ Management Platform + * Copyright (C) 2010 Red Hat, Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package org.rhq.bundle.ant.task; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Chmod; +import org.apache.tools.ant.taskdefs.Copy; +import org.apache.tools.ant.taskdefs.optional.unix.Symlink; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +/** + * An Ant task that installs a system startup/shutdown service. Currently only Red Hat Linux versions are supported. + * + * @author Ian Springer + */ +public class InstallSystemServiceTask extends Task { + private static final String OS_NAME = System.getProperty("os.name"); + private static final File REDHAT_RELEASE_FILE = new File("/etc/redhat-release"); + private static final Set<Character> REDHAT_RUN_LEVELS = new HashSet<Character>(); + static { + for (char c = '0'; c <= '6'; c++) { + REDHAT_RUN_LEVELS.add(c); + } + // TODO: Add 's' and/or 'S' depending on the flavor of UNIX. + } + private static final File INIT_DIR = new File("/etc/init.d"); + private static final File SYSCONFIG_DIR = new File("/etc/sysconfig"); + private static final File DEFAULT_ROOT = new File("/"); + + private String name; + private File scriptFile; + private File configFile; + private boolean overwriteScript; + private boolean overwriteConfig; + private boolean overwriteLinks = true; + private File root = DEFAULT_ROOT; + private String startLevels; + + /** + * An integer from 0-99 indicating the service's start order - services with a lower priority number are started + * before services with a higher priority number. + */ + private Byte startPriority; + + /** + * An integer from 0-99 indicating the service's stop order - services with a lower priority number are stopped + * before services with a higher priority number. + */ + private Byte stopPriority; + + private Set<Character> startLevelChars; + private Set<Character> stopLevelChars; + + @Override + public void execute() throws BuildException { + if (!OS_NAME.equals("Linux") || !REDHAT_RELEASE_FILE.exists() ) { + throw new BuildException("This task can only be run on Red Hat Linux systems."); + } + validateAttributes(); + + // Install the config file if one was provided (e.g. /etc/sysconfig/named). + if (this.configFile != null) { + File sysconfigDir = new File(this.root, SYSCONFIG_DIR.getPath().substring(1)); + if (!sysconfigDir.exists()) { + sysconfigDir.mkdirs(); + } + if (!sysconfigDir.canWrite()) { + throw new BuildException(sysconfigDir + " directory is not writeable."); + } + File configDestFile = new File(sysconfigDir, this.name); + copyFile(this.configFile, configDestFile, this.overwriteConfig); + setPermissions(configDestFile, "644"); + } + + // Install the script itself (e.g. /etc/init.d/named). + File initDir = new File(this.root, INIT_DIR.getPath().substring(1)); + if (!initDir.exists()) { + initDir.mkdirs(); + } + if (!initDir.canWrite()) { + throw new BuildException(initDir + " directory is not writeable."); + } + File scriptDestFile = new File(initDir, this.name); + getProject().log("Installing service script " + scriptDestFile + "..."); + copyFile(this.scriptFile, scriptDestFile, this.overwriteScript); + setPermissions(scriptDestFile, "755"); + + // Create the symlinks in the rcX.d dirs (e.g. /etc/rc3.d/S24named -> ../init.d/named) + createScriptSymlinks(scriptDestFile, this.startPriority, this.startLevelChars, 'S'); + createScriptSymlinks(scriptDestFile, this.stopPriority, this.stopLevelChars, 'K'); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public File getScriptFile() { + return scriptFile; + } + + public void setScriptFile(File scriptFile) { + this.scriptFile = scriptFile; + } + + public File getConfigFile() { + return configFile; + } + + public void setConfigFile(File configFile) { + this.configFile = configFile; + } + + public boolean isOverwriteScript() { + return overwriteScript; + } + + public void setOverwriteScript(boolean overwriteScript) { + this.overwriteScript = overwriteScript; + } + + public boolean isOverwriteConfig() { + return overwriteConfig; + } + + public void setOverwriteConfig(boolean overwriteConfig) { + this.overwriteConfig = overwriteConfig; + } + + public boolean isOverwriteLinks() { + return overwriteLinks; + } + + public void setOverwriteLinks(boolean overwriteLinks) { + this.overwriteLinks = overwriteLinks; + } + + public String getStartLevels() { + return startLevels; + } + + public void setStartLevels(String startLevels) { + this.startLevels = startLevels; + } + + public byte getStartPriority() { + return startPriority; + } + + public void setStartPriority(byte startPriority) { + this.startPriority = startPriority; + } + + public byte getStopPriority() { + return stopPriority; + } + + public void setStopPriority(byte stopPriority) { + this.stopPriority = stopPriority; + } + + public File getRoot() { + return root; + } + + public void setRoot(File root) { + this.root = root; + } + + /** + * Ensure we have a consistent and legal set of attributes, and set + * any internal flags necessary based on different combinations + * of attributes. + * + * @throws BuildException if an error occurs + */ + protected void validateAttributes() throws BuildException { + if (this.name == null) { + throw new BuildException("The 'name' attribute is required."); + } + if (this.name.length() == 0) { + throw new BuildException("The 'name' attribute must have a non-empty value."); + } + + if (this.scriptFile == null) { + throw new BuildException("The 'scriptFile' attribute is required."); + } + if (!this.scriptFile.exists() || this.scriptFile.isDirectory()) { + throw new BuildException("The 'scriptFile' attribute must be set to the path of an existing regular file."); + } + if (!this.configFile.exists() || this.configFile.isDirectory()) { + throw new BuildException("The 'configFile' attribute must be set to the path of an existing regular file."); + } + + if (this.startLevels == null) { + throw new BuildException("The 'startLevels' attribute is required."); + } + if (this.startLevels.length() == 0) { + throw new BuildException("The 'startLevels' attribute must have a non-empty value."); + } + this.startLevelChars = parseLevels(this.startLevels); + this.stopLevelChars = new TreeSet<Character>(); + for (char level : REDHAT_RUN_LEVELS) { + if (!this.startLevelChars.contains(level)) { + this.stopLevelChars.add(level); + } + } + + if (this.startPriority == null) { + throw new BuildException("The 'startPriority' attribute is required."); + } + if (this.startPriority < 0 || this.startPriority > 99) { + throw new BuildException("The 'startPriority' attribute must be >=0 and <= 99."); + } + if (this.stopPriority == null) { + throw new BuildException("The 'stopPriority' attribute is required."); + } + if (this.stopPriority < 0 || this.stopPriority > 99) { + throw new BuildException("The 'startPriority' attribute must be >=0 and <= 99."); + } + + if (!this.root.isDirectory()) { + throw new BuildException("The 'root' attribute must be set to the path of an existing directory."); + } + if (!this.root.equals(DEFAULT_ROOT)) { + getProject().log("Using root " + this.root + "."); + } + } + + private static Set<Character> parseLevels(String levels) { + Set<Character> levelChars = new TreeSet<Character>(); + String[] tokens = levels.split("[ ]*,[ ]*"); + for (String token : tokens) { + if (!token.equals("")) { + Character level; + try { + if (token.length() != 1) { + throw new Exception(); + } + level = token.charAt(0); + if (!REDHAT_RUN_LEVELS.contains(level)) { + throw new Exception(); + } + + } catch (Exception e) { + throw new BuildException("Invalid run level: " + token + + " - the 'startLevels' attribute must be a comma-separated list of run levels - the valid levels are " + + REDHAT_RUN_LEVELS + "."); + } + if (levelChars.contains(level)) { + throw new BuildException("The 'startLevels' attribute defines run level " + level + " more than once."); + } + levelChars.add(level); + } + } + return levelChars; + } + + private void createScriptSymlinks(File scriptFile, byte priority, Set<Character> levels, char fileNamePrefix) { + String priorityString = String.format("%02d", priority); + for (char level : levels) { + File rcDir = new File(this.root, "etc/rc" + level + ".d"); + if (!rcDir.exists()) { + rcDir.mkdirs(); + } + if (!rcDir.exists()) { + throw new BuildException(rcDir + " does not exist."); + } + if (!rcDir.isDirectory()) { + throw new BuildException(rcDir + " exists but is not a directory."); + } + if (!rcDir.isDirectory()) { + throw new BuildException(rcDir + " directory is not writeable."); + } + File link = new File(rcDir, fileNamePrefix + priorityString + this.name); + getProject().log("Creating symbolic link " + link + " referencing " + scriptFile + "..."); + + createSymlink(scriptFile, link, this.overwriteLinks); + } + } + + private void copyFile(File sourceFile, File destFile, boolean overwrite) { + Copy copyTask = new Copy(); + copyTask.setProject(getProject()); + copyTask.init(); + copyTask.setFile(sourceFile); + copyTask.setTofile(destFile); + copyTask.setOverwrite(overwrite); + copyTask.execute(); + } + + private void createSymlink(File targetFile, File linkFile, boolean overwrite) { + Symlink symlinkTask = new Symlink(); + symlinkTask.setProject(getProject()); + symlinkTask.init(); + symlinkTask.setResource(targetFile.getAbsolutePath()); + symlinkTask.setLink(linkFile.getAbsolutePath()); + symlinkTask.setOverwrite(overwrite); + symlinkTask.execute(); + } + + private void setPermissions(File file, String perms) { + Chmod chmodTask = new Chmod(); + chmodTask.setProject(getProject()); + chmodTask.init(); + chmodTask.setFile(file); + chmodTask.setPerm(perms); + chmodTask.execute(); + } +} diff --git a/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml b/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml index 0a552d2..b522491 100644 --- a/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml +++ b/modules/common/ant-bundle/src/main/resources/org/rhq/bundle/antlib.xml @@ -7,7 +7,7 @@ <taskdef name="bundle" classname="org.rhq.bundle.ant.task.BundleTask"/>
<!-- inputProperty task --> - <taskdef name="inputProperty" classname="org.rhq.bundle.ant.task.InputPropertyTask"/> + <taskdef name="input-property" classname="org.rhq.bundle.ant.task.InputPropertyTask"/>
<!-- deploy task and its child types --> <taskdef name="deploy" classname="org.rhq.bundle.ant.task.DeployTask"/> @@ -18,4 +18,6 @@ <typedef name="ignore" classname="org.rhq.bundle.ant.type.IgnoreType"/> <typedef name="fileset" classname="org.rhq.bundle.ant.type.FileSet"/>
+ <!-- install-system-service task --> + <taskdef name="install-system-service" classname="org.rhq.bundle.ant.task.InstallSystemServiceTask"/> </antlib> diff --git a/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java b/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java index 7235815..d5802e9 100644 --- a/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java +++ b/modules/common/ant-bundle/src/test/java/org/rhq/bundle/ant/AntLauncherTest.java @@ -60,8 +60,10 @@ public class AntLauncherTest { assert bundleFiles.get("pkg").equals("package.zip") : bundleFiles;*/
ConfigurationDefinition configDef = project.getConfigurationDefinition(); - assert configDef.getPropertyDefinitions().size() == 1; - assert configDef.getPropertyDefinitionSimple("listener.port") != null; + assert configDef.getPropertyDefinitions().size() == 1 : configDef.getPropertyDefinitions(); + PropertyDefinitionSimple propDef = configDef.getPropertyDefinitionSimple("listener.port"); + assert propDef != null; + assert propDef.getType() == PropertySimpleType.INTEGER; }
public void testInstall() throws Exception { @@ -81,7 +83,7 @@ public class AntLauncherTest { assert bundleFiles.get("pkg").equals("package.zip") : bundleFiles;*/
ConfigurationDefinition configDef = project.getConfigurationDefinition(); - assert configDef.getPropertyDefinitions().size() == 1; + assert configDef.getPropertyDefinitions().size() == 1 : configDef.getPropertyDefinitions(); PropertyDefinitionSimple propDef = configDef.getPropertyDefinitionSimple("listener.port"); assert propDef != null; assert propDef.getType() == PropertySimpleType.INTEGER; diff --git a/modules/common/ant-bundle/src/test/resources/foo-config b/modules/common/ant-bundle/src/test/resources/foo-config new file mode 100644 index 0000000..a5343be --- /dev/null +++ b/modules/common/ant-bundle/src/test/resources/foo-config @@ -0,0 +1,3 @@ +# sysconfig file for foo init script + +FOO=1 diff --git a/modules/common/ant-bundle/src/test/resources/foo-script b/modules/common/ant-bundle/src/test/resources/foo-script new file mode 100644 index 0000000..909ff79 --- /dev/null +++ b/modules/common/ant-bundle/src/test/resources/foo-script @@ -0,0 +1,24 @@ +#! /bin/bash + +# a sample init script + +# Set config defaults. +FOO=0 + +# Source config if it exists. +NAME=`basename $0` +CONFIG=/etc/sysconfig/$NAME +[ -f "$CONFIG" ] && . "$CONFIG" + +case "$1" in + *start) + echo "Starting foo (FOO=$FOO)..." + ;; + *stop) + echo "Stopping foo (FOO=$FOO)..." + ;; + *) + echo "Usage: $NAME {start|stop}" >&2 + exit 1 + ;; +esac diff --git a/modules/common/ant-bundle/src/test/resources/test-bundle-v1.xml b/modules/common/ant-bundle/src/test/resources/test-bundle-v1.xml index b5349c9..cf2c019 100644 --- a/modules/common/ant-bundle/src/test/resources/test-bundle-v1.xml +++ b/modules/common/ant-bundle/src/test/resources/test-bundle-v1.xml @@ -5,7 +5,7 @@ <rhq:bundle name="example.com (JBoss EAP 4.3)" version="1.0" description="example.com corporate website hosted on JBoss EAP 4.3"/>
- <rhq:inputProperty + <rhq:input-property name="listener.port" description="This is where the product will listen for incoming messages" required="true" @@ -14,6 +14,7 @@
<target name="deploy"> <echo>Deploying Test Bundle v1.0 to ${rhq.deploy.dir}...</echo> + rhq:deploy <rhq:file name="test-v1.properties" destinationFile="subdir/test.properties"/> <rhq:archive name="file.zip"/> @@ -23,7 +24,11 @@ rhq:ignore <rhq:fileset includes="*.log"/> </rhq:ignore> - </rhq:deploy> + </rhq:deploy> + + <mkdir dir="root"/> + <rhq:install-system-service name="foo" scriptFile="foo-script" configFile="foo-config" overwriteScript="true" + startLevels="3,4,5" startPriority="80" stopPriority="20" root="root"/> </target>
</project> \ No newline at end of file diff --git a/modules/common/ant-bundle/src/test/resources/test-bundle-v2.xml b/modules/common/ant-bundle/src/test/resources/test-bundle-v2.xml index 4c522f6..fd7ed44 100644 --- a/modules/common/ant-bundle/src/test/resources/test-bundle-v2.xml +++ b/modules/common/ant-bundle/src/test/resources/test-bundle-v2.xml @@ -5,7 +5,7 @@ <rhq:bundle name="example.com (JBoss EAP 4.3)" version="2.0" description="example.com corporate website hosted on JBoss EAP 4.3"/>
- <rhq:inputProperty + <rhq:input-property name="listener.port" description="This is where the product will listen for incoming messages" required="true" diff --git a/modules/plugins/ant-bundle/src/test/resources/test-build.xml b/modules/plugins/ant-bundle/src/test/resources/test-build.xml index 30a35f9..e4e062b 100644 --- a/modules/plugins/ant-bundle/src/test/resources/test-build.xml +++ b/modules/plugins/ant-bundle/src/test/resources/test-build.xml @@ -8,13 +8,13 @@ <rhq:bundle name="example.com (JBoss EAP 4.3)" version="1.0" description="example.com corporate website hosted on JBoss EAP 4.3"/>
- <rhq:inputProperty + <rhq:input-property name="custom.prop1" description="my prop 1" required="true" defaultValue="default 1"/>
- <rhq:inputProperty + <rhq:input-property name="custom.prop2" description="my prop 2" required="true"
rhq-commits@lists.fedorahosted.org