diff --git a/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java b/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java index 981ae162b9..89d7e88d32 100644 --- a/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java @@ -25,6 +25,7 @@ package org.jclouds.initbuilder; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -33,6 +34,9 @@ import org.jclouds.initbuilder.domain.ShellToken; import org.jclouds.initbuilder.util.Utils; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** @@ -48,6 +52,9 @@ public class InitBuilder { @VisibleForTesting Map variables = Maps.newHashMap(); + @VisibleForTesting + List variablesToUnset = Lists.newArrayList("path", "javaHome", "libraryPath"); + /** * Adds a switch statement to the script. If its value is found, it will invoke the corresponding * action. @@ -73,6 +80,14 @@ public class InitBuilder { return this; } + /** + * Unsets a variable to ensure it is set within the script. + */ + public InitBuilder unsetEnvironmentVariable(String name) { + variablesToUnset.add(checkNotNull(name, "name")); + return this; + } + /** * Exports a variable inside the script */ @@ -92,15 +107,26 @@ public class InitBuilder { * @param osFamily * whether to write a cmd or bash script. */ - public String build(OsFamily osFamily) { + public String build(final OsFamily osFamily) { StringBuilder builder = new StringBuilder(); builder.append(ShellToken.SHEBANG.to(osFamily)); - builder.append(ShellToken.ZERO_PATH.to(osFamily)); + builder.append(Utils.writeUnsetVariables(Lists.newArrayList(Iterables.transform( + variablesToUnset, new Function() { + + @Override + public String apply(String from) { + if (ShellToken.tokenValueMap(osFamily).containsKey(from + "Variable")) + return Utils.FUNCTION_UPPER_UNDERSCORE_TO_LOWER_CAMEL.apply(ShellToken + .tokenValueMap(osFamily).get(from + "Variable")); + return from; + } + + })), osFamily)); + builder.append(Utils.writeZeroPath(osFamily)); builder.append(Utils.writeVariableExporters(variables, osFamily)); for (Entry> entry : switchExec.entrySet()) { builder.append(Utils.writeSwitch(entry.getKey(), entry.getValue(), osFamily)); } return builder.toString(); } - } \ No newline at end of file diff --git a/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java b/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java index dacacc6577..6006ea9a3c 100644 --- a/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java @@ -33,13 +33,13 @@ import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; /** - * Constants used in operating suy + * Constants used in shell scripting. * * @author Adrian Cole */ public enum ShellToken { - FS, PS, LF, SH, SOURCE, REM, ARGS, VARSTART, VAREND, SHEBANG, ZERO_PATH, EXE; + FS, PS, LF, SH, SOURCE, REM, ARGS, VARSTART, VAREND, SHEBANG, LIBRARY_PATH_VARIABLE; private static final Map> familyToTokenValueMap = new MapMaker() .makeComputingMap(new Function>() { @@ -91,6 +91,13 @@ public enum ShellToken { case UNIX: return "bash"; } + case LIBRARY_PATH_VARIABLE: + switch (family) { + case WINDOWS: + return "PATH"; + case UNIX: + return "LD_LIBRARY_PATH"; + } case SOURCE: switch (family) { case WINDOWS: @@ -133,20 +140,7 @@ public enum ShellToken { case UNIX: return "#!/bin/bash\n"; } - case ZERO_PATH: - switch (family) { - case WINDOWS: - return "set PATH=c:\\windows\\;C:\\windows\\system32\r\n"; - case UNIX: - return "export PATH=/usr/ucb/bin:/bin:/usr/bin:/usr/sbin\n"; - } - case EXE: - switch (family) { - case WINDOWS: - return ".exe"; - case UNIX: - return ""; - } + default: throw new UnsupportedOperationException("token " + this + " not configured"); } diff --git a/initbuilder/src/main/java/org/jclouds/initbuilder/util/Utils.java b/initbuilder/src/main/java/org/jclouds/initbuilder/util/Utils.java index 665a1c9e56..0c9066a044 100644 --- a/initbuilder/src/main/java/org/jclouds/initbuilder/util/Utils.java +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/util/Utils.java @@ -23,6 +23,7 @@ */ package org.jclouds.initbuilder.util; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; @@ -31,7 +32,10 @@ import java.util.regex.Pattern; import org.jclouds.initbuilder.domain.OsFamily; import com.google.common.base.CaseFormat; +import com.google.common.base.Function; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; /** * Utilities used to build init scripts. @@ -40,7 +44,25 @@ import com.google.common.collect.ImmutableMap; */ public class Utils { - public static final Pattern pattern = Pattern.compile("\\{(.+?)\\}"); + public static final LowerCamelToUpperUnderscore FUNCTION_LOWER_CAMEL_TO_UPPER_UNDERSCORE = new LowerCamelToUpperUnderscore(); + + public static final class LowerCamelToUpperUnderscore implements Function { + @Override + public String apply(String from) { + return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, from); + } + } + + public static final UpperUnderscoreToLowerCamel FUNCTION_UPPER_UNDERSCORE_TO_LOWER_CAMEL = new UpperUnderscoreToLowerCamel(); + + public static final class UpperUnderscoreToLowerCamel implements Function { + @Override + public String apply(String from) { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, from); + } + } + + private static final Pattern pattern = Pattern.compile("\\{(.+?)\\}"); /** * replaces tokens that are expressed as {token} @@ -98,6 +120,70 @@ public class Utils { return initializers.toString(); } + public static final Map OS_TO_POSITIONAL_VAR_PATTERN = ImmutableMap.of( + OsFamily.UNIX, "set {key}=$1\nshift\n", OsFamily.WINDOWS, "set {key}=%1\r\nshift\r\n"); + + public static final Map OS_TO_LOCAL_VAR_PATTERN = ImmutableMap.of( + OsFamily.UNIX, "set {key}=\"{value}\"\n", OsFamily.WINDOWS, "set {key}={value}\r\n"); + + /** + * Writes an initialization statement for use inside a script or a function. + * + * @param positionalVariablesInLowerCamelCase + * - transfer the value of args into these statements. Note that there is no check to + * ensure that all source args are indeed present. + */ + public static String writePositionalVars(List positionalVariablesInLowerCamelCase, + OsFamily family) { + StringBuilder initializers = new StringBuilder(); + for (String variableInLowerCamelCase : positionalVariablesInLowerCamelCase) { + String key = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, + variableInLowerCamelCase); + initializers.append(replaceTokens(OS_TO_POSITIONAL_VAR_PATTERN.get(family), ImmutableMap + .of("key", key))); + } + return initializers.toString(); + } + + /** + * Ensures that variables come from a known source instead of bleeding in from a profile + * + * @param variablesInLowerCamelCase + * - System variables to unset + */ + public static String writeUnsetVariables(List variablesInLowerCamelCase, OsFamily family) { + switch (family) { + case UNIX: + return String.format("unset %s\n", Joiner.on(' ').join( + Iterables.transform(variablesInLowerCamelCase, + FUNCTION_LOWER_CAMEL_TO_UPPER_UNDERSCORE))); + case WINDOWS: + StringBuilder initializers = new StringBuilder(); + for (String variableInLowerCamelCase : variablesInLowerCamelCase) { + String key = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, + variableInLowerCamelCase); + initializers.append(replaceTokens(OS_TO_LOCAL_VAR_PATTERN.get(family), ImmutableMap + .of("key", key, "value", ""))); + } + return initializers.toString(); + default: + throw new UnsupportedOperationException("unsupported os: " + family); + } + + } + + public static final Map OS_TO_ZERO_PATH = ImmutableMap.of(OsFamily.WINDOWS, + "set PATH=c:\\windows\\;C:\\windows\\system32\r\n", OsFamily.UNIX, + "export PATH=/usr/ucb/bin:/bin:/usr/bin:/usr/sbin\n"); + + /** + * @return line used to zero out the path of the script such that basic commands such as unix ps + * will work. + */ + public static String writeZeroPath(OsFamily family) { + return OS_TO_ZERO_PATH.get(family); + } + public static final Map OS_TO_SWITCH_PATTERN = ImmutableMap.of(OsFamily.UNIX, "case ${variable} in\n", OsFamily.WINDOWS, "goto CASE%{variable}\r\n"); @@ -108,6 +194,25 @@ public class Utils { "{value})\n {action}\n ;;\n", OsFamily.WINDOWS, ":CASE_{value}\r\n {action}\r\n GOTO END_SWITCH\r\n"); + /** + * Generates a switch statement based on {@code variable}. If its value is found to be a key in + * {@code valueToActions}, the corresponding action is invoked. + * + *

+ * Ex. variable is {@code 1} - the first argument to the script
+ * and valueToActions is {"start" -> "echo hello", "stop" -> "echo goodbye"}
+ * the script created will respond accordingly:
+ * {@code ./script start }
+ * << returns hello
+ * {@code ./script stop }
+ * << returns goodbye
+ * + * @param variable + * - shell variable to switch on + * @param valueToActions + * - case statements, if the value of the variable matches a key, the corresponding + * value will be invoked. + */ public static String writeSwitch(String variable, Map valueToActions, OsFamily family) { StringBuilder switchClause = new StringBuilder(); diff --git a/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java b/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java index ada6a93947..b7a1237830 100644 --- a/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java +++ b/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java @@ -40,9 +40,8 @@ public class ShellTokenTest { public void testTokenValueMapUNIX() { Map expected = new ImmutableMap.Builder().put("fs", "/").put( "ps", ":").put("lf", "\n").put("sh", "bash").put("source", ".").put("rem", "#").put( - "args", "$@").put("varstart", "$").put("varend", "").put("shebang", "#!/bin/bash\n") - .put("zeroPath", "export PATH=/usr/ucb/bin:/bin:/usr/bin:/usr/sbin\n") - .put("exe", "").build(); + "args", "$@").put("varstart", "$").put("varend", "").put("libraryPathVariable", + "LD_LIBRARY_PATH").put("shebang", "#!/bin/bash\n").build(); assertEquals(ShellToken.tokenValueMap(OsFamily.UNIX), expected); } @@ -51,9 +50,7 @@ public class ShellTokenTest { Map expected = new ImmutableMap.Builder().put("fs", "\\") .put("ps", ";").put("lf", "\r\n").put("sh", "cmd").put("source", "@call").put("rem", "@rem").put("args", "%*").put("varstart", "%").put("varend", "%").put( - "shebang", "@echo off\r\n").put("zeroPath", - "set PATH=c:\\windows\\;C:\\windows\\system32\r\n").put("exe", ".exe") - .build(); + "libraryPathVariable", "PATH").put("shebang", "@echo off\r\n").build(); assertEquals(ShellToken.tokenValueMap(OsFamily.WINDOWS), expected); } diff --git a/initbuilder/src/test/java/org/jclouds/initbuilder/util/UtilsTest.java b/initbuilder/src/test/java/org/jclouds/initbuilder/util/UtilsTest.java index 11a1b0d6d9..f8c3902cb2 100644 --- a/initbuilder/src/test/java/org/jclouds/initbuilder/util/UtilsTest.java +++ b/initbuilder/src/test/java/org/jclouds/initbuilder/util/UtilsTest.java @@ -24,10 +24,13 @@ package org.jclouds.initbuilder.util; import static org.testng.Assert.assertEquals; + import java.io.UnsupportedEncodingException; import org.jclouds.initbuilder.domain.OsFamily; import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; /** @@ -53,6 +56,26 @@ public class UtilsTest { "set MAVEN_OPTS=-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError\r\n"); } + public void testWritePositionalVarsUNIX() { + assertEquals(Utils.writePositionalVars(ImmutableList.of("host", "port"), OsFamily.UNIX), + "set HOST=$1\nshift\nset PORT=$1\nshift\n"); + } + + public void testWritePositionalVarsWindows() { + assertEquals(Utils.writePositionalVars(ImmutableList.of("host", "port"), OsFamily.WINDOWS), + "set HOST=%1\r\nshift\r\nset PORT=%1\r\nshift\r\n"); + } + + public void testWriteUnsetVariablesUNIX() { + assertEquals(Utils.writeUnsetVariables(ImmutableList.of("host", "port"), OsFamily.UNIX), + "unset HOST PORT\n"); + } + + public void testWriteUnsetVariablesWindows() { + assertEquals(Utils.writeUnsetVariables(ImmutableList.of("host", "port"), OsFamily.WINDOWS), + "set HOST=\r\nset PORT=\r\n"); + } + public void testWriteSwitchUNIX() { assertEquals(Utils.writeSwitch("i", ImmutableMap.of("0", "echo hello zero", "1", "echo hello one"), OsFamily.UNIX), diff --git a/initbuilder/src/test/resources/test_script.bash b/initbuilder/src/test/resources/test_script.bash index 77db713c9f..e7917baf4c 100644 --- a/initbuilder/src/test/resources/test_script.bash +++ b/initbuilder/src/test/resources/test_script.bash @@ -1,4 +1,5 @@ #!/bin/bash +unset PATH JAVA_HOME LD_LIBRARY_PATH export PATH=/usr/ucb/bin:/bin:/usr/bin:/usr/sbin export JAVA_HOME="/apps/jdk1.6" case $1 in diff --git a/initbuilder/src/test/resources/test_script.cmd b/initbuilder/src/test/resources/test_script.cmd index eb43eff7d6..95a7598e77 100644 --- a/initbuilder/src/test/resources/test_script.cmd +++ b/initbuilder/src/test/resources/test_script.cmd @@ -1,4 +1,7 @@ @echo off +set PATH= +set JAVA_HOME= +set PATH= set PATH=c:\windows\;C:\windows\system32 set JAVA_HOME=/apps/jdk1.6 goto CASE%1