| /* |
| * 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 |
| * |
| * https://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.ivy.plugins.repository.ssh; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.StringReader; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.ivy.core.settings.TimeoutConstraint; |
| import org.apache.ivy.plugins.repository.Resource; |
| import org.apache.ivy.util.Message; |
| |
| import com.jcraft.jsch.ChannelExec; |
| import com.jcraft.jsch.JSchException; |
| import com.jcraft.jsch.Session; |
| |
| /** |
| * Ivy Repository based on SSH |
| */ |
| public class SshRepository extends AbstractSshBasedRepository { |
| |
| private static final int BUFFER_SIZE = 64 * 1024; |
| |
| private static final String ARGUMENT_PLACEHOLDER = "%arg"; |
| |
| private static final int POLL_SLEEP_TIME = 500; |
| |
| private char fileSeparator = '/'; |
| |
| private String listCommand = "ls -1"; |
| |
| private String existCommand = "ls"; |
| |
| private String createDirCommand = "mkdir"; |
| |
| private String publishPermissions = null; |
| |
| public SshRepository() { |
| |
| } |
| |
| public SshRepository(final TimeoutConstraint timeoutConstraint) { |
| super(timeoutConstraint); |
| } |
| |
| /** |
| * create a new resource with lazy initializing |
| * |
| * @param source String |
| * @return Resource |
| */ |
| public Resource getResource(String source) { |
| Message.debug("SShRepository:getResource called: " + source); |
| return new SshResource(this, source); |
| } |
| |
| /** |
| * Fetch the needed file information for a given file (size, last modification time) and report |
| * it back in a SshResource |
| * |
| * @param source |
| * ssh uri for the file to get info for |
| * @return SshResource filled with the needed information |
| * @see org.apache.ivy.plugins.repository.Repository#getResource(java.lang.String) |
| */ |
| public SshResource resolveResource(String source) { |
| Message.debug("SShRepository:resolveResource called: " + source); |
| SshResource result = null; |
| Session session = null; |
| try { |
| session = getSession(source); |
| Scp myCopy = new Scp(session); |
| Scp.FileInfo fileInfo = myCopy.getFileinfo(new URI(source).getPath()); |
| result = new SshResource(this, source, true, fileInfo.getLength(), |
| fileInfo.getLastModified()); |
| } catch (IOException | URISyntaxException e) { |
| if (session != null) { |
| releaseSession(session, source); |
| } |
| result = new SshResource(); |
| } catch (RemoteScpException e) { |
| result = new SshResource(); |
| } |
| Message.debug("SShRepository:resolveResource end."); |
| return result; |
| } |
| |
| /** |
| * Reads out the output of a ssh session exec |
| * |
| * @param channel |
| * Channel to read from |
| * @param strStdout |
| * StringBuilder that receives Session Stdout output |
| * @param strStderr |
| * StringBuilder that receives Session Stderr output |
| * @throws IOException |
| * in case of trouble with the network |
| */ |
| private void readSessionOutput(ChannelExec channel, StringBuilder strStdout, |
| StringBuilder strStderr) throws IOException { |
| InputStream stdout = channel.getInputStream(); |
| InputStream stderr = channel.getErrStream(); |
| |
| try { |
| channel.connect(); |
| } catch (JSchException jsche) { |
| throw new IOException("Channel connection problems", jsche); |
| } |
| |
| byte[] buffer = new byte[BUFFER_SIZE]; |
| while (true) { |
| int avail = 0; |
| while ((avail = stdout.available()) > 0) { |
| int len = stdout.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail)); |
| strStdout.append(new String(buffer, 0, len)); |
| } |
| while ((avail = stderr.available()) > 0) { |
| int len = stderr.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail)); |
| strStderr.append(new String(buffer, 0, len)); |
| } |
| if (channel.isClosed()) { |
| break; |
| } |
| try { |
| Thread.sleep(POLL_SLEEP_TIME); |
| } catch (Exception ee) { |
| // ignored |
| } |
| } |
| int avail = 0; |
| while ((avail = stdout.available()) > 0) { |
| int len = stdout.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail)); |
| strStdout.append(new String(buffer, 0, len)); |
| } |
| while ((avail = stderr.available()) > 0) { |
| int len = stderr.read(buffer, 0, (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail)); |
| strStderr.append(new String(buffer, 0, len)); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.ivy.repository.Repository#list(java.lang.String) |
| */ |
| public List<String> list(String parent) throws IOException { |
| Message.debug("SShRepository:list called: " + parent); |
| List<String> result = new ArrayList<>(); |
| Session session = null; |
| ChannelExec channel = null; |
| session = getSession(parent); |
| channel = getExecChannel(session); |
| URI parentUri = null; |
| try { |
| parentUri = new URI(parent); |
| } catch (URISyntaxException e) { |
| throw new IOException("The uri '" + parent + "' is not valid!", e); |
| } |
| String fullCmd = replaceArgument(listCommand, parentUri.getPath()); |
| channel.setCommand(fullCmd); |
| StringBuilder stdOut = new StringBuilder(); |
| StringBuilder stdErr = new StringBuilder(); |
| readSessionOutput(channel, stdOut, stdErr); |
| if (channel.getExitStatus() != 0) { |
| Message.error("Ssh ListCommand exited with status != 0"); |
| Message.error(stdErr.toString()); |
| return null; |
| } else { |
| BufferedReader br = new BufferedReader(new StringReader(stdOut.toString())); |
| String line = null; |
| while ((line = br.readLine()) != null) { |
| result.add(line); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @param session Session |
| * @return ChannelExec |
| */ |
| private ChannelExec getExecChannel(Session session) throws IOException { |
| ChannelExec channel; |
| try { |
| channel = (ChannelExec) session.openChannel("exec"); |
| } catch (JSchException e) { |
| throw new IOException(); |
| } |
| return channel; |
| } |
| |
| /** |
| * Replace the argument placeholder with argument or append the argument if no placeholder is |
| * present |
| * |
| * @param command |
| * with argument placeholder or not |
| * @param argument ditto |
| * @return replaced full command |
| */ |
| private String replaceArgument(String command, String argument) { |
| String fullCmd; |
| if (!command.contains(ARGUMENT_PLACEHOLDER)) { |
| fullCmd = command + " " + argument; |
| } else { |
| fullCmd = command.replaceAll(ARGUMENT_PLACEHOLDER, argument); |
| } |
| return fullCmd; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.ivy.repository.Repository#put(java.io.File, java.lang.String, boolean) |
| */ |
| public void put(File source, String destination, boolean overwrite) throws IOException { |
| Message.debug("SShRepository:put called: " + destination); |
| Session session = getSession(destination); |
| |
| URI destinationUri = null; |
| try { |
| destinationUri = new URI(destination); |
| } catch (URISyntaxException e) { |
| throw new IOException("The uri '" + destination + "' is not valid!", e); |
| } |
| |
| try { |
| String filePath = destinationUri.getPath(); |
| int lastSep = filePath.lastIndexOf(fileSeparator); |
| String path; |
| String name; |
| if (lastSep == -1) { |
| name = filePath; |
| path = null; |
| } else { |
| name = filePath.substring(lastSep + 1); |
| path = filePath.substring(0, lastSep); |
| } |
| if (!overwrite) { |
| if (checkExistence(filePath, session)) { |
| throw new IOException("destination file exists and overwrite == false"); |
| } |
| } |
| if (path != null) { |
| makePath(path, session); |
| } |
| Scp myCopy = new Scp(session); |
| myCopy.put(source.getCanonicalPath(), path, name, publishPermissions); |
| } catch (IOException e) { |
| if (session != null) { |
| releaseSession(session, destination); |
| } |
| throw e; |
| } catch (RemoteScpException e) { |
| throw new IOException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Tries to create a directory path on the target system |
| * |
| * @param path |
| * to create |
| * @param session |
| * to use |
| */ |
| private void makePath(String path, Session session) throws IOException { |
| ChannelExec channel = null; |
| String trimmed = path; |
| try { |
| while (trimmed.length() > 0 && trimmed.charAt(trimmed.length() - 1) == fileSeparator) { |
| trimmed = trimmed.substring(0, trimmed.length() - 1); |
| } |
| if (trimmed.length() == 0 || checkExistence(trimmed, session)) { |
| return; |
| } |
| int nextSlash = trimmed.lastIndexOf(fileSeparator); |
| if (nextSlash > 0) { |
| String parent = trimmed.substring(0, nextSlash); |
| makePath(parent, session); |
| } |
| channel = getExecChannel(session); |
| String mkdir = replaceArgument(createDirCommand, trimmed); |
| Message.debug("SShRepository: trying to create path: " + mkdir); |
| channel.setCommand(mkdir); |
| StringBuilder stdOut = new StringBuilder(); |
| StringBuilder stdErr = new StringBuilder(); |
| readSessionOutput(channel, stdOut, stdErr); |
| } finally { |
| if (channel != null) { |
| channel.disconnect(); |
| } |
| } |
| } |
| |
| /** |
| * check for existence of file or dir on target system |
| * |
| * @param filePath |
| * to the object to check |
| * @param session |
| * to use |
| * @return true: object exists, false otherwise |
| */ |
| private boolean checkExistence(String filePath, Session session) throws IOException { |
| Message.debug("SShRepository: checkExistence called: " + filePath); |
| ChannelExec channel = null; |
| channel = getExecChannel(session); |
| String fullCmd = replaceArgument(existCommand, filePath); |
| channel.setCommand(fullCmd); |
| StringBuilder stdOut = new StringBuilder(); |
| StringBuilder stdErr = new StringBuilder(); |
| readSessionOutput(channel, stdOut, stdErr); |
| return channel.getExitStatus() == 0; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.ivy.repository.Repository#get(java.lang.String, java.io.File) |
| */ |
| public void get(String source, File destination) throws IOException { |
| Message.debug("SShRepository:get called: " + source + " to " |
| + destination.getCanonicalPath()); |
| if (destination.getParentFile() != null) { |
| destination.getParentFile().mkdirs(); |
| } |
| Session session = getSession(source); |
| |
| URI sourceUri = null; |
| try { |
| sourceUri = new URI(source); |
| } catch (URISyntaxException e) { |
| throw new IOException("The uri '" + source + "' is not valid!", e); |
| } |
| |
| try { |
| Scp myCopy = new Scp(session); |
| myCopy.get(sourceUri.getPath(), destination.getCanonicalPath()); |
| } catch (IOException e) { |
| if (session != null) { |
| releaseSession(session, source); |
| } |
| throw e; |
| } catch (RemoteScpException e) { |
| throw new IOException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * sets the list command to use for a directory listing listing must be only the filename and |
| * each filename on a separate line |
| * |
| * @param cmd |
| * to use. default is "ls -1" |
| */ |
| public void setListCommand(String cmd) { |
| this.listCommand = cmd.trim(); |
| } |
| |
| /** |
| * @return the list command to use |
| */ |
| public String getListCommand() { |
| return listCommand; |
| } |
| |
| /** |
| * @return the createDirCommand |
| */ |
| public String getCreateDirCommand() { |
| return createDirCommand; |
| } |
| |
| /** |
| * @param createDirCommand |
| * the createDirCommand to set |
| */ |
| public void setCreateDirCommand(String createDirCommand) { |
| this.createDirCommand = createDirCommand; |
| } |
| |
| /** |
| * @return the existCommand |
| */ |
| public String getExistCommand() { |
| return existCommand; |
| } |
| |
| /** |
| * @param existCommand |
| * the existCommand to set |
| */ |
| public void setExistCommand(String existCommand) { |
| this.existCommand = existCommand; |
| } |
| |
| /** |
| * The file separator is the separator to use on the target system On a unix system it is '/', |
| * but I don't know, how this is solved on different ssh implementations. Using the default |
| * might be fine |
| * |
| * @param fileSeparator |
| * The fileSeparator to use. default '/' |
| */ |
| public void setFileSeparator(char fileSeparator) { |
| this.fileSeparator = fileSeparator; |
| } |
| |
| /** |
| * A four digit string (e.g., 0644, see "man chmod", "man open") specifying the permissions of |
| * the published files. |
| * |
| * @param permissions String |
| */ |
| public void setPublishPermissions(String permissions) { |
| this.publishPermissions = permissions; |
| } |
| |
| /** |
| * return ssh as scheme use the Resolver type name here? would be nice if it would be static, so |
| * we could use SshResolver.getTypeName() |
| * |
| * @return String |
| */ |
| protected String getRepositoryScheme() { |
| return "ssh"; |
| } |
| |
| /** |
| * Not really streaming...need to implement a proper streaming approach? |
| * |
| * @param resource |
| * to stream |
| * @return InputStream of the resource data |
| * @throws IOException if something goes wrong |
| */ |
| public InputStream openStream(SshResource resource) throws IOException { |
| Session session = getSession(resource.getName()); |
| Scp scp = new Scp(session); |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| try { |
| scp.get(resource.getName(), os); |
| } catch (IOException e) { |
| if (session != null) { |
| releaseSession(session, resource.getName()); |
| } |
| throw e; |
| } catch (RemoteScpException e) { |
| throw new IOException(e.getMessage()); |
| } |
| return new ByteArrayInputStream(os.toByteArray()); |
| } |
| } |