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
This commit is contained in:
adrian.f.cole 2009-11-28 08:15:14 +00:00
parent a52209bfc8
commit c66eb4c389
9 changed files with 468 additions and 9 deletions

View File

@ -0,0 +1,106 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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<String, Map<String, String>> switchExec = Maps.newHashMap();
@VisibleForTesting
Map<String, String> variables = Maps.newHashMap();
/**
* Adds a switch statement to the script. If its value is found, it will invoke the corresponding
* action.
*
* <p/>
* Ex. variable is {@code 1} - the first argument to the script<br/>
* and valueToActions is {"start" -> "echo hello", "stop" -> "echo goodbye"}<br/>
* the script created will respond accordingly:<br/>
* {@code ./script start }<br/>
* << returns hello<br/>
* {@code ./script stop }<br/>
* << returns goodbye<br/>
*
* @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<String, String> 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
* <ol>
* <li>shell declaration line</li>
* <li>variable exports</li>
* <li>case/switch</li>
* </ol>
*
* @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<String, Map<String, String>> entry : switchExec.entrySet()) {
builder.append(Utils.writeSwitch(entry.getKey(), entry.getValue(), osFamily));
}
return builder.toString();
}
}

View File

@ -23,6 +23,8 @@
*/ */
package org.jclouds.initbuilder.domain; package org.jclouds.initbuilder.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import java.net.URI; import java.net.URI;
/** /**
@ -45,15 +47,15 @@ public class InitMetadata {
public InitMetadata(String name, String platformHome, URI endPoint, String startDir, public InitMetadata(String name, String platformHome, URI endPoint, String startDir,
String stopDir, String configDir, String dataDir, String logDir, String goldDir) { String stopDir, String configDir, String dataDir, String logDir, String goldDir) {
this.name = name; this.name = checkNotNull(name, "name");
this.platformHome = platformHome; this.platformHome = checkNotNull(platformHome, "platformHome");
this.endPoint = endPoint; this.endPoint = endPoint;
this.startDir = startDir; this.startDir = checkNotNull(startDir, "startDir");
this.stopDir = stopDir; this.stopDir = checkNotNull(stopDir, "stopDir");
this.configDir = configDir; this.configDir = checkNotNull(configDir, "configDir");
this.dataDir = dataDir; this.dataDir = checkNotNull(dataDir, "dataDir");
this.logDir = logDir; this.logDir = checkNotNull(logDir, "logDir");
this.goldDir = goldDir; this.goldDir = checkNotNull(goldDir, "goldDir");
} }
/** /**
@ -69,6 +71,7 @@ public class InitMetadata {
public String getStopDir() { public String getStopDir() {
return stopDir; return stopDir;
} }
/** /**
* Where the platform that this process is an instance of is located. This is analogous to the * Where the platform that this process is an instance of is located. This is analogous to the
* CATALINA_HOME on the tomcat platform. * CATALINA_HOME on the tomcat platform.

View File

@ -0,0 +1,155 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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<OsFamily, Map<String, String>> familyToTokenValueMap = new MapMaker()
.makeComputingMap(new Function<OsFamily, Map<String, String>>() {
@Override
public Map<String, String> apply(OsFamily from) {
Map<String, String> 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<String, String> 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");
}
}
}

View File

@ -97,4 +97,29 @@ public class Utils {
} }
return initializers.toString(); return initializers.toString();
} }
public static final Map<OsFamily, String> OS_TO_SWITCH_PATTERN = ImmutableMap.of(OsFamily.UNIX,
"case ${variable} in\n", OsFamily.WINDOWS, "goto CASE%{variable}\r\n");
public static final Map<OsFamily, String> OS_TO_END_SWITCH_PATTERN = ImmutableMap.of(
OsFamily.UNIX, "esac\n", OsFamily.WINDOWS, ":END_SWITCH\r\n");
public static final Map<OsFamily, String> 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<String, String> 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<String, String> 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();
}
} }

View File

@ -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);
}
}

View File

@ -0,0 +1,60 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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<String, String> expected = new ImmutableMap.Builder<String, String>().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<String, String> expected = new ImmutableMap.Builder<String, String>().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);
}
}

View File

@ -33,7 +33,7 @@ import com.google.common.collect.ImmutableMap;
/** /**
* @author Adrian Cole * @author Adrian Cole
*/ */
@Test(groups = "unit", testName = "jclouds.UtilsTest") @Test(groups = "unit", testName = "initbuilder.UtilsTest")
public class UtilsTest { public class UtilsTest {
public void testReplaceTokens() throws UnsupportedEncodingException { public void testReplaceTokens() throws UnsupportedEncodingException {
@ -52,4 +52,17 @@ public class UtilsTest {
"-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError"), OsFamily.WINDOWS), "-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError"), OsFamily.WINDOWS),
"set MAVEN_OPTS=-Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError\r\n"); "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");
}
} }

View File

@ -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

View File

@ -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