From c66eb4c389f09173d923898b3a121dcdf9747813 Mon Sep 17 00:00:00 2001 From: "adrian.f.cole" Date: Sat, 28 Nov 2009 08:15:14 +0000 Subject: [PATCH] Issue 126: first cut at init builder, complete with shell script generation git-svn-id: http://jclouds.googlecode.com/svn/trunk@2338 3d8758e0-26b5-11de-8745-db77d3ebf521 --- .../org/jclouds/initbuilder/InitBuilder.java | 106 ++++++++++++ .../initbuilder/domain/InitMetadata.java | 19 ++- .../initbuilder/domain/ShellToken.java | 155 ++++++++++++++++++ .../org/jclouds/initbuilder/util/Utils.java | 25 +++ .../jclouds/initbuilder/InitBuilderTest.java | 75 +++++++++ .../initbuilder/domain/ShellTokenTest.java | 60 +++++++ .../jclouds/initbuilder/util/UtilsTest.java | 15 +- .../src/test/resources/test_script.bash | 11 ++ .../src/test/resources/test_script.cmd | 11 ++ 9 files changed, 468 insertions(+), 9 deletions(-) create mode 100644 initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java create mode 100644 initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java create mode 100644 initbuilder/src/test/java/org/jclouds/initbuilder/InitBuilderTest.java create mode 100644 initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java create mode 100644 initbuilder/src/test/resources/test_script.bash create mode 100644 initbuilder/src/test/resources/test_script.cmd diff --git a/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java b/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java new file mode 100644 index 0000000000..981ae162b9 --- /dev/null +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/InitBuilder.java @@ -0,0 +1,106 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.jclouds.initbuilder; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.Map.Entry; + +import org.jclouds.initbuilder.domain.OsFamily; +import org.jclouds.initbuilder.domain.ShellToken; +import org.jclouds.initbuilder.util.Utils; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; + +/** + * Creates a start script. + * + * @author Adrian Cole + */ +public class InitBuilder { + + @VisibleForTesting + Map> switchExec = Maps.newHashMap(); + + @VisibleForTesting + Map variables = Maps.newHashMap(); + + /** + * Adds a switch statement to the script. If its value is found, it will invoke the corresponding + * action. + * + *

+ * 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 InitBuilder switchOn(String variable, Map valueToActions) { + switchExec.put(checkNotNull(variable, "variable"), checkNotNull(valueToActions, + "valueToActions")); + return this; + } + + /** + * Exports a variable inside the script + */ + public InitBuilder export(String name, String value) { + variables.put(checkNotNull(name, "name"), checkNotNull(value, "value")); + return this; + } + + /** + * builds the shell script, by adding the following + *

    + *
  1. shell declaration line
  2. + *
  3. variable exports
  4. + *
  5. case/switch
  6. + *
+ * + * @param osFamily + * whether to write a cmd or bash script. + */ + public String build(OsFamily osFamily) { + StringBuilder builder = new StringBuilder(); + builder.append(ShellToken.SHEBANG.to(osFamily)); + builder.append(ShellToken.ZERO_PATH.to(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/InitMetadata.java b/initbuilder/src/main/java/org/jclouds/initbuilder/domain/InitMetadata.java index 2eb0b8c989..12b0453926 100644 --- a/initbuilder/src/main/java/org/jclouds/initbuilder/domain/InitMetadata.java +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/domain/InitMetadata.java @@ -23,6 +23,8 @@ */ package org.jclouds.initbuilder.domain; +import static com.google.common.base.Preconditions.checkNotNull; + import java.net.URI; /** @@ -45,15 +47,15 @@ public class InitMetadata { public InitMetadata(String name, String platformHome, URI endPoint, String startDir, String stopDir, String configDir, String dataDir, String logDir, String goldDir) { - this.name = name; - this.platformHome = platformHome; + this.name = checkNotNull(name, "name"); + this.platformHome = checkNotNull(platformHome, "platformHome"); this.endPoint = endPoint; - this.startDir = startDir; - this.stopDir = stopDir; - this.configDir = configDir; - this.dataDir = dataDir; - this.logDir = logDir; - this.goldDir = goldDir; + this.startDir = checkNotNull(startDir, "startDir"); + this.stopDir = checkNotNull(stopDir, "stopDir"); + this.configDir = checkNotNull(configDir, "configDir"); + this.dataDir = checkNotNull(dataDir, "dataDir"); + this.logDir = checkNotNull(logDir, "logDir"); + this.goldDir = checkNotNull(goldDir, "goldDir"); } /** @@ -69,6 +71,7 @@ public class InitMetadata { public String getStopDir() { return stopDir; } + /** * Where the platform that this process is an instance of is located. This is analogous to the * CATALINA_HOME on the tomcat platform. diff --git a/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java b/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java new file mode 100644 index 0000000000..dacacc6577 --- /dev/null +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/domain/ShellToken.java @@ -0,0 +1,155 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.jclouds.initbuilder.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Function; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Maps; + +/** + * Constants used in operating suy + * + * @author Adrian Cole + */ +public enum ShellToken { + + FS, PS, LF, SH, SOURCE, REM, ARGS, VARSTART, VAREND, SHEBANG, ZERO_PATH, EXE; + + private static final Map> familyToTokenValueMap = new MapMaker() + .makeComputingMap(new Function>() { + + @Override + public Map apply(OsFamily from) { + Map map = Maps.newHashMap(); + for (ShellToken token : ShellToken.values()) { + map.put(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, token + .toString()), token.to(from)); + } + return map; + } + + }); + + public static Map tokenValueMap(OsFamily family) { + return familyToTokenValueMap.get(family); + } + + public String to(OsFamily family) { + checkNotNull(family, "family"); + switch (this) { + case FS: + switch (family) { + case WINDOWS: + return "\\"; + case UNIX: + return "/"; + } + case PS: + switch (family) { + case WINDOWS: + return ";"; + case UNIX: + return ":"; + } + case LF: + switch (family) { + case WINDOWS: + return "\r\n"; + case UNIX: + return "\n"; + } + case SH: + switch (family) { + case WINDOWS: + return "cmd"; + case UNIX: + return "bash"; + } + case SOURCE: + switch (family) { + case WINDOWS: + return "@call"; + case UNIX: + return "."; + } + case REM: + switch (family) { + case WINDOWS: + return "@rem"; + case UNIX: + return "#"; + } + case ARGS: + switch (family) { + case WINDOWS: + return "%*"; + case UNIX: + return "$@"; + } + case VARSTART: + switch (family) { + case WINDOWS: + return "%"; + case UNIX: + return "$"; + } + case VAREND: + switch (family) { + case WINDOWS: + return "%"; + case UNIX: + return ""; + } + case SHEBANG: + switch (family) { + case WINDOWS: + return "@echo off\r\n"; + 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"); + } + } + +} \ No newline at end of file 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 1052f4de73..665a1c9e56 100644 --- a/initbuilder/src/main/java/org/jclouds/initbuilder/util/Utils.java +++ b/initbuilder/src/main/java/org/jclouds/initbuilder/util/Utils.java @@ -97,4 +97,29 @@ public class Utils { } return initializers.toString(); } + + public static final Map OS_TO_SWITCH_PATTERN = ImmutableMap.of(OsFamily.UNIX, + "case ${variable} in\n", OsFamily.WINDOWS, "goto CASE%{variable}\r\n"); + + public static final Map OS_TO_END_SWITCH_PATTERN = ImmutableMap.of( + OsFamily.UNIX, "esac\n", OsFamily.WINDOWS, ":END_SWITCH\r\n"); + + public static final Map OS_TO_CASE_PATTERN = ImmutableMap.of(OsFamily.UNIX, + "{value})\n {action}\n ;;\n", OsFamily.WINDOWS, + ":CASE_{value}\r\n {action}\r\n GOTO END_SWITCH\r\n"); + + public static String writeSwitch(String variable, Map valueToActions, + OsFamily family) { + StringBuilder switchClause = new StringBuilder(); + switchClause.append(replaceTokens(OS_TO_SWITCH_PATTERN.get(family), ImmutableMap.of( + "variable", CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, variable)))); + + for (Entry entry : valueToActions.entrySet()) { + switchClause.append(replaceTokens(OS_TO_CASE_PATTERN.get(family), ImmutableMap.of("value", + entry.getKey(), "action", entry.getValue()))); + } + + switchClause.append(OS_TO_END_SWITCH_PATTERN.get(family)); + return switchClause.toString(); + } } diff --git a/initbuilder/src/test/java/org/jclouds/initbuilder/InitBuilderTest.java b/initbuilder/src/test/java/org/jclouds/initbuilder/InitBuilderTest.java new file mode 100644 index 0000000000..3e0e6a9c34 --- /dev/null +++ b/initbuilder/src/test/java/org/jclouds/initbuilder/InitBuilderTest.java @@ -0,0 +1,75 @@ +package org.jclouds.initbuilder; + +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.net.MalformedURLException; + +import org.jclouds.initbuilder.domain.OsFamily; +import org.jclouds.initbuilder.domain.ShellToken; +import org.testng.annotations.Test; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.CharStreams; +import com.google.common.io.Resources; + +/** + * Tests possible uses of InitBuilder + * + * @author Adrian Cole + */ +public class InitBuilderTest { + + InitBuilder testScriptBuilder = new InitBuilder().switchOn("1", + ImmutableMap.of("start", "echo started", "stop", "echo stopped")).export("javaHome", + "/apps/jdk1.6"); + + @Test + public void testBuildSimpleWindows() throws MalformedURLException, IOException { + assertEquals(testScriptBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources + .newReaderSupplier(Resources.getResource("test_script." + + ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8))); + } + + @Test + public void testBuildSimpleUNIX() throws MalformedURLException, IOException { + assertEquals(testScriptBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources + .newReaderSupplier(Resources.getResource("test_script." + + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8))); + } + + @Test + public void testSwitchOn() { + InitBuilder builder = new InitBuilder(); + builder.switchOn("1", ImmutableMap.of("start", "echo started", "stop", "echo stopped")); + assertEquals(builder.switchExec, ImmutableMap.of("1", ImmutableMap.of("start", + "echo started", "stop", "echo stopped"))); + } + + @Test + public void testNoSwitchOn() { + InitBuilder builder = new InitBuilder(); + assertEquals(builder.switchExec.size(), 0); + } + + @Test + public void testExport() { + InitBuilder builder = new InitBuilder(); + builder.export("javaHome", "/apps/jdk1.6"); + assertEquals(builder.variables, ImmutableMap.of("javaHome", "/apps/jdk1.6")); + + } + + @Test + public void testNoExport() { + InitBuilder builder = new InitBuilder(); + assertEquals(builder.variables.size(), 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testExportNPE() { + new InitBuilder().export(null, null); + } + +} diff --git a/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java b/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java new file mode 100644 index 0000000000..ada6a93947 --- /dev/null +++ b/initbuilder/src/test/java/org/jclouds/initbuilder/domain/ShellTokenTest.java @@ -0,0 +1,60 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.jclouds.initbuilder.domain; + +import static org.testng.Assert.assertEquals; + +import java.util.Map; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "initbuilder.ShellTokenTest") +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(); + + assertEquals(ShellToken.tokenValueMap(OsFamily.UNIX), expected); + } + + public void testTokenValueMapWindows() { + 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(); + + 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 4f0a32d42d..11a1b0d6d9 100644 --- a/initbuilder/src/test/java/org/jclouds/initbuilder/util/UtilsTest.java +++ b/initbuilder/src/test/java/org/jclouds/initbuilder/util/UtilsTest.java @@ -33,7 +33,7 @@ import com.google.common.collect.ImmutableMap; /** * @author Adrian Cole */ -@Test(groups = "unit", testName = "jclouds.UtilsTest") +@Test(groups = "unit", testName = "initbuilder.UtilsTest") public class UtilsTest { public void testReplaceTokens() throws UnsupportedEncodingException { @@ -52,4 +52,17 @@ public class UtilsTest { "-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError"), OsFamily.WINDOWS), "set MAVEN_OPTS=-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError\r\n"); } + + public void testWriteSwitchUNIX() { + assertEquals(Utils.writeSwitch("i", ImmutableMap.of("0", "echo hello zero", "1", + "echo hello one"), OsFamily.UNIX), + "case $I in\n0)\n echo hello zero\n ;;\n1)\n echo hello one\n ;;\nesac\n"); + } + + public void testWriteSwitchWindows() { + assertEquals( + Utils.writeSwitch("i", ImmutableMap + .of("0", "echo hello zero", "1", "echo hello one"), OsFamily.WINDOWS), + "goto CASE%I\r\n:CASE_0\r\n echo hello zero\r\n GOTO END_SWITCH\r\n:CASE_1\r\n echo hello one\r\n GOTO END_SWITCH\r\n:END_SWITCH\r\n"); + } } diff --git a/initbuilder/src/test/resources/test_script.bash b/initbuilder/src/test/resources/test_script.bash new file mode 100644 index 0000000000..77db713c9f --- /dev/null +++ b/initbuilder/src/test/resources/test_script.bash @@ -0,0 +1,11 @@ +#!/bin/bash +export PATH=/usr/ucb/bin:/bin:/usr/bin:/usr/sbin +export JAVA_HOME="/apps/jdk1.6" +case $1 in +start) + echo started + ;; +stop) + echo stopped + ;; +esac diff --git a/initbuilder/src/test/resources/test_script.cmd b/initbuilder/src/test/resources/test_script.cmd new file mode 100644 index 0000000000..eb43eff7d6 --- /dev/null +++ b/initbuilder/src/test/resources/test_script.cmd @@ -0,0 +1,11 @@ +@echo off +set PATH=c:\windows\;C:\windows\system32 +set JAVA_HOME=/apps/jdk1.6 +goto CASE%1 +:CASE_start + echo started + GOTO END_SWITCH +:CASE_stop + echo stopped + GOTO END_SWITCH +:END_SWITCH