mirror of https://github.com/apache/jclouds.git
added create file support to scriptbuilder
This commit is contained in:
parent
fa8386b357
commit
a96471c9a8
|
@ -66,6 +66,8 @@ import org.jclouds.predicates.SocketOpen;
|
|||
import org.jclouds.rest.RestContextFactory;
|
||||
import org.jclouds.scriptbuilder.InitBuilder;
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.jclouds.ssh.ExecResponse;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.SshException;
|
||||
|
@ -75,6 +77,7 @@ import org.testng.annotations.BeforeTest;
|
|||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
@ -250,25 +253,25 @@ public class EBSBootEC2ClientLiveTest {
|
|||
"mkebsboot",// name of the script
|
||||
"/tmp",// working directory
|
||||
"/tmp/logs",// location of stdout.log and stderr.log
|
||||
ImmutableMap.of("imageDir", "/mnt/tmp", "ebsDevice", "/dev/sdh", "ebsMountPoint", "/mnt/ebs"),// variables
|
||||
// used
|
||||
// inside
|
||||
// of
|
||||
// the
|
||||
// script
|
||||
"echo creating a filesystem and mounting the ebs volume",// what to
|
||||
// execute
|
||||
ImmutableMap.of("imageDir", "/mnt/tmp", "ebsDevice", "/dev/sdh", "ebsMountPoint", "/mnt/ebs"),
|
||||
ImmutableList
|
||||
.<Statement> of(Statements
|
||||
.interpret(
|
||||
"echo creating a filesystem and mounting the ebs volume",
|
||||
"{md} {varl}IMAGE_DIR{varr} {varl}EBS_MOUNT_POINT{varr}",
|
||||
"rm -rf {varl}IMAGE_DIR{varr}/*",
|
||||
"yes| mkfs -t ext3 {varl}EBS_DEVICE{varr} 2>&-",
|
||||
"mount {varl}EBS_DEVICE{varr} {varl}EBS_MOUNT_POINT{varr}",
|
||||
"echo making a local working copy of the boot disk",
|
||||
"rsync -ax --exclude /ubuntu/.bash_history --exclude /home/*/.bash_history --exclude /etc/ssh/ssh_host_* --exclude /etc/ssh/moduli --exclude /etc/udev/rules.d/*persistent-net.rules --exclude /var/lib/ec2/* --exclude=/mnt/* --exclude=/proc/* --exclude=/tmp/* --exclude=/dev/log / {varl}IMAGE_DIR{varr}",
|
||||
"echo preparing the local working copy", "touch {varl}IMAGE_DIR{varr}/etc/init.d/ec2-init-user-data",
|
||||
"echo copying the local working copy to the ebs mount", "{cd} {varl}IMAGE_DIR{varr}",
|
||||
"echo preparing the local working copy",
|
||||
"touch {varl}IMAGE_DIR{varr}/etc/init.d/ec2-init-user-data",
|
||||
"echo copying the local working copy to the ebs mount",
|
||||
"{cd} {varl}IMAGE_DIR{varr}",
|
||||
"tar -cSf - * | tar xf - -C {varl}EBS_MOUNT_POINT{varr}", "echo size of ebs",
|
||||
"du -sk {varl}EBS_MOUNT_POINT{varr}", "echo size of source", "du -sk {varl}IMAGE_DIR{varr}",
|
||||
"rm -rf {varl}IMAGE_DIR{varr}/*", "umount {varl}EBS_MOUNT_POINT{varr}", "echo " + SCRIPT_END)
|
||||
"du -sk {varl}EBS_MOUNT_POINT{varr}", "echo size of source",
|
||||
"du -sk {varl}IMAGE_DIR{varr}", "rm -rf {varl}IMAGE_DIR{varr}/*",
|
||||
"umount {varl}EBS_MOUNT_POINT{varr}", "echo " + SCRIPT_END)))
|
||||
.build(OsFamily.UNIX);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,12 +35,15 @@ import org.jclouds.io.Payloads;
|
|||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.scriptbuilder.InitBuilder;
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.jclouds.ssh.ExecResponse;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.util.Utils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
|
@ -73,7 +76,7 @@ public class RunScriptOnNode implements SshCallable<ExecResponse> {
|
|||
public static Payload createRunScript(String scriptName, Payload script) {
|
||||
String path = "/tmp/" + scriptName;
|
||||
InitBuilder initBuilder = new InitBuilder(scriptName, path, path, Collections.<String, String> emptyMap(),
|
||||
splitOnNewlines(script));
|
||||
ImmutableList.<Statement> of(Statements.interpret(splitOnNewlines(script))));
|
||||
return Payloads.newByteArrayPayload(initBuilder.build(OsFamily.UNIX).getBytes());
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ import org.jclouds.net.IPSocket;
|
|||
import org.jclouds.predicates.SocketOpen;
|
||||
import org.jclouds.rest.RestContext;
|
||||
import org.jclouds.scriptbuilder.InitBuilder;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.jclouds.ssh.ExecResponse;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.SshException;
|
||||
|
@ -55,6 +57,7 @@ import org.testng.annotations.Test;
|
|||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.inject.AbstractModule;
|
||||
|
@ -242,8 +245,8 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes
|
|||
|
||||
public static String initScript(String scriptName, String script) {
|
||||
return new InitBuilder(scriptName, "/tmp/" + scriptName, "/tmp/" + scriptName,
|
||||
ImmutableMap.<String, String> of(), Iterables.toArray(Splitter.on("\n").split(
|
||||
new String(checkNotNull(script, "script"))), String.class))
|
||||
ImmutableMap.<String, String> of(), ImmutableList.<Statement> of(Statements.interpret(Iterables.toArray(
|
||||
Splitter.on("\n").split(new String(checkNotNull(script, "script"))), String.class))))
|
||||
.build(org.jclouds.scriptbuilder.domain.OsFamily.UNIX);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ import static org.jclouds.scriptbuilder.domain.Statements.switchArg;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
|
@ -41,10 +43,10 @@ import com.google.common.collect.Iterables;
|
|||
public class InitBuilder extends ScriptBuilder {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public InitBuilder(String instanceName, String instanceHome, String logDir,
|
||||
Map<String, String> variables, String... execLines) {
|
||||
Map<String, String> defaultVariables = ImmutableMap.of("instanceName", instanceName,
|
||||
"instanceHome", instanceHome, "logDir", logDir);
|
||||
public InitBuilder(String instanceName, String instanceHome, String logDir, Map<String, String> variables,
|
||||
Iterable<Statement> statements) {
|
||||
Map<String, String> defaultVariables = ImmutableMap.of("instanceName", instanceName, "instanceHome",
|
||||
instanceHome, "logDir", logDir);
|
||||
addEnvironmentVariableScope("default", defaultVariables)
|
||||
.addEnvironmentVariableScope(instanceName, variables)
|
||||
.addStatement(
|
||||
|
@ -53,31 +55,26 @@ public class InitBuilder extends ScriptBuilder {
|
|||
new ImmutableMap.Builder()
|
||||
.put(
|
||||
"init",
|
||||
newStatementList(call("default"),
|
||||
call(instanceName), createRunScript(
|
||||
newStatementList(call("default"), call(instanceName),
|
||||
createRunScript(
|
||||
instanceName,// TODO: convert
|
||||
// so
|
||||
// that
|
||||
// createRunScript
|
||||
// can take from a
|
||||
// variable
|
||||
Iterables.concat(variables
|
||||
.keySet(),
|
||||
defaultVariables
|
||||
.keySet()),
|
||||
"{varl}INSTANCE_HOME{varr}",
|
||||
execLines)))
|
||||
Iterables.concat(variables.keySet(),
|
||||
defaultVariables.keySet()),
|
||||
"{varl}INSTANCE_HOME{varr}", statements)))
|
||||
.put(
|
||||
"status",
|
||||
newStatementList(
|
||||
call("default"),
|
||||
newStatementList(call("default"),
|
||||
findPid("{varl}INSTANCE_NAME{varr}"),
|
||||
interpret("echo [{varl}FOUND_PID{varr}]{lf}")))
|
||||
.put(
|
||||
"stop",
|
||||
newStatementList(call("default"),
|
||||
findPid("{varl}INSTANCE_NAME{varr}"),
|
||||
kill()))
|
||||
findPid("{varl}INSTANCE_NAME{varr}"), kill()))
|
||||
.put(
|
||||
"start",
|
||||
newStatementList(
|
||||
|
@ -88,13 +85,11 @@ public class InitBuilder extends ScriptBuilder {
|
|||
"{varl}LOG_DIR{varr}")))
|
||||
.put(
|
||||
"tail",
|
||||
newStatementList(
|
||||
call("default"),
|
||||
newStatementList(call("default"),
|
||||
interpret("tail {varl}LOG_DIR{varr}{fs}stdout.log{lf}")))
|
||||
.put(
|
||||
"tailerr",
|
||||
newStatementList(
|
||||
call("default"),
|
||||
newStatementList(call("default"),
|
||||
interpret("tail {varl}LOG_DIR{varr}{fs}stderr.log{lf}")))
|
||||
.put(
|
||||
"run",
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
|
||||
*
|
||||
* ====================================================================
|
||||
* Licensed 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.scriptbuilder.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.interpret;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Creates a run script
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class CreateFile implements Statement {
|
||||
public final static String MARKER = "END_OF_FILE";
|
||||
final String path;
|
||||
final Iterable<String> lines;
|
||||
|
||||
public CreateFile(String path, Iterable<String> lines) {// TODO: convert so
|
||||
this.path = checkNotNull(path, "path");
|
||||
this.lines = checkNotNull(lines, "lines");
|
||||
checkState(Iterables.size(lines) > 0, "you must pass something to execute");
|
||||
}
|
||||
|
||||
public static String escapeVarTokens(String toEscape, OsFamily family) {
|
||||
Map<String, String> inputToEscape = Maps.newHashMap();
|
||||
for (ShellToken token : ImmutableList.of(ShellToken.VARL, ShellToken.VARR)) {
|
||||
if (!token.to(family).equals("")) {
|
||||
String tokenS = "{" + token.toString().toLowerCase() + "}";
|
||||
inputToEscape.put(tokenS, "{escvar}" + tokenS);
|
||||
}
|
||||
}
|
||||
for (Entry<String, String> entry : inputToEscape.entrySet()) {
|
||||
toEscape = toEscape.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return toEscape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> functionDependecies(OsFamily family) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(OsFamily family) {
|
||||
List<Statement> statements = Lists.newArrayList();
|
||||
if (family == OsFamily.UNIX) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
hereFile(path, builder);
|
||||
statements.add(interpret(builder.toString()));
|
||||
} else {
|
||||
statements.add(interpret(String.format("{rm} %s 2{closeFd}{lf}", path)));
|
||||
for (String line : lines) {
|
||||
statements.add(appendToFile(line, path, family));
|
||||
}
|
||||
}
|
||||
return new StatementList(statements).render(family);
|
||||
}
|
||||
|
||||
private void hereFile(String path, StringBuilder builder) {
|
||||
builder.append("cat > ").append(path).append(" <<'").append(MARKER).append("'\n");
|
||||
for (String line : lines) {
|
||||
builder.append(line).append("\n");
|
||||
}
|
||||
builder.append(MARKER).append("\n");
|
||||
}
|
||||
|
||||
private Statement appendToFile(String line, String path, OsFamily family) {
|
||||
String quote = "";
|
||||
if (!ShellToken.VQ.to(family).equals("")) {
|
||||
quote = "'";
|
||||
} else {
|
||||
line = escapeVarTokens(line, family);
|
||||
}
|
||||
return interpret(addSpaceToEnsureWeDontAccidentallyRedirectFd(String.format("echo %s%s%s>>%s{lf}", quote, line,
|
||||
quote, path)));
|
||||
}
|
||||
|
||||
public static final Pattern REDIRECT_FD_PATTERN = Pattern.compile(".*[0-2]>>.*");
|
||||
|
||||
static String addSpaceToEnsureWeDontAccidentallyRedirectFd(String line) {
|
||||
return REDIRECT_FD_PATTERN.matcher(line).matches() ? line.replace(">>", " >>") : line;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,6 @@
|
|||
package org.jclouds.scriptbuilder.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.interpret;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -48,19 +47,13 @@ public class CreateRunScript implements Statement {
|
|||
final String instanceName;
|
||||
final Iterable<String> exports;
|
||||
final String pwd;
|
||||
final String[] execLines;
|
||||
final Iterable<Statement> statements;
|
||||
|
||||
public CreateRunScript(String instanceName, Iterable<String> exports, String pwd,
|
||||
String... execLines) {// TODO: convert so
|
||||
// that
|
||||
// createRunScript
|
||||
// can take from a
|
||||
// variable
|
||||
public CreateRunScript(String instanceName, Iterable<String> exports, String pwd, Iterable<Statement> statements) {
|
||||
this.instanceName = checkNotNull(instanceName, "instanceName");
|
||||
this.exports = checkNotNull(exports, "exports");
|
||||
this.pwd = checkNotNull(pwd, "pwd").replaceAll("[/\\\\]", "{fs}");
|
||||
this.execLines = checkNotNull(execLines, "execLines");
|
||||
checkState(execLines.length > 0, "you must pass something to execute");
|
||||
this.statements = checkNotNull(statements, "statements");
|
||||
}
|
||||
|
||||
public static class AddTitleToFile implements Statement {
|
||||
|
@ -72,10 +65,9 @@ public class CreateRunScript implements Statement {
|
|||
this.file = checkNotNull(file, "file");
|
||||
}
|
||||
|
||||
public static final Map<OsFamily, String> OS_TO_TITLE_PATTERN = ImmutableMap.of(
|
||||
OsFamily.UNIX,
|
||||
"echo \"PROMPT_COMMAND='echo -ne \\\"\\033]0;{title}\\007\\\"'\">>{file}\n",
|
||||
OsFamily.WINDOWS, "echo title {title}>>{file}\r\n");
|
||||
public static final Map<OsFamily, String> OS_TO_TITLE_PATTERN = ImmutableMap.of(OsFamily.UNIX,
|
||||
"echo \"PROMPT_COMMAND='echo -ne \\\"\\033]0;{title}\\007\\\"'\">>{file}\n", OsFamily.WINDOWS,
|
||||
"echo title {title}>>{file}\r\n");
|
||||
|
||||
@Override
|
||||
public Iterable<String> functionDependecies(OsFamily family) {
|
||||
|
@ -84,8 +76,8 @@ public class CreateRunScript implements Statement {
|
|||
|
||||
@Override
|
||||
public String render(OsFamily family) {
|
||||
return addSpaceToEnsureWeDontAccidentallyRedirectFd(Utils.replaceTokens(
|
||||
OS_TO_TITLE_PATTERN.get(family), ImmutableMap.of("title", title, "file", file)));
|
||||
return addSpaceToEnsureWeDontAccidentallyRedirectFd(Utils.replaceTokens(OS_TO_TITLE_PATTERN.get(family),
|
||||
ImmutableMap.of("title", title, "file", file)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,14 +87,13 @@ public class CreateRunScript implements Statement {
|
|||
final String file;
|
||||
|
||||
public AddExportToFile(String export, String value, String file) {
|
||||
this.export = checkNotNull(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, export),
|
||||
"export");
|
||||
this.export = checkNotNull(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, export), "export");
|
||||
this.value = checkNotNull(value, "value");
|
||||
this.file = checkNotNull(file, "file");
|
||||
}
|
||||
|
||||
public static final Map<OsFamily, String> OS_TO_EXPORT_PATTERN = ImmutableMap.of(
|
||||
OsFamily.UNIX, "echo \"export {export}='{value}'\">>{file}\n", OsFamily.WINDOWS,
|
||||
public static final Map<OsFamily, String> OS_TO_EXPORT_PATTERN = ImmutableMap.of(OsFamily.UNIX,
|
||||
"echo \"export {export}='{value}'\">>{file}\n", OsFamily.WINDOWS,
|
||||
"echo set {export}={value}>>{file}\r\n");
|
||||
|
||||
@Override
|
||||
|
@ -112,9 +103,8 @@ public class CreateRunScript implements Statement {
|
|||
|
||||
@Override
|
||||
public String render(OsFamily family) {
|
||||
return addSpaceToEnsureWeDontAccidentallyRedirectFd(Utils.replaceTokens(
|
||||
OS_TO_EXPORT_PATTERN.get(family), ImmutableMap.of("export", export, "value",
|
||||
value, "file", file)));
|
||||
return addSpaceToEnsureWeDontAccidentallyRedirectFd(Utils.replaceTokens(OS_TO_EXPORT_PATTERN.get(family),
|
||||
ImmutableMap.of("export", export, "value", value, "file", file)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,8 +127,8 @@ public class CreateRunScript implements Statement {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static final Map<OsFamily, String> OS_TO_CHMOD_PATTERN = ImmutableMap.of(OsFamily.UNIX,
|
||||
"chmod u+x {file}\n", OsFamily.WINDOWS, "");
|
||||
public static final Map<OsFamily, String> OS_TO_CHMOD_PATTERN = ImmutableMap.of(OsFamily.UNIX, "chmod u+x {file}\n",
|
||||
OsFamily.WINDOWS, "");
|
||||
|
||||
@Override
|
||||
public String render(OsFamily family) {
|
||||
|
@ -158,32 +148,29 @@ public class CreateRunScript implements Statement {
|
|||
statements.add(interpret(builder.toString()));
|
||||
} else {
|
||||
statements.add(interpret(String.format("{rm} %s 2{closeFd}{lf}", runScript)));
|
||||
for (String line : Splitter.on(ShellToken.LF.to(family)).split(
|
||||
ShellToken.BEGIN_SCRIPT.to(family))) {
|
||||
for (String line : Splitter.on(ShellToken.LF.to(family)).split(ShellToken.BEGIN_SCRIPT.to(family))) {
|
||||
if (!line.equals(""))
|
||||
statements.add(appendToFile(line, runScript, family));
|
||||
}
|
||||
statements.add(new AddTitleToFile(instanceName, runScript));
|
||||
statements.add(appendToFile(Utils.writeZeroPath(family).replace(ShellToken.LF.to(family),
|
||||
""), runScript, family));
|
||||
statements.add(appendToFile(Utils.writeZeroPath(family).replace(ShellToken.LF.to(family), ""), runScript,
|
||||
family));
|
||||
statements.add(new AddExportToFile("instanceName", instanceName, runScript));
|
||||
for (String export : exports) {
|
||||
statements.add(new AddExportToFile(export, Utils.replaceTokens("{varl}"
|
||||
+ CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, export) + "{varr}",
|
||||
tokenMap), runScript));
|
||||
statements
|
||||
.add(new AddExportToFile(export, Utils.replaceTokens("{varl}"
|
||||
+ CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, export) + "{varr}", tokenMap),
|
||||
runScript));
|
||||
}
|
||||
statements.add(appendToFile("{cd} " + pwd, runScript, family));
|
||||
for (String execLine : execLines) {
|
||||
statements.add(appendToFile(execLine, runScript, family));
|
||||
}
|
||||
for (String line : Splitter.on(ShellToken.LF.to(family)).split(
|
||||
ShellToken.END_SCRIPT.to(family))) {
|
||||
statements.addAll(statements);
|
||||
for (String line : Splitter.on(ShellToken.LF.to(family)).split(ShellToken.END_SCRIPT.to(family))) {
|
||||
if (!line.equals(""))
|
||||
statements.add(appendToFile(line, runScript, family));
|
||||
}
|
||||
}
|
||||
statements.add(interpret(Utils.replaceTokens(OS_TO_CHMOD_PATTERN.get(family), ImmutableMap
|
||||
.of("file", runScript))));
|
||||
statements
|
||||
.add(interpret(Utils.replaceTokens(OS_TO_CHMOD_PATTERN.get(family), ImmutableMap.of("file", runScript))));
|
||||
return new StatementList(statements).render(family);
|
||||
}
|
||||
|
||||
|
@ -198,8 +185,8 @@ public class CreateRunScript implements Statement {
|
|||
builder.append("# add desired commands from the user\n");
|
||||
builder.append("cat >> ").append(runScript).append(" <<'").append(MARKER).append("'\n");
|
||||
builder.append("cd ").append(pwd).append("\n");
|
||||
for (String execLine : execLines) {
|
||||
builder.append(execLine).append("\n");
|
||||
for (Statement statement : statements) {
|
||||
builder.append(statement.render(OsFamily.UNIX)).append("\n");
|
||||
}
|
||||
builder.append(MARKER).append("\n");
|
||||
}
|
||||
|
@ -208,15 +195,12 @@ public class CreateRunScript implements Statement {
|
|||
builder.append("# create runscript header\n");
|
||||
builder.append("cat > ").append(runScript).append(" <<").append(MARKER).append("\n");
|
||||
builder.append(ShellToken.BEGIN_SCRIPT.to(family));
|
||||
builder.append("PROMPT_COMMAND='echo -ne \"\\033]0;").append(instanceName).append(
|
||||
"\\007\"'\n");
|
||||
builder.append("PROMPT_COMMAND='echo -ne \"\\033]0;").append(instanceName).append("\\007\"'\n");
|
||||
builder.append(Utils.writeZeroPath(family));
|
||||
builder.append("export INSTANCE_NAME='").append(instanceName).append("'\n");
|
||||
for (String export : exports) {
|
||||
String variableNameInUpper = CaseFormat.LOWER_CAMEL
|
||||
.to(CaseFormat.UPPER_UNDERSCORE, export);
|
||||
builder.append("export ").append(variableNameInUpper).append("='$").append(
|
||||
variableNameInUpper).append("'\n");
|
||||
String variableNameInUpper = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, export);
|
||||
builder.append("export ").append(variableNameInUpper).append("='$").append(variableNameInUpper).append("'\n");
|
||||
}
|
||||
builder.append(MARKER).append("\n");
|
||||
}
|
||||
|
@ -228,8 +212,8 @@ public class CreateRunScript implements Statement {
|
|||
} else {
|
||||
line = escapeVarTokens(line, family);
|
||||
}
|
||||
return interpret(addSpaceToEnsureWeDontAccidentallyRedirectFd(String.format(
|
||||
"echo %s%s%s>>%s{lf}", quote, line, quote, runScript)));
|
||||
return interpret(addSpaceToEnsureWeDontAccidentallyRedirectFd(String.format("echo %s%s%s>>%s{lf}", quote, line,
|
||||
quote, runScript)));
|
||||
}
|
||||
|
||||
public static final Pattern REDIRECT_FD_PATTERN = Pattern.compile(".*[0-2]>>.*");
|
||||
|
|
|
@ -21,8 +21,11 @@ package org.jclouds.scriptbuilder.domain;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jclouds.scriptbuilder.util.Utils;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
|
@ -32,14 +35,15 @@ import com.google.common.collect.ImmutableList;
|
|||
*/
|
||||
public class InterpretableStatement implements Statement {
|
||||
|
||||
private String statement;
|
||||
private String[] statements;
|
||||
|
||||
public InterpretableStatement(String statement) {
|
||||
this.statement = checkNotNull(statement, "statement");
|
||||
public InterpretableStatement(String... statements) {
|
||||
this.statements = checkNotNull(statements, "statements");
|
||||
}
|
||||
|
||||
public String render(OsFamily family) {
|
||||
return Utils.replaceTokens(statement, ShellToken.tokenValueMap(family));
|
||||
return Utils
|
||||
.replaceTokens(Joiner.on(ShellToken.LF.to(family)).join(statements), ShellToken.tokenValueMap(family));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,7 +55,7 @@ public class InterpretableStatement implements Statement {
|
|||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((statement == null) ? 0 : statement.hashCode());
|
||||
result = prime * result + Arrays.hashCode(statements);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -64,10 +68,7 @@ public class InterpretableStatement implements Statement {
|
|||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
InterpretableStatement other = (InterpretableStatement) obj;
|
||||
if (statement == null) {
|
||||
if (other.statement != null)
|
||||
return false;
|
||||
} else if (!statement.equals(other.statement))
|
||||
if (!Arrays.equals(statements, other.statements))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -41,13 +41,17 @@ public class Statements {
|
|||
return new Call(function, args);
|
||||
}
|
||||
|
||||
public static Statement createRunScript(String instanceName, Iterable<String> exports,
|
||||
String pwd, String... execLines) {// TODO: convert so
|
||||
public static Statement createFile(String path, Iterable<String> lines) {
|
||||
return new CreateFile(path, lines);
|
||||
}
|
||||
|
||||
public static Statement createRunScript(String instanceName, Iterable<String> exports, String pwd,
|
||||
Iterable<Statement> statements) {// TODO: convert so
|
||||
// that
|
||||
// createRunScript
|
||||
// can take from a
|
||||
// variable
|
||||
return new CreateRunScript(instanceName, exports, pwd, execLines);
|
||||
return new CreateRunScript(instanceName, exports, pwd, statements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,12 +93,12 @@ public class Statements {
|
|||
}
|
||||
|
||||
/**
|
||||
* statement can have multiple newlines, note you should use {@code {lf} } to be portable
|
||||
* statement can have multiple newlines, note you should use {@code lf} to be portable
|
||||
*
|
||||
* @see ShellToken
|
||||
*/
|
||||
public static Statement interpret(String portableStatement) {
|
||||
return new InterpretableStatement(portableStatement);
|
||||
public static Statement interpret(String ... portableStatements) {
|
||||
return new InterpretableStatement(portableStatements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -43,15 +43,14 @@ public class SwitchArg implements Statement {
|
|||
|
||||
private static final String INDENT = " ";
|
||||
|
||||
public static final Map<OsFamily, String> OS_TO_SWITCH_PATTERN = ImmutableMap.of(OsFamily.UNIX,
|
||||
"case ${arg} in\n", OsFamily.WINDOWS, "goto CASE_%{arg}\r\n");
|
||||
public static final Map<OsFamily, String> OS_TO_SWITCH_PATTERN = ImmutableMap.of(OsFamily.UNIX, "case ${arg} in\n",
|
||||
OsFamily.WINDOWS, "goto CASE_%{arg}\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_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", OsFamily.WINDOWS,
|
||||
":CASE_{value}\r\n{action}GOTO END_SWITCH\r\n");
|
||||
"{value})\n{action};;\n", OsFamily.WINDOWS, ":CASE_{value}\r\n{action}GOTO END_SWITCH\r\n");
|
||||
|
||||
private final int arg;
|
||||
|
||||
|
@ -84,26 +83,31 @@ public class SwitchArg implements Statement {
|
|||
public String render(OsFamily family) {
|
||||
StringBuilder switchClause = new StringBuilder();
|
||||
addArgValidation(switchClause, family);
|
||||
switchClause.append(Utils.replaceTokens(OS_TO_SWITCH_PATTERN.get(family), ImmutableMap.of(
|
||||
"arg", arg + "")));
|
||||
switchClause.append(Utils.replaceTokens(OS_TO_SWITCH_PATTERN.get(family), ImmutableMap.of("arg", arg + "")));
|
||||
|
||||
for (Entry<String, Statement> entry : valueToActions.entrySet()) {
|
||||
|
||||
StringBuilder actionBuilder = new StringBuilder();
|
||||
boolean shouldIndent = true;
|
||||
boolean inRunScript = false;
|
||||
for (String line : Splitter.on(ShellToken.LF.to(family)).split(
|
||||
entry.getValue().render(family))) {
|
||||
if (!inRunScript)
|
||||
boolean inCreateFile = false;
|
||||
for (String line : Splitter.on(ShellToken.LF.to(family)).split(entry.getValue().render(family))) {
|
||||
if (shouldIndent)
|
||||
actionBuilder.append(INDENT);
|
||||
actionBuilder.append(line).append(ShellToken.LF.to(family));
|
||||
if (line.indexOf(CreateRunScript.MARKER) != -1) {
|
||||
inRunScript = inRunScript ? false : true;
|
||||
|
||||
}
|
||||
if (line.indexOf(CreateFile.MARKER) != -1) {
|
||||
inCreateFile = inCreateFile ? false : true;
|
||||
}
|
||||
actionBuilder.delete(actionBuilder.lastIndexOf(ShellToken.LF.to(family)), actionBuilder
|
||||
.length());
|
||||
switchClause.append(Utils.replaceTokens(OS_TO_CASE_PATTERN.get(family), ImmutableMap.of(
|
||||
"value", entry.getKey(), "action", actionBuilder.toString())));
|
||||
shouldIndent = !inCreateFile && !inRunScript;
|
||||
|
||||
}
|
||||
actionBuilder.delete(actionBuilder.lastIndexOf(ShellToken.LF.to(family)), actionBuilder.length());
|
||||
switchClause.append(Utils.replaceTokens(OS_TO_CASE_PATTERN.get(family), ImmutableMap.of("value", entry
|
||||
.getKey(), "action", actionBuilder.toString())));
|
||||
}
|
||||
|
||||
switchClause.append(OS_TO_END_SWITCH_PATTERN.get(family));
|
||||
|
@ -114,11 +118,9 @@ public class SwitchArg implements Statement {
|
|||
void addArgValidation(StringBuilder switchClause, OsFamily family) {
|
||||
if (family.equals(OsFamily.WINDOWS)) {
|
||||
for (String value : valueToActions.keySet()) {
|
||||
switchClause.append("if not \"%").append(arg).append(
|
||||
String.format("\" == \"%s\" ", value));
|
||||
switchClause.append("if not \"%").append(arg).append(String.format("\" == \"%s\" ", value));
|
||||
}
|
||||
switchClause.append("(\r\n set EXCEPTION=bad argument: %").append(arg)
|
||||
.append(" not in ");
|
||||
switchClause.append("(\r\n set EXCEPTION=bad argument: %").append(arg).append(" not in ");
|
||||
switchClause.append(Joiner.on(" ").join(valueToActions.keySet()));
|
||||
switchClause.append("\r\n goto abort\r\n)\r\n");
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.jclouds.scriptbuilder;
|
||||
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.call;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -26,9 +28,12 @@ import java.net.MalformedURLException;
|
|||
|
||||
import org.jclouds.scriptbuilder.domain.OsFamily;
|
||||
import org.jclouds.scriptbuilder.domain.ShellToken;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.io.Resources;
|
||||
|
@ -40,21 +45,20 @@ import com.google.common.io.Resources;
|
|||
*/
|
||||
public class InitBuilderTest {
|
||||
|
||||
InitBuilder testInitBuilder = new InitBuilder("mkebsboot", "/mnt/tmp", "/mnt/tmp", ImmutableMap
|
||||
.of("tmpDir", "/mnt/tmp"), "find /");
|
||||
InitBuilder testInitBuilder = new InitBuilder("mkebsboot", "/mnt/tmp", "/mnt/tmp", ImmutableMap.of("tmpDir",
|
||||
"/mnt/tmp"), ImmutableList.<Statement> of(
|
||||
createFile("{tmp}{fs}{uid}{fs}scripttest{fs}temp.txt", ImmutableList.<String> of("hello world")), call("find /")));
|
||||
|
||||
@Test
|
||||
public void testBuildSimpleWindows() throws MalformedURLException, IOException {
|
||||
assertEquals(testInitBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_init."
|
||||
+ ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
assertEquals(testInitBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_init." + ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSimpleUNIX() throws MalformedURLException, IOException {
|
||||
assertEquals(testInitBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_init."
|
||||
+ ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
assertEquals(testInitBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_init." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -64,9 +68,13 @@ public class InitBuilderTest {
|
|||
"mkebsboot",// name of the script
|
||||
"/tmp",// working directory
|
||||
"/tmp/logs",// location of stdout.log and stderr.log
|
||||
ImmutableMap.of("imageDir", "/mnt/tmp", "ebsDevice", "/dev/sdh",
|
||||
"ebsMountPoint", "/mnt/ebs"),// variables used inside of the script
|
||||
"echo creating a filesystem and mounting the ebs volume",// what to execute
|
||||
ImmutableMap.of("imageDir", "/mnt/tmp", "ebsDevice", "/dev/sdh", "ebsMountPoint", "/mnt/ebs"),// variables
|
||||
// used
|
||||
// inside
|
||||
// of
|
||||
// the
|
||||
// script
|
||||
ImmutableList.<Statement> of(Statements.interpret("echo creating a filesystem and mounting the ebs volume",// what to execute
|
||||
"{md} {varl}IMAGE_DIR{varr} {varl}EBS_MOUNT_POINT{varr}",
|
||||
"rm -rf {varl}IMAGE_DIR{varr}/*",
|
||||
"yes| mkfs -t ext3 {varl}EBS_DEVICE{varr} 2>&-",
|
||||
|
@ -75,17 +83,11 @@ public class InitBuilderTest {
|
|||
"rsync -ax --exclude /ubuntu/.bash_history --exclude /home/*/.bash_history --exclude /etc/ssh/ssh_host_* --exclude /etc/ssh/moduli --exclude /etc/udev/rules.d/*persistent-net.rules --exclude /var/lib/ec2/* --exclude=/mnt/* --exclude=/proc/* --exclude=/tmp/* --exclude=/dev/log / {varl}IMAGE_DIR{varr}",
|
||||
"echo preparing the local working copy",
|
||||
"touch {varl}IMAGE_DIR{varr}/etc/init.d/ec2-init-user-data",
|
||||
"echo copying the local working copy to the ebs mount",
|
||||
"{cd} {varl}IMAGE_DIR{varr}",
|
||||
"tar -cSf - * | tar xf - -C {varl}EBS_MOUNT_POINT{varr}",
|
||||
"echo size of ebs",
|
||||
"du -sk {varl}EBS_MOUNT_POINT{varr}",
|
||||
"echo size of source",
|
||||
"du -sk {varl}IMAGE_DIR{varr}",
|
||||
"rm -rf {varl}IMAGE_DIR{varr}/*",
|
||||
"umount {varl}EBS_MOUNT_POINT{varr}", "echo ----COMPLETE----")
|
||||
.build(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(
|
||||
Resources.getResource("test_ebs." + ShellToken.SH.to(OsFamily.UNIX)),
|
||||
Charsets.UTF_8)));
|
||||
"echo copying the local working copy to the ebs mount", "{cd} {varl}IMAGE_DIR{varr}",
|
||||
"tar -cSf - * | tar xf - -C {varl}EBS_MOUNT_POINT{varr}", "echo size of ebs",
|
||||
"du -sk {varl}EBS_MOUNT_POINT{varr}", "echo size of source", "du -sk {varl}IMAGE_DIR{varr}",
|
||||
"rm -rf {varl}IMAGE_DIR{varr}/*", "umount {varl}EBS_MOUNT_POINT{varr}", "echo ----COMPLETE----")
|
||||
)).build(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_ebs." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.jclouds.scriptbuilder;
|
||||
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.call;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.findPid;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.interpret;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.kill;
|
||||
|
@ -57,28 +58,27 @@ public class ScriptBuilderTest {
|
|||
ImmutableMap
|
||||
.of(
|
||||
"start",
|
||||
newStatementList(
|
||||
call("default"),
|
||||
newStatementList(call("default"),
|
||||
interpret("echo start {varl}RUNTIME{varr}{lf}")),
|
||||
"stop",
|
||||
newStatementList(
|
||||
call("default"),
|
||||
newStatementList(call("default"),
|
||||
interpret("echo stop {varl}RUNTIME{varr}{lf}")),
|
||||
"status",
|
||||
newStatementList(interpret("echo {vq}the following should be []: [{varl}RUNTIME{varr}]{vq}{lf}")))));
|
||||
newStatementList(
|
||||
createFile("{tmp}{fs}{uid}{fs}scripttest{fs}temp.txt",
|
||||
ImmutableList.<String> of("hello world")),
|
||||
interpret("echo {vq}the following should be []: [{varl}RUNTIME{varr}]{vq}{lf}")))));
|
||||
|
||||
@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)));
|
||||
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)));
|
||||
assertEquals(testScriptBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_script." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
ScriptBuilder findPidBuilder = new ScriptBuilder().addStatement(findPid("{args}")).addStatement(
|
||||
|
@ -86,40 +86,35 @@ public class ScriptBuilderTest {
|
|||
|
||||
@Test
|
||||
public void testFindPidWindows() throws MalformedURLException, IOException {
|
||||
assertEquals(findPidBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_find_pid."
|
||||
+ ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
assertEquals(findPidBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_find_pid." + ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPidUNIX() throws MalformedURLException, IOException {
|
||||
assertEquals(findPidBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_find_pid."
|
||||
+ ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
assertEquals(findPidBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_find_pid." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
ScriptBuilder seekAndDestroyBuilder = new ScriptBuilder().addStatement(findPid("{args}"))
|
||||
.addStatement(kill());
|
||||
ScriptBuilder seekAndDestroyBuilder = new ScriptBuilder().addStatement(findPid("{args}")).addStatement(kill());
|
||||
|
||||
@Test
|
||||
public void testSeekAndDestroyWindows() throws MalformedURLException, IOException {
|
||||
assertEquals(seekAndDestroyBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_seek_and_destroy."
|
||||
+ ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
assertEquals(seekAndDestroyBuilder.build(OsFamily.WINDOWS), CharStreams.toString(Resources.newReaderSupplier(
|
||||
Resources.getResource("test_seek_and_destroy." + ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekAndDestroyUNIX() throws MalformedURLException, IOException {
|
||||
assertEquals(seekAndDestroyBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_seek_and_destroy."
|
||||
+ ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
assertEquals(seekAndDestroyBuilder.build(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(
|
||||
Resources.getResource("test_seek_and_destroy." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchOn() {
|
||||
ScriptBuilder builder = new ScriptBuilder();
|
||||
builder.addStatement(switchArg(1, ImmutableMap.of("start", interpret("echo started{lf}"),
|
||||
"stop", interpret("echo stopped{lf}"))));
|
||||
builder.addStatement(switchArg(1, ImmutableMap.of("start", interpret("echo started{lf}"), "stop",
|
||||
interpret("echo stopped{lf}"))));
|
||||
assertEquals(builder.statements, ImmutableList.of(new SwitchArg(1, ImmutableMap.of("start",
|
||||
interpret("echo started{lf}"), "stop", interpret("echo stopped{lf}")))));
|
||||
}
|
||||
|
@ -134,8 +129,7 @@ public class ScriptBuilderTest {
|
|||
public void testExport() {
|
||||
ScriptBuilder builder = new ScriptBuilder();
|
||||
builder.addEnvironmentVariableScope("default", ImmutableMap.of("javaHome", "/apps/jdk1.6"));
|
||||
assertEquals(builder.variableScopes, ImmutableMap.of("default", ImmutableMap.of("javaHome",
|
||||
"/apps/jdk1.6")));
|
||||
assertEquals(builder.variableScopes, ImmutableMap.of("default", ImmutableMap.of("javaHome", "/apps/jdk1.6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
|
||||
*
|
||||
* ====================================================================
|
||||
* Licensed 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.scriptbuilder.domain;
|
||||
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
/**
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Test(groups = "unit", testName = "scriptbuilder.CreateFileTest")
|
||||
public class CreateFileTest {
|
||||
Statement statement = createFile("{root}etc{fs}chef{fs}client.rb", ImmutableList.of("log_level :info",
|
||||
"log_location STDOUT", String.format("chef_server_url \"%s\"", "http://localhost:4000")));
|
||||
|
||||
public void testUNIX() throws IOException {
|
||||
assertEquals(statement.render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("client_rb." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
public void testWINDOWS() throws IOException {
|
||||
assertEquals(statement.render(OsFamily.WINDOWS), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("client_rb." + ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
public void testRedirectGuard() {
|
||||
assertEquals(CreateFile.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo>>"), "foo>>");
|
||||
assertEquals(CreateFile.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo0>>"), "foo0 >>");
|
||||
assertEquals(CreateFile.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo1>>"), "foo1 >>");
|
||||
assertEquals(CreateFile.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo2>>"), "foo2 >>");
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.jclouds.scriptbuilder.domain;
|
||||
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.call;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.createRunScript;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
|
@ -36,30 +38,32 @@ import com.google.common.io.Resources;
|
|||
*/
|
||||
@Test(groups = "unit", testName = "scriptbuilder.CreateRunScriptTest")
|
||||
public class CreateRunScriptTest {
|
||||
Statement statement = createRunScript("yahooprod", ImmutableList.<String> of("javaHome"),
|
||||
"{tmp}{fs}{uid}{fs}scripttest", "echo hello",
|
||||
"echo {varl}JAVA_HOME{varr}{fs}bin{fs}java -DinstanceName={varl}INSTANCE_NAME{varr} myServer.Main");
|
||||
Statement statement = createRunScript(
|
||||
"yahooprod",
|
||||
ImmutableList.<String> of("javaHome"),
|
||||
"{tmp}{fs}{uid}{fs}scripttest",
|
||||
ImmutableList
|
||||
.<Statement> of(
|
||||
call("echo hello"),
|
||||
createFile("{tmp}{fs}{uid}{fs}scripttest{fs}temp.txt", ImmutableList
|
||||
.<String> of("hello world")),
|
||||
call("echo {varl}JAVA_HOME{varr}{fs}bin{fs}java -DinstanceName={varl}INSTANCE_NAME{varr} myServer.Main")));
|
||||
|
||||
public void testUNIX() throws IOException {
|
||||
assertEquals(statement.render(OsFamily.UNIX), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_runrun."
|
||||
+ ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
assertEquals(statement.render(OsFamily.UNIX), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_runrun." + ShellToken.SH.to(OsFamily.UNIX)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
public void testWINDOWS() throws IOException {
|
||||
assertEquals(statement.render(OsFamily.WINDOWS), CharStreams.toString(Resources
|
||||
.newReaderSupplier(Resources.getResource("test_runrun."
|
||||
+ ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
assertEquals(statement.render(OsFamily.WINDOWS), CharStreams.toString(Resources.newReaderSupplier(Resources
|
||||
.getResource("test_runrun." + ShellToken.SH.to(OsFamily.WINDOWS)), Charsets.UTF_8)));
|
||||
}
|
||||
|
||||
public void testRedirectGuard() {
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo>>"), "foo>>");
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo0>>"),
|
||||
"foo0 >>");
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo1>>"),
|
||||
"foo1 >>");
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo2>>"),
|
||||
"foo2 >>");
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo0>>"), "foo0 >>");
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo1>>"), "foo1 >>");
|
||||
assertEquals(CreateRunScript.addSpaceToEnsureWeDontAccidentallyRedirectFd("foo2>>"), "foo2 >>");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,10 +19,15 @@
|
|||
|
||||
package org.jclouds.scriptbuilder.domain;
|
||||
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.createFile;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.interpret;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import static org.jclouds.scriptbuilder.domain.Statements.*;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
|
@ -32,9 +37,10 @@ import com.google.common.collect.ImmutableMap;
|
|||
public class SwitchArgTest {
|
||||
|
||||
public void testSwitchArgUNIX() {
|
||||
assertEquals(new SwitchArg(1, ImmutableMap.of("0", interpret("echo hello zero{lf}"), "1",
|
||||
interpret("echo hello one{lf}"))).render(OsFamily.UNIX),
|
||||
"case $1 in\n0)\n echo hello zero\n ;;\n1)\n echo hello one\n ;;\nesac\n");
|
||||
assertEquals(new SwitchArg(1, ImmutableMap.of("0", newStatementList(createFile(
|
||||
"{tmp}{fs}{uid}{fs}scripttest{fs}temp.txt", Collections.singleton("hello world")),
|
||||
interpret("echo hello zero{lf}")), "1", interpret("echo hello one{lf}"))).render(OsFamily.UNIX),
|
||||
"case $1 in\n0)\n cat > /tmp/$USER/scripttest/temp.txt <<'END_OF_FILE'\nhello world\nEND_OF_FILE\n echo hello zero\n ;;\n1)\n echo hello one\n ;;\nesac\n");
|
||||
}
|
||||
|
||||
public void testSwitchArgWindows() {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
del c:\etc\chef\client.rb 2>NUL
|
||||
echo log_level :info>>c:\etc\chef\client.rb
|
||||
echo log_location STDOUT>>c:\etc\chef\client.rb
|
||||
echo chef_server_url "http://localhost:4000">>c:\etc\chef\client.rb
|
|
@ -0,0 +1,5 @@
|
|||
cat > /etc/chef/client.rb <<'END_OF_FILE'
|
||||
log_level :info
|
||||
log_location STDOUT
|
||||
chef_server_url "http://localhost:4000"
|
||||
END_OF_FILE
|
|
@ -80,7 +80,17 @@ goto CASE_%1
|
|||
echo set INSTANCE_HOME=%INSTANCE_HOME%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set LOG_DIR=%LOG_DIR%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo cd /d %%INSTANCE_HOME%%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo find />>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
md %INSTANCE_HOME%
|
||||
del %INSTANCE_HOME%\mkebsboot.cmd 2>NUL
|
||||
echo @echo off>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo title mkebsboot>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set PATH=c:\windows\;C:\windows\system32;c:\windows\system32\wbem>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set INSTANCE_NAME=mkebsboot>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set TMP_DIR=%TMP_DIR%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set INSTANCE_NAME=%INSTANCE_NAME%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set INSTANCE_HOME=%INSTANCE_HOME%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo set LOG_DIR=%LOG_DIR%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo cd /d %%INSTANCE_HOME%%>>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
echo exit /b 0 >>%INSTANCE_HOME%\mkebsboot.cmd
|
||||
GOTO END_SWITCH
|
||||
:CASE_status
|
||||
|
|
|
@ -78,7 +78,12 @@ END_OF_SCRIPT
|
|||
# add desired commands from the user
|
||||
cat >> $INSTANCE_HOME/mkebsboot.sh <<'END_OF_SCRIPT'
|
||||
cd $INSTANCE_HOME
|
||||
find /
|
||||
cat > /tmp/$USER/scripttest/temp.txt <<'END_OF_FILE'
|
||||
hello world
|
||||
END_OF_FILE
|
||||
|
||||
find / || exit 1
|
||||
|
||||
END_OF_SCRIPT
|
||||
|
||||
# add runscript footer
|
||||
|
|
|
@ -6,6 +6,12 @@ echo set PATH=c:\windows\;C:\windows\system32;c:\windows\system32\wbem>>%TEMP%\%
|
|||
echo set INSTANCE_NAME=yahooprod>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo set JAVA_HOME=%JAVA_HOME%>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo cd /d %TEMP%\%USERNAME%\scripttest>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo echo hello>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo echo %%JAVA_HOME%%\bin\java -DinstanceName=%%INSTANCE_NAME%% myServer.Main>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
md %TEMP%\%USERNAME%\scripttest
|
||||
del %TEMP%\%USERNAME%\scripttest\yahooprod.cmd 2>NUL
|
||||
echo @echo off>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo title yahooprod>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo set PATH=c:\windows\;C:\windows\system32;c:\windows\system32\wbem>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo set INSTANCE_NAME=yahooprod>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo set JAVA_HOME=%JAVA_HOME%>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo cd /d %TEMP%\%USERNAME%\scripttest>>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
echo exit /b 0 >>%TEMP%\%USERNAME%\scripttest\yahooprod.cmd
|
||||
|
|
|
@ -15,8 +15,14 @@ END_OF_SCRIPT
|
|||
# add desired commands from the user
|
||||
cat >> /tmp/$USER/scripttest/yahooprod.sh <<'END_OF_SCRIPT'
|
||||
cd /tmp/$USER/scripttest
|
||||
echo hello
|
||||
echo $JAVA_HOME/bin/java -DinstanceName=$INSTANCE_NAME myServer.Main
|
||||
echo hello || return 1
|
||||
|
||||
cat > /tmp/$USER/scripttest/temp.txt <<'END_OF_FILE'
|
||||
hello world
|
||||
END_OF_FILE
|
||||
|
||||
echo $JAVA_HOME/bin/java -DinstanceName=$INSTANCE_NAME myServer.Main || return 1
|
||||
|
||||
END_OF_SCRIPT
|
||||
|
||||
# add runscript footer
|
||||
|
|
|
@ -28,6 +28,8 @@ goto CASE_%1
|
|||
echo stop %RUNTIME%
|
||||
GOTO END_SWITCH
|
||||
:CASE_status
|
||||
del %TEMP%\%USERNAME%\scripttest\temp.txt 2>NUL
|
||||
echo hello world>>%TEMP%\%USERNAME%\scripttest\temp.txt
|
||||
echo the following should be []: [%RUNTIME%]
|
||||
GOTO END_SWITCH
|
||||
:END_SWITCH
|
||||
|
|
|
@ -22,6 +22,9 @@ stop)
|
|||
echo stop $RUNTIME
|
||||
;;
|
||||
status)
|
||||
cat > /tmp/$USER/scripttest/temp.txt <<'END_OF_FILE'
|
||||
hello world
|
||||
END_OF_FILE
|
||||
echo "the following should be []: [$RUNTIME]"
|
||||
;;
|
||||
esac
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.jclouds.scriptbuilder.domain.OsFamily;
|
|||
import org.jclouds.scriptbuilder.domain.ShellToken;
|
||||
import org.jclouds.scriptbuilder.domain.Statement;
|
||||
import org.jclouds.scriptbuilder.domain.StatementList;
|
||||
import org.jclouds.scriptbuilder.domain.Statements;
|
||||
import org.jclouds.tools.ant.util.SSHExecute;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
@ -130,8 +131,7 @@ public class SSHJava extends Java {
|
|||
if (remotedir == null)
|
||||
remotedir = new File(remotebase, id);
|
||||
|
||||
String command = createInitScript(osFamily, id, remotedir.getAbsolutePath(), env,
|
||||
getCommandLine());
|
||||
String command = createInitScript(osFamily, id, remotedir.getAbsolutePath(), env, getCommandLine());
|
||||
|
||||
try {
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(new File(localDirectory, "init."
|
||||
|
@ -159,8 +159,8 @@ public class SSHJava extends Java {
|
|||
File source = new File(entry.getKey());
|
||||
if (source.isDirectory()) {
|
||||
set.setDir(new File(entry.getKey()));
|
||||
mkdirAndCopyTo(remotebase.getAbsolutePath() + ShellToken.FS.to(osFamily)
|
||||
+ entry.getValue(), ImmutableList.of(set));
|
||||
mkdirAndCopyTo(remotebase.getAbsolutePath() + ShellToken.FS.to(osFamily) + entry.getValue(), ImmutableList
|
||||
.of(set));
|
||||
} else {
|
||||
String destination = remotebase.getAbsolutePath() + ShellToken.FS.to(osFamily)
|
||||
+ new File(entry.getValue()).getParent();
|
||||
|
@ -179,19 +179,16 @@ public class SSHJava extends Java {
|
|||
}
|
||||
|
||||
if (getCommandLine().getBootclasspath() != null) {
|
||||
copyPathTo(getCommandLine().getBootclasspath(), remotedir.getAbsolutePath()
|
||||
+ "/bootclasspath");
|
||||
copyPathTo(getCommandLine().getBootclasspath(), remotedir.getAbsolutePath() + "/bootclasspath");
|
||||
}
|
||||
|
||||
if (osFamily == OsFamily.UNIX) {
|
||||
sshexec(exec("chmod 755 " + remotedir.getAbsolutePath() + "{fs}init.{sh}")
|
||||
.render(osFamily));
|
||||
sshexec(exec("chmod 755 " + remotedir.getAbsolutePath() + "{fs}init.{sh}").render(osFamily));
|
||||
}
|
||||
|
||||
Statement statement = new StatementList(exec("{cd} " + remotedir.getAbsolutePath()),
|
||||
exec(remotedir.getAbsolutePath() + "{fs}init.{sh} init"), exec(remotedir
|
||||
Statement statement = new StatementList(exec("{cd} " + remotedir.getAbsolutePath()), exec(remotedir
|
||||
.getAbsolutePath()
|
||||
+ "{fs}init.{sh} run"));
|
||||
+ "{fs}init.{sh} init"), exec(remotedir.getAbsolutePath() + "{fs}init.{sh} run"));
|
||||
try {
|
||||
return sshexecRedirectStreams(statement);
|
||||
} catch (IOException e) {
|
||||
|
@ -270,13 +267,11 @@ public class SSHJava extends Java {
|
|||
}
|
||||
|
||||
private String getScpDir(String path) {
|
||||
return String.format("%s:%s@%s:%s", userInfo.getName(),
|
||||
userInfo.getKeyfile() == null ? userInfo.getPassword() : userInfo.getPassphrase(),
|
||||
scp.getHost(), path);
|
||||
return String.format("%s:%s@%s:%s", userInfo.getName(), userInfo.getKeyfile() == null ? userInfo.getPassword()
|
||||
: userInfo.getPassphrase(), scp.getHost(), path);
|
||||
}
|
||||
|
||||
void resetPathToUnderPrefixIfExistsAndIsFileIfNotExistsAddAsIs(Path path, String prefix,
|
||||
StringBuilder destination) {
|
||||
void resetPathToUnderPrefixIfExistsAndIsFileIfNotExistsAddAsIs(Path path, String prefix, StringBuilder destination) {
|
||||
if (path == null)
|
||||
return;
|
||||
String[] paths = path.list();
|
||||
|
@ -324,8 +319,7 @@ public class SSHJava extends Java {
|
|||
for (Entry<String, String> entry : shiftMap.entrySet()) {
|
||||
if (in.startsWith(entry.getKey())) {
|
||||
log("match shift map: " + entry.getKey(), Project.MSG_DEBUG);
|
||||
in = remotebase + ShellToken.FS.to(osFamily) + entry.getValue()
|
||||
+ in.substring(entry.getKey().length());
|
||||
in = remotebase + ShellToken.FS.to(osFamily) + entry.getValue() + in.substring(entry.getKey().length());
|
||||
}
|
||||
}
|
||||
for (Entry<String, String> entry : replace.entrySet()) {
|
||||
|
@ -381,7 +375,7 @@ public class SSHJava extends Java {
|
|||
}
|
||||
|
||||
InitBuilder testInitBuilder = new InitBuilder(id, basedir, basedir, envVariables,
|
||||
commandBuilder.toString());
|
||||
ImmutableList.<Statement> of(Statements.interpret( commandBuilder.toString())));
|
||||
return testInitBuilder.build(osFamily);
|
||||
}
|
||||
|
||||
|
@ -560,11 +554,10 @@ public class SSHJava extends Java {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SSHJava [append=" + append + ", env=" + env + ", errorFile=" + errorFile
|
||||
+ ", errorProperty=" + errorProperty + ", localDirectory=" + localDirectory
|
||||
+ ", osFamily=" + osFamily + ", outputFile=" + outputFile + ", outputProperty="
|
||||
+ outputProperty + ", remoteDirectory=" + remotebase + ", userInfo=" + userInfo
|
||||
+ "]";
|
||||
return "SSHJava [append=" + append + ", env=" + env + ", errorFile=" + errorFile + ", errorProperty="
|
||||
+ errorProperty + ", localDirectory=" + localDirectory + ", osFamily=" + osFamily + ", outputFile="
|
||||
+ outputFile + ", outputProperty=" + outputProperty + ", remoteDirectory=" + remotebase + ", userInfo="
|
||||
+ userInfo + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue