/* * Copyright 2016 Fizzed, Inc. * * 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 com.fizzed.stork.deploy; import static com.fizzed.blaze.Contexts.fail; import com.fizzed.blaze.SecureShells; import com.fizzed.blaze.core.Actions; import com.fizzed.blaze.ssh.SshSession; import com.fizzed.blaze.util.Streamables; import java.io.IOException; import static com.fizzed.blaze.SecureShells.sshExec; import com.fizzed.blaze.ssh.SshSftpSession; import com.fizzed.blaze.util.MutableUri; import com.fizzed.crux.vagrant.VagrantClient; import com.fizzed.crux.vagrant.VagrantClients; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Targets { static private final Logger log = LoggerFactory.getLogger(Targets.class); static public final VagrantClient VAGRANT_CLIENT = VagrantClients.cachingOrEmptyClient(); static public Target connect(String uri) throws IOException { SshSession ssh = sshConnect(uri); SshSftpSession sftp = SecureShells.sshSftp(ssh).run(); return probe(ssh, sftp); } static public Target probe(SshSession ssh, SshSftpSession sftp) throws IOException { InitType initType = initType(ssh); Map<String,String> commands = which(ssh, Arrays.asList("doas", "sudo", "tar", "unzip")); String uname = uname(ssh); return new UnixTarget(ssh, sftp, uname, initType, "/tmp", commands); } static public SshSession sshConnect(String uri) throws IOException { MutableUri u = MutableUri.of(uri); if (u.getScheme() == null) { throw new IOException("uri missing scheme (not in format such as ssh://host)"); } switch (u.getScheme()) { case "ssh": return SecureShells.sshConnect(uri).run(); case "vagrant+ssh": { String host = u.getHost(); log.info("Querying vagrant ssh-config for {}", u.getHost()); return SecureShells.sshConnect("ssh://" + u.getHost()) .configFile(VAGRANT_CLIENT.sshConfig(u.getHost())) .run(); } default: fail("Unsupported target uri. Support for either ssh://host or vagrant+ssh://host"); return null; } } static private InitType initType(SshSession ssh) { String initTypeString = sshExec(ssh) .command("sh").args("-c", "if $(/sbin/init --version | egrep -q 'upstart'); then echo upstart; " + "elif $(systemctl | egrep -q '.mount'); then echo systemd; " + "elif [ -f /etc/init.d/cron ]; then echo sysv; " + "else echo unknown; fi") .pipeOutput(Streamables.captureOutput()) .pipeError(Streamables.nullOutput()) .runResult() .map(Actions::toCaptureOutput) .asString(); if (initTypeString == null) { return null; } switch (initTypeString.trim()) { case "sysv": return InitType.SYSV; case "systemd": return InitType.SYSTEMD; case "upstart": return InitType.UPSTART; default: return InitType.UNKNOWN; } } static private Map<String,String> which(SshSession ssh, List<String> commands) { // doesn't matter if we find it or not String whichString = sshExec(ssh, "which", commands.toArray()) .pipeOutput(Streamables.captureOutput()) .pipeError(Streamables.nullOutput()) .exitValues(0, 1, 2) .runResult() .map(Actions::toCaptureOutput) .asString(); if (whichString == null) { return Collections.emptyMap(); } Map<String,String> result = new HashMap<>(); String[] lines = whichString.split("\\\n"); for (String line : lines) { Path cmd = Paths.get(line.trim()); result.put(cmd.getFileName().toString(), line.trim()); } return result; } static private String uname(SshSession ssh) { // doesn't matter if we find it or not return sshExec(ssh, "uname", "-srm") .pipeOutput(Streamables.captureOutput()) .pipeError(Streamables.nullOutput()) .exitValues(0) .runResult() .map(Actions::toCaptureOutput) .asString() .trim(); } }