blob: 93468d3859df22eee500415ff8c46fffb4aa3fc9 [file] [log] [blame]
/*
* 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.apache.brooklyn.util.ssh;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.text.Identifiers;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes;
import org.apache.brooklyn.util.time.Duration;
import com.google.common.annotations.Beta;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
public class BashCommands {
/**
* Returns a string for checking whether the given executable is available,
* and installing it if necessary.
* <p/>
* Uses {@link #installPackage} and accepts the same flags e.g. for apt, yum, rpm.
*/
public static String installExecutable(Map<?,?> flags, String executable) {
return onlyIfExecutableMissing(executable, installPackage(flags, executable));
}
public static String installExecutable(String executable) {
return installExecutable(MutableMap.of(), executable);
}
/**
* Returns a command with all output redirected to /dev/null
*/
public static String quiet(String command) {
return format("(%s > /dev/null 2>&1)", command);
}
/**
* Returns a command that always exits successfully
*/
public static String ok(String command) {
return String.format("(%s || true)", command);
}
/**
* Returns a command for safely running as root, using {@code sudo}.
* <p/>
* Ensuring non-blocking if password not set by using
* {@code -n} which means to exit if password required
* (this is unsupported in Ubuntu 8 but all modern OS's seem okay with this!),
* and (perhaps unnecessarily ?)
* {@code -S} which reads from stdin (routed to {@code /dev/null}, it was claimed here previously, though I'm not sure?).
* <p/>
* Also specify {@code -E} to pass the parent environment in.
* <p/>
* If already root, simply runs the command, wrapped in brackets in case it is backgrounded.
* <p/>
* The command is not quoted or escaped in any ways.
* If you are doing privileged redirect you may need to pass e.g. "bash -c 'echo hi > file'".
* <p/>
* If null is supplied, it is returned (sometimes used to indicate no command desired).
*/
public static String sudo(String command) {
if (command.startsWith("( ") || command.endsWith(" &"))
return sudoNew(command);
else
return sudoOld(command);
}
// TODO would like to move away from sudoOld -- but needs extensive testing!
private static String sudoOld(String command) {
if (command==null) return null;
// some OS's (which?) fail if you try running sudo when you're already root (dumb but true)
return format("( if test \"$UID\" -eq 0; then ( %s ); else sudo -E -n -S -- %s; fi )", command, command);
}
private static String sudoNew(String command) {
if (command==null) return null;
// on some OS's e.g. Centos 6.5 in SL, sudo -- X tries to run X as a literal argument;
// in particular "( echo foo && echo bar )" fails when passed as an argument in that way,
// but works if passed to bash -c;
// but others e.g. OS X fail if you say sudo -- bash -c "( echo foo )" ... not liking the parentheses
// piping to sudo bash seems the most reliable way
return "( if test \"$UID\" -eq 0; then ( "+command+" ); else "
+ "echo " + BashStringEscapes.wrapBash(command) + " | "
+ "sudo -E -n -S -s -- bash"
+ " ; fi )";
}
/** sudo to a given user and run the indicated command;
* @deprecated since 0.7.0 semantics of this are fiddly, e.g. whether user gets their environment */
@Beta
public static String sudoAsUser(String user, String command) {
return sudoAsUserOld(user, command);
}
private static String sudoAsUserOld(String user, String command) {
if (command == null) return null;
return format("{ sudo -E -n -u %s -s -- %s ; }", user, command);
}
// TODO would like to move away from sudoOld -- but needs extensive testing!
// private static String sudoAsUserNew(String user, String command) {
// if (command == null) return null;
// // no -E, run with permissions of this user
// // FIXME still doesn't always work e.g. doesn't have path of user
// // (Alex says: can't find any combinations which work reliably)
// return "{ sudo -n -S -i -u "+user+" -- "+BashStringEscapes.wrapBash(command)+" ; }";
// }
public static String addSbinPathCommand() {
return "export PATH=" + sbinPath();
}
public static String sbinPath() {
return "$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
}
/** executes a command, then as user tees the output to the given file.
* useful e.g. for appending to a file which is only writable by root or a priveleged user. */
public static String executeCommandThenAsUserTeeOutputToFile(String commandWhoseOutputToWrite, String user, String file) {
return format("{ %s | sudo -E -n -u %s -s -- tee -a %s ; }",
commandWhoseOutputToWrite, user, file);
}
/**
* Some machines require a TTY for sudo. Brooklyn by default does not use a TTY
* so that it can get separate STDERR and STDOUT streams. You can enable a TTY as an
* option to every SSH command, or you can do it once and modify the machine so that
* a TTY is not subsequently required. If this task has already been executed it
* will try to detect the changes and do nothing.
* <p>
* This command must be run with allocatePTY set as a flag to ssh.
* See {@link SshTasks#dontRequireTtyForSudo(SshMachineLocation, OnFailingTask)} which sets that up.
* <p>
* Having a TTY for sudo seems like another case of imaginary security which is just irritating.
* Like water restrictions at airport security.
*/
public static String dontRequireTtyForSudo() {
String sudoersFileName = "/etc/sudoers";
String tmpSuffix = Identifiers.makeRandomLowercaseId(6); // Avoid clobbering
// Visudo's quiet mode (-q) is not enabled. visudo's output is used for diagnostic purposes
return ifFileExistsElse0(sudoersFileName,
alternatives(
sudo(format("grep brooklyn-removed-require-tty %s", sudoersFileName)),
chainGroup(
sudo(format("cp %1$s %1$s.%2$s", sudoersFileName, tmpSuffix)),
sudo(format("sed -i.brooklyn.bak 's/.*requiretty.*/#brooklyn-removed-require-tty/' %1$s.%2$s", sudoersFileName, tmpSuffix)),
sudo(format("visudo -c -f %1$s.%2$s", sudoersFileName, tmpSuffix)),
sudo(format("mv %1$s.%2$s %1$s", sudoersFileName, tmpSuffix)))));
}
/** generates ~/.ssh/id_rsa if that file does not exist */
public static String generateKeyInDotSshIdRsaIfNotThere() {
return "[ -f ~/.ssh/id_rsa ] || ( mkdir -p ~/.ssh ; chmod 700 ~/.ssh ; ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa )";
}
// TODO a builder would be better than these ifBlahExistsElseBlah methods!
// (ideally formatting better also; though maybe SshTasks would be better?)
/**
* Returns a command that runs only if the specified file (or link or directory) exists;
* if the command runs and fails that exit is preserved (but if the file does not exist exit code is zero).
* Executed as { { not-file-exists && ok ; } || command ; } for portability.
* ("if [ ... ] ; then xxx ; else xxx ; fi" syntax is not quite as portable, I seem to recall (not sure, Alex Aug 2013).)
*/
public static String ifFileExistsElse0(String path, String command) {
return alternativesGroup(
chainGroup(format("test ! -e %s", path), "true"),
command);
}
/** as {@link #ifFileExistsElse0(String, String)} but returns non-zero if the test fails (also returns non-zero if the command fails,
* so you can't tell the difference :( -- we need if ; then ; else ; fi semantics for that I think, but not sure how portable that is) */
public static String ifFileExistsElse1(String path, String command) {
return chainGroup(format("test -e %s", path), command);
}
/**
* Returns a command that runs only if the specified executable exists on the path (using `which`).
* if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero).
* @see #ifFileExistsElse0(String, String) for implementation discussion, using <code>{ { test -z `which executable` && true ; } || command ; }
*/
public static String ifExecutableElse0(String executable, String command) {
return alternativesGroup(
chainGroup(format("test -z `which %s`", executable), "true"),
command);
}
/** as {@link #ifExecutableElse0(String, String)} but returns 1 if the test fails (also returns non-zero if the command fails) */
public static String ifExecutableElse1(String executable, String command) {
return chainGroup(format("which %s", executable), command);
}
/**
* Returns a command which
* executes <code>statement</code> only if <code>command</code> is NOT found in <code>$PATH</code>
*
* @param command
* @param statement
* @return command
*/
public static String ifNotExecutable(String command, String statement) {
return String.format("{ { test ! -z `which %s`; } || { %s; } }", command, statement);
}
/**
* Returns a command that runs only if the specified executable exists on the path (using `which`).
* if the command runs and fails that exit is preserved (but if the executable is not on the path exit code is zero).
* @see #ifFileExistsElse0(String, String) for implementation discussion, using <code>{ { test -z `which executable` && true ; } || command ; }
*/
public static String onlyIfExecutableMissing(String executable, String command) {
return alternativesGroup(format("which %s", executable), command);
}
/**
* @deprecated As of release 0.10.0, replaced by {@link #ifExecutableDoesNotExistElse(String, String, String)}
*/
@Deprecated
public static String ifExecutableElse(String command, String ifNotExist, String ifExist) {
return ifExecutableDoesNotExistElse(command, ifNotExist, ifExist);
}
public static String ifExecutableDoesNotExistElse(String command, String ifNotExist, String ifExist) {
return com.google.common.base.Joiner.on('\n').join(
ifExecutableDoesNotExistElse(command, ImmutableList.<String>of(ifNotExist), ImmutableList.<String>of(ifExist)));
}
/**
* @deprecated As of release 0.10.0, replaced by {@link #ifExecutableDoesNotExistElse(String, List, List)}
*/
@Deprecated
public static ImmutableList<String> ifExecutableElse(String command, List<String> ifNotExist, List<String> ifExist) {
return ifExecutableDoesNotExistElse(command, ifNotExist, ifExist);
}
public static ImmutableList<String> ifExecutableDoesNotExistElse(String command, List<String> ifNotExist, List<String> ifExist) {
return ImmutableList.<String>builder()
.add(String.format("if test -z `which %s`; then", command))
.addAll(ifNotExist)
.add("else")
.addAll(ifExist)
.add("fi")
.build();
}
/**
* Returns a sequence of chained commands that runs until one of them fails (i.e. joined by '&&')
* This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently.
* (Use {@link #chainGroup(Collection)} or {@link #chainSubshell(Collection)} to be clear.)
*/
public static String chain(Collection<String> commands) {
return "( " + Strings.join(commands, " && ") + " )";
}
/** Convenience for {@link #chain(Collection)} */
public static String chain(String ...commands) {
return "( " + Strings.join(commands, " && ") + " )";
}
/** As {@link #chain(Collection)}, but explicitly using { } grouping characters
* to ensure exits are propagated. */
public static String chainGroup(Collection<String> commands) {
// spaces required around curly braces
return "{ " + Strings.join(commands, " && ") + " ; }";
}
/** As {@link #chainGroup(Collection)} */
public static String chainGroup(String ...commands) {
return "{ " + Strings.join(commands, " && ") + " ; }";
}
/** As {@link #chain(Collection)}, but explicitly using ( ) grouping characters
* to ensure exits are caught. */
public static String chainSubshell(Collection<String> commands) {
// the spaces are not required, but it might be possible that a (( expr )) is interpreted differently
// (won't hurt to have the spaces in any case!)
return "( " + Strings.join(commands, " && ") + " )";
}
/** As {@link #chainSubshell(Collection)} */
public static String chainSubshell(String ...commands) {
return "( " + Strings.join(commands, " && ") + " )";
}
/**
* Returns a sequence of chained commands that runs until one of them succeeds (i.e. joined by '||').
* This currently runs as a subshell (so exits are swallowed) but behaviour may be changed imminently.
* (Use {@link #alternativesGroup(Collection)} or {@link #alternativesSubshell(Collection)} to be clear.)
*/
public static String alternatives(Collection<String> commands) {
return "( " + Strings.join(commands, " || ") + " )";
}
/** As {@link #alternatives(Collection)} */
public static String alternatives(String ...commands) {
return "( " + Strings.join(commands, " || ") + " )";
}
/** As {@link #alternatives(Collection)}, but explicitly using { } grouping characters
* to ensure exits are propagated. */
public static String alternativesGroup(Collection<String> commands) {
// spaces required around curly braces
return "{ " + Strings.join(commands, " || ") + " ; }";
}
/** As {@link #alternativesGroup(Collection)} */
public static String alternativesGroup(String ...commands) {
return "{ " + Strings.join(commands, " || ") + " ; }";
}
/** As {@link #alternatives(Collection)}, but explicitly using ( ) grouping characters
* to ensure exits are caught. */
public static String alternativesSubshell(Collection<String> commands) {
// the spaces are not required, but it might be possible that a (( expr )) is interpreted differently
// (won't hurt to have the spaces in any case!)
return "( " + Strings.join(commands, " || ") + " )";
}
/** As {@link #alternativesSubshell(Collection)} */
public static String alternativesSubshell(String ...commands) {
return "( " + Strings.join(commands, " || ") + " )";
}
/** returns the pattern formatted with the given arg if the arg is not null, otherwise returns null */
public static String formatIfNotNull(String pattern, Object arg) {
if (arg==null) return null;
return format(pattern, arg);
}
public static String installPackage(String packageDefaultName) {
return installPackage(MutableMap.of(), packageDefaultName);
}
/**
* Returns a command for installing the given package.
* <p>
* Warns, but does not fail or return non-zero if it ultimately fails.
* <p>
* Flags can contain common overrides for {@code apt}, {@code yum}, {@code port} and {@code brew}
* as the package names can be different for each of those. Setting the default package name to
* {@literal null} will use only the overridden package manager values. The {@code onlyifmissing} flag
* adds a check for an executable, and only attempts to install packages if it is not found.
* <pre>
* installPackage(ImmutableMap.of("yum", "openssl-devel", "apt", "openssl libssl-dev zlib1g-dev"), "libssl-devel");
* installPackage(ImmutableMap.of("apt", "libaio1"), null);
* installPackage(ImmutableMap.of("onlyifmissing", "curl"), "curl");
* </pre>
*/
public static String installPackage(Map<?,?> flags, String packageDefaultName) {
return installPackageOr(flags, packageDefaultName, null);
}
public static String installPackageOrFail(Map<?,?> flags, String packageDefaultName) {
return installPackageOr(flags, packageDefaultName, "exit 9");
}
public static String installPackageOr(Map<?,?> flags, String packageDefaultName, String optionalCommandToRunIfNone) {
String ifMissing = (String) flags.get("onlyifmissing");
String zypperInstall = formatIfNotNull("zypper --non-interactive --no-gpg-checks install %s", getFlag(flags, "zypper", packageDefaultName));
String aptInstall = formatIfNotNull("apt-get install -y --allow-unauthenticated %s", getFlag(flags, "apt", packageDefaultName));
String yumInstall = formatIfNotNull("yum -y --nogpgcheck install %s", getFlag(flags, "yum", packageDefaultName));
String brewInstall = formatIfNotNull("brew install %s", getFlag(flags, "brew", packageDefaultName));
String portInstall = formatIfNotNull("port install %s", getFlag(flags, "port", packageDefaultName));
List<String> commands = new LinkedList<String>();
if (ifMissing != null)
commands.add(format("which %s", ifMissing));
if (zypperInstall != null)
commands.add(ifExecutableElse1("zypper",
chainGroup(
"echo zypper exists, doing refresh",
ok(sudo("zypper --non-interactive --no-gpg-checks refresh")),
sudo(zypperInstall))));
if (aptInstall != null)
commands.add(ifExecutableElse1("apt-get",
chainGroup(
"echo apt-get exists, doing update",
"export DEBIAN_FRONTEND=noninteractive",
ok(sudo("apt-get update")),
sudo(aptInstall))));
if (yumInstall != null)
// Need to upgrade ca-certificates sometimes:
// http://serverfault.com/questions/637549/epel-repo-for-centos-6-causing-error?newreg=7c6019c0d0ae483c8bb3af387166ce49
commands.add(ifExecutableElse1("yum",
chainGroup(
"echo yum exists, doing update",
ok(sudo("yum check-update")),
ok(sudo("yum -y install epel-release")),
ok(sudo("yum upgrade -y ca-certificates --disablerepo=epel")),
sudo(yumInstall))));
if (brewInstall != null)
commands.add(ifExecutableElse1("brew", brewInstall));
if (portInstall != null)
commands.add(ifExecutableElse1("port", sudo(portInstall)));
String lastCommand = ok(warn("WARNING: no known/successful package manager to install " +
(packageDefaultName!=null ? packageDefaultName : flags.toString()) +
", may fail subsequently"));
if (optionalCommandToRunIfNone != null)
lastCommand = chain(lastCommand, optionalCommandToRunIfNone);
commands.add(lastCommand);
return alternatives(commands);
}
public static String warn(String message) {
return "( echo "+BashStringEscapes.wrapBash(message)+" | tee /dev/stderr )";
}
/** returns a command which logs a message to stdout and stderr then exits with the given error code */
public static String fail(String message, int code) {
return chainGroup(warn(message), "exit "+code);
}
/** requires the command to have a non-zero exit code; e.g.
* <code>require("which foo", "Command foo must be found", 1)</code> */
public static String require(String command, String failureMessage, int exitCode) {
return alternativesGroup(command, fail(failureMessage, exitCode));
}
/** as {@link #require(String, String, int)} but returning the original exit code */
public static String require(String command, String failureMessage) {
return alternativesGroup(command, chainGroup("EXIT_CODE=$?", warn(failureMessage), "exit $EXIT_CODE"));
}
/** requires the test to pass, as valid bash `test` arguments; e.g.
* <code>requireTest("-f /etc/hosts", "Hosts file must exist", 1)</code> */
public static String requireTest(String test, String failureMessage, int exitCode) {
return require("test "+test, failureMessage, exitCode);
}
/** as {@link #requireTest(String, String, int)} but returning the original exit code */
public static String requireTest(String test, String failureMessage) {
return require("test "+test, failureMessage);
}
/** fails with nice error if the given file does not exist */
public static String requireFile(String file) {
return requireTest("-f "+BashStringEscapes.wrapBash(file), "The required file \""+file+"\" does not exist");
}
/** fails with nice error if the given file does not exist */
public static String requireExecutable(String command) {
return require("which "+BashStringEscapes.wrapBash(command), "The required executable \""+command+"\" does not exist");
}
public static String waitForFileContents(String file, String desiredContent, Duration timeout, boolean failOnTimeout) {
long secs = Math.max(timeout.toSeconds(), 1);
List<String> commands = ImmutableList.of(
"for i in {1.."+secs+"}; do",
" grep '"+desiredContent+"' "+file+" && result=0 || result=$?",
" [ \"$result\" == 0 ] && break",
" sleep 1",
"done",
"if test \"$result\" -ne 0; then",
" "+ (failOnTimeout ?
"echo \"Couldn't find "+desiredContent+" in "+file+"; aborting\" && exit 1" :
"echo \"Couldn't find "+desiredContent+" in "+file+"; continuing\""),
"fi");
return Joiner.on("\n").join(commands);
}
public static String waitForPortFree(int port, Duration timeout, boolean failOnTimeout) {
long secs = Math.max(timeout.toSeconds(), 1);
// TODO How platform-dependent are the args + output format of netstat?
// TODO Not using sudo as wrapping either netstat call or sudo(alternativesGroup(...)) fails; parentheses too confusing!
String netstatCommand = alternativesGroup(
"sudo netstat -antp --tcp", // for Centos
"sudo netstat -antp TCP"); // for OS X
// number could appear in an IP address or as a port; look for white space at end, and dot or colon before
String grepCommand = "grep -E '(:|\\.)"+port+"($|\\s)' > /dev/null";
List<String> commands = ImmutableList.of(
"for i in {1.."+secs+"}; do",
" "+BashCommands.requireExecutable("netstat"),
" "+alternativesGroup(
chainGroup("which awk", "AWK_EXEC=awk"),
chainGroup("which gawk", "AWK_EXEC=gawk"),
chainGroup("which /usr/bin/awk", "AWK_EXEC=/usr/bin/awk"),
chainGroup("echo \"No awk to determine if Port "+port+" still in use; aborting\"", "exit 1")),
" "+netstatCommand+" | $AWK_EXEC '{print $4}' | "+grepCommand+" && result=0 || result=$?",
" [ \"$result\" != 0 ] && break",
" sleep 1",
"done",
"if test \"$result\" -eq 0; then",
" "+ (failOnTimeout ?
"echo \"Port "+port+" still in use (according to netstat); aborting\" && exit 1" :
"echo \"Port "+port+" still in use (according to netstat); continuing\""),
"fi");
return Joiner.on("\n").join(commands);
}
public static String unzip(String file, String targetDir) {
return "unzip " + file + (Strings.isNonBlank(targetDir) ? " -d "+targetDir : "");
}
public static final String INSTALL_TAR = installExecutable("tar");
public static final String INSTALL_CURL = installExecutable("curl");
public static final String INSTALL_WGET = installExecutable("wget");
public static final String INSTALL_ZIP = installExecutable("zip");
public static final String INSTALL_UNZIP = alternatives(installExecutable("unzip"), installExecutable("zip"));
public static final String INSTALL_SYSSTAT = installPackage(ImmutableMap.of("onlyifmissing", "iostat"), "sysstat");
/**
* Returns commands to download the URL, saving as the given file. Will try each URL in turn until one is successful
* (see `curl -f` documentation).
*/
public static List<String> commandsToDownloadUrlsAs(List<String> urls, String saveAs) {
return Arrays.asList(INSTALL_CURL,
require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
}
public static String commandToDownloadUrlsAs(List<String> urls, String saveAs) {
return chain(INSTALL_CURL,
require(simpleDownloadUrlAs(urls, saveAs), "Could not retrieve "+saveAs+". Tried: " + Joiner.on(", ").join(urls), 9));
}
public static String commandToDownloadUrlAs(String url, String saveAs) {
return chain(INSTALL_CURL,
require(simpleDownloadUrlAs(Arrays.asList(url), saveAs), "Could not retrieve "+saveAs+" from " + url, 9));
}
/**
* Returns command to download the URL, sending the output to stdout --
* suitable for redirect by appending " | tar xvf".
* Will try each URL in turn until one is successful
*/
public static String downloadToStdout(List<String> urls) {
return chain(
INSTALL_CURL + " > /dev/null",
require(simpleDownloadUrlAs(urls, null),
"Could not retrieve file. Tried: " + Joiner.on(", ").join(urls), 9));
}
/** as {@link #downloadToStdout(List)} but varargs for convenience */
public static String downloadToStdout(String ...urls) {
return downloadToStdout(Arrays.asList(urls));
}
/**
* Same as {@link downloadUrlAs(List, String)}, except does not install curl, and does not exit on failure,
* and if saveAs is null it downloads it so stdout.
*/
public static String simpleDownloadUrlAs(List<String> urls, String saveAs) {
return simpleDownloadUrlAs(urls, null, null, saveAs);
}
public static String simpleDownloadUrlAs(List<String> urls, String user, String password, String saveAs) {
if (urls.isEmpty()) throw new IllegalArgumentException("No URLs supplied to download "+saveAs);
List<String> commands = new ArrayList<String>();
for (String url : urls) {
String command = "curl -f -L -k --retry 10 --keepalive-time 30 --speed-time 30 ";
if (user!=null && password!=null) {
command = command + format("-u %s:%s ", user, password);
}
command = command + format("\"%s\"", url);
if (saveAs!=null) {
command = command + format(" -o %s", saveAs);
}
commands.add(command);
}
return alternatives(commands);
}
private static Object getFlag(Map<?,?> flags, String flagName, Object defaultValue) {
Object found = flags.get(flagName);
return found == null ? defaultValue : found;
}
/**
* Install a particular Java runtime, fails if not possible.
* <p>
* <em><strong>Note</strong> Java 8 is not yet supported on SUSE</em>
*
* @return The command to install the given Java runtime.
* @see #installJava6OrFail()
* @see #installJava7Or6OrFail()
* @see #installJavaLatestOrFail()
*/
public static String installJava(int version) {
Preconditions.checkArgument(version == 6 || version == 7 || version == 8, "Supported Java versions are 6, 7, or 8");
return installPackageOr(MutableMap.of("apt", "openjdk-" + version + "-jdk","yum", "java-1." + version + ".0-openjdk-devel"), null,
ifExecutableElse1("zypper", chainGroup(
ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/SLE_11_SP3 java_sles_11")),
ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_11.4 java_suse_11")),
ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_12.3 java_suse_12")),
ok(sudo("zypper --non-interactive addrepo http://download.opensuse.org/repositories/Java:/openjdk6:/Factory/openSUSE_13.1 java_suse_13")),
alternatives(installPackageOrFail(MutableMap.of("zypper", "java-1_" + version + "_0-openjdk-devel"), null),
installPackageOrFail(MutableMap.of("zypper", "java-1_" + version + "_0-ibm"), null)))));
}
public static String installJava6() {
return installJava(6);
}
public static String installJava7() {
return installJava(7);
}
public static String installJava8() {
return installJava(8);
}
public static String installJava6IfPossible() {
return ok(installJava6());
}
public static String installJava7IfPossible() {
return ok(installJava7());
}
public static String installJava8IfPossible() {
return ok(installJava8());
}
public static String installJava6OrFail() {
return alternatives(installJava6(), fail("java 6 install failed", 9));
}
public static String installJava7OrFail() {
return alternatives(installJava7(), fail("java 7 install failed", 9));
}
public static String installJava7Or6OrFail() {
return alternatives(installJava7(), installJava6(), fail("java install failed", 9));
}
public static String installJavaLatestOrFail() {
return alternatives(installJava8(), installJava7(), installJava6(), fail("java latest install failed", 9));
}
public static String installJavaLatestOrWarn() {
return alternatives(installJava8(), installJava7(), installJava6(), warn("java latest install failed, entity may subsequently fail"));
}
/** cats the given text to the given command, using bash << multi-line input syntax */
public static String pipeTextTo(String text, String command) {
return "cat << EOL_BROOKLYN | "+command+"\n"
+text
+"\n"+"EOL_BROOKLYN\n";
}
public static String pipeTextToFile(String text, String filepath) {
return "cat > \"" + filepath + "\" << EOF_BROOKLYN\n"
+ text + "\n"
+ "EOF_BROOKLYN\n";
}
public static String prependToEtcHosts(String ip, String... hostnames) {
String tempFileId = "bak"+Identifiers.makeRandomId(4);
return sudo(String.format("sed -i."+tempFileId+" -e '1i\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
}
public static String appendToEtcHosts(String ip, String... hostnames) {
// Using sed rather than `echo ... >> /etc/hosts` because when embedded inside sudo,
// the redirect doesn't get executed by sudo.
String tempFileId = "bak"+Identifiers.makeRandomId(4);
return sudo(String.format("sed -i."+tempFileId+" -e '$a\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
}
/**
* Sets the hostname, splitting the given hostname if it contains a dot to include the unqualified and fully qualified names.
*
* @see {@link #setHostname(String, String)}
*/
@Beta
public static List<String> setHostname(String newHostname) {
// See http://www.dns-sd.org/trailingdotsindomainnames.html.
// If we are given "abcd." then let's pass that as-is to setHostname("abcd.", null)
if (newHostname.indexOf(".") > 0) {
String hostPart = newHostname.substring(0, newHostname.indexOf("."));
String domainPart = newHostname.substring(hostPart.length()+1);
return setHostname(hostPart, domainPart);
} else {
return setHostname(newHostname, null);
}
}
/**
* Sets the hostname to {@code hostPart + "." + domainPart}, or if domainPart is null/empty then {code hostPart}.
*
* @param hostPart
* @param domainPart
* @return
*/
@Beta
public static List<String> setHostname(String hostPart, String domainPart) {
// See:
// - http://www.rackspace.com/knowledge_center/article/centos-hostname-change
// - https://wiki.debian.org/HowTo/ChangeHostname
// - http://askubuntu.com/questions/9540/how-do-i-change-the-computer-name
//
// We prepend in /etc/hosts, to ensure the right fqn appears first.
// e.g. comment in http://askubuntu.com/questions/158957/how-to-set-the-fully-qualified-domain-name-in-12-04
// says "It's important to note that the first domain in /etc/hosts should be your FQDN. "
//
// TODO Should we run `sudo service hostname restart` or `sudo /etc/init.d/hostname restart`?
// I don't think we need to because we've run `sudo hostname <newname>`
//
// TODO What if /etc/sysconfig/network doesn't have a line for HOSTNAME=...?
//
// TODO What about hostPart ending in "." - see http://www.dns-sd.org/trailingdotsindomainnames.html
// for what that means in DNS. However, hostname is not the same as the DNS name (hostnames
// predate the invention of DNS! - although frequently the DNS name has the same first portion
// as the hostname) so the link you gave is not relevant. However despite searching Google and
// man pages I [Ricard] am unable to find a reference which clearly states what characters are
// relevant in a hostname. I think it's safest to assume that the hostname is just [a-z,0-9,-]
// and no dots at all.
checkNotNull(hostPart, "hostPart");
checkArgument(!hostPart.contains("."), "hostPart '%s' must not contain '.'", hostPart);
String tempFileId = "bak"+Identifiers.makeRandomId(4);
List<String> allhostnames = Lists.newArrayList();
String fqdn = hostPart;
if (Strings.isNonBlank(domainPart)) {
fqdn = hostPart+"."+domainPart;
allhostnames.add(fqdn);
}
allhostnames.add(hostPart);
allhostnames.add("localhost");
return ImmutableList.of(
sudo("sed -i."+tempFileId+" -e 's/^127.0.0.1/# Replaced by Brooklyn\\\n#127.0.0.1/' /etc/hosts"),
prependToEtcHosts("127.0.0.1", allhostnames.toArray(new String[allhostnames.size()])),
ifFileExistsElse0("/etc/sysconfig/network", sudo("sed -i."+tempFileId+" -e 's/^HOSTNAME=.*$/HOSTNAME="+hostPart+"/' /etc/sysconfig/network")),
ifFileExistsElse0("/etc/hostname", sudo("sed -i."+tempFileId+" -e 's/^[a-zA-Z_0-9].*$/"+hostPart+"/' /etc/hostname")),
sudo("hostname "+hostPart));
}
}