| /* |
| * 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)); |
| } |
| } |