1
package org.apache.maven.wagon.providers.ssh.external;
4
* Licensed to the Apache Software Foundation (ASF) under one
5
* or more contributor license agreements. See the NOTICE file
6
* distributed with this work for additional information
7
* regarding copyright ownership. The ASF licenses this file
8
* to you under the Apache License, Version 2.0 (the
9
* "License"); you may not use this file except in compliance
10
* with the License. You may obtain a copy of the License at
12
* http://www.apache.org/licenses/LICENSE-2.0
14
* Unless required by applicable law or agreed to in writing,
15
* software distributed under the License is distributed on an
16
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
* KIND, either express or implied. See the License for the
18
* specific language governing permissions and limitations
22
import org.apache.maven.wagon.AbstractWagon;
23
import org.apache.maven.wagon.CommandExecutionException;
24
import org.apache.maven.wagon.CommandExecutor;
25
import org.apache.maven.wagon.PathUtils;
26
import org.apache.maven.wagon.PermissionModeUtils;
27
import org.apache.maven.wagon.ResourceDoesNotExistException;
28
import org.apache.maven.wagon.Streams;
29
import org.apache.maven.wagon.TransferFailedException;
30
import org.apache.maven.wagon.WagonConstants;
31
import org.apache.maven.wagon.authentication.AuthenticationException;
32
import org.apache.maven.wagon.authentication.AuthenticationInfo;
33
import org.apache.maven.wagon.authorization.AuthorizationException;
34
import org.apache.maven.wagon.events.TransferEvent;
35
import org.apache.maven.wagon.providers.ssh.ScpHelper;
36
import org.apache.maven.wagon.repository.RepositoryPermissions;
37
import org.apache.maven.wagon.resource.Resource;
38
import org.codehaus.plexus.util.StringUtils;
39
import org.codehaus.plexus.util.cli.CommandLineException;
40
import org.codehaus.plexus.util.cli.CommandLineUtils;
41
import org.codehaus.plexus.util.cli.Commandline;
44
import java.io.FileNotFoundException;
45
import java.util.List;
46
import java.util.Locale;
49
* SCP deployer using "external" scp program. To allow for
50
* ssh-agent type behavior, until we can construct a Java SSH Agent and interface for JSch.
52
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
53
* @version $Id:ScpExternalWagon.java 477260 2006-11-20 17:11:39Z brett $
54
* @todo [BP] add compression flag
55
* @plexus.component role="org.apache.maven.wagon.Wagon"
57
* instantiation-strategy="per-lookup"
59
public class ScpExternalWagon
61
implements CommandExecutor
64
* The external SCP command to use - default is <code>scp</code>.
66
* @component.configuration default="scp"
68
private String scpExecutable = "scp";
71
* The external SSH command to use - default is <code>ssh</code>.
73
* @component.configuration default="ssh"
75
private String sshExecutable = "ssh";
78
* Arguments to pass to the SCP command.
80
* @component.configuration
82
private String scpArgs;
85
* Arguments to pass to the SSH command.
87
* @component.configuration
89
private String sshArgs;
91
private ScpHelper sshTool = new ScpHelper( this );
93
private static final int SSH_FATAL_EXIT_CODE = 255;
95
// ----------------------------------------------------------------------
97
// ----------------------------------------------------------------------
99
protected void openConnectionInternal()
100
throws AuthenticationException
102
if ( authenticationInfo == null )
104
authenticationInfo = new AuthenticationInfo();
108
public void closeConnection()
110
// nothing to disconnect
113
public boolean getIfNewer( String resourceName, File destination, long timestamp )
114
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
116
fireSessionDebug( "getIfNewer in SCP wagon is not supported - performing an unconditional get" );
117
get( resourceName, destination );
122
* @return The hostname of the remote server prefixed with the username, which comes either from the repository URL
123
* or from the authenticationInfo.
125
private String buildRemoteHost()
127
String username = this.getRepository().getUsername();
128
if ( username == null )
130
username = authenticationInfo.getUserName();
133
if ( username == null )
135
return getRepository().getHost();
139
return username + "@" + getRepository().getHost();
143
public void executeCommand( String command )
144
throws CommandExecutionException
146
fireTransferDebug( "Executing command: " + command );
148
executeCommand( command, false );
151
public Streams executeCommand( String command, boolean ignoreFailures )
152
throws CommandExecutionException
154
boolean putty = isPuTTY();
159
privateKey = ScpHelper.getPrivateKey( authenticationInfo );
161
catch ( FileNotFoundException e )
163
throw new CommandExecutionException( e.getMessage(), e );
165
Commandline cl = createBaseCommandLine( putty, sshExecutable, privateKey );
168
repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
169
if ( port != ScpHelper.DEFAULT_SSH_PORT )
173
cl.createArg().setLine( "-P " + port );
177
cl.createArg().setLine( "-p " + port );
181
if ( sshArgs != null )
183
cl.createArg().setLine( sshArgs );
186
String remoteHost = this.buildRemoteHost();
188
cl.createArg().setValue( remoteHost );
190
cl.createArg().setValue( command );
192
fireSessionDebug( "Executing command: " + cl.toString() );
196
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
197
CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
198
int exitCode = CommandLineUtils.executeCommandLine( cl, out, err );
199
Streams streams = new Streams();
200
streams.setOut( out.getOutput() );
201
streams.setErr( err.getOutput() );
202
fireSessionDebug( streams.getOut() );
203
fireSessionDebug( streams.getErr() );
206
if ( !ignoreFailures || exitCode == SSH_FATAL_EXIT_CODE )
208
throw new CommandExecutionException( "Exit code " + exitCode + " - " + err.getOutput() );
213
catch ( CommandLineException e )
215
throw new CommandExecutionException( "Error executing command line", e );
219
protected boolean isPuTTY()
221
return sshExecutable.toLowerCase( Locale.ENGLISH ).indexOf( "plink" ) >= 0;
224
private Commandline createBaseCommandLine( boolean putty, String executable, File privateKey )
226
Commandline cl = new Commandline();
228
cl.setExecutable( executable );
230
if ( privateKey != null )
232
cl.createArg().setValue( "-i" );
233
cl.createArg().setFile( privateKey );
236
String password = authenticationInfo.getPassword();
237
if ( putty && password != null )
239
cl.createArg().setValue( "-pw" );
240
cl.createArg().setValue( password );
243
// should check interactive flag, but scpexe never works in interactive mode right now due to i/o streams
246
cl.createArg().setValue( "-batch" );
250
cl.createArg().setValue( "-o" );
251
cl.createArg().setValue( "BatchMode yes" );
257
private void executeScpCommand( Resource resource, File localFile, boolean put )
258
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
260
boolean putty = isPuTTYSCP();
265
privateKey = ScpHelper.getPrivateKey( authenticationInfo );
267
catch ( FileNotFoundException e )
269
fireSessionConnectionRefused();
271
throw new AuthorizationException( e.getMessage() );
273
Commandline cl = createBaseCommandLine( putty, scpExecutable, privateKey );
275
cl.setWorkingDirectory( localFile.getParentFile().getAbsolutePath() );
278
repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
279
if ( port != ScpHelper.DEFAULT_SSH_PORT )
281
cl.createArg().setLine( "-P " + port );
284
if ( scpArgs != null )
286
cl.createArg().setLine( scpArgs );
289
String resourceName = normalizeResource( resource );
290
String remoteFile = getRepository().getBasedir() + "/" + resourceName;
292
remoteFile = StringUtils.replace( remoteFile, " ", "\\ " );
294
String qualifiedRemoteFile = this.buildRemoteHost() + ":" + remoteFile;
297
cl.createArg().setValue( localFile.getName() );
298
cl.createArg().setValue( qualifiedRemoteFile );
302
cl.createArg().setValue( qualifiedRemoteFile );
303
cl.createArg().setValue( localFile.getName() );
306
fireSessionDebug( "Executing command: " + cl.toString() );
310
CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
311
int exitCode = CommandLineUtils.executeCommandLine( cl, null, err );
314
if ( !put && err.getOutput().trim().toLowerCase( Locale.ENGLISH ).indexOf( "no such file or directory" )
317
throw new ResourceDoesNotExistException( err.getOutput() );
321
TransferFailedException e =
322
new TransferFailedException( "Exit code: " + exitCode + " - " + err.getOutput() );
324
fireTransferError( resource, e, put ? TransferEvent.REQUEST_PUT : TransferEvent.REQUEST_GET );
330
catch ( CommandLineException e )
332
fireTransferError( resource, e, put ? TransferEvent.REQUEST_PUT : TransferEvent.REQUEST_GET );
334
throw new TransferFailedException( "Error executing command line", e );
340
return scpExecutable.toLowerCase( Locale.ENGLISH ).indexOf( "pscp" ) >= 0;
343
private String normalizeResource( Resource resource )
345
return StringUtils.replace( resource.getName(), "\\", "/" );
348
public void put( File source, String destination )
349
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
351
Resource resource = new Resource( destination );
353
firePutInitiated( resource, source );
355
if ( !source.exists() )
357
throw new ResourceDoesNotExistException( "Specified source file does not exist: " + source );
360
String basedir = getRepository().getBasedir();
362
String resourceName = StringUtils.replace( destination, "\\", "/" );
364
String dir = PathUtils.dirname( resourceName );
366
dir = StringUtils.replace( dir, "\\", "/" );
368
String umaskCmd = null;
369
if ( getRepository().getPermissions() != null )
371
String dirPerms = getRepository().getPermissions().getDirectoryMode();
373
if ( dirPerms != null )
375
umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms );
379
String mkdirCmd = "mkdir -p " + basedir + "/" + dir + "\n";
381
if ( umaskCmd != null )
383
mkdirCmd = umaskCmd + "; " + mkdirCmd;
388
executeCommand( mkdirCmd );
390
catch ( CommandExecutionException e )
392
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
394
throw new TransferFailedException( "Error executing command for transfer", e );
397
resource.setContentLength( source.length() );
399
resource.setLastModified( source.lastModified() );
401
firePutStarted( resource, source );
403
executeScpCommand( resource, source, true );
405
postProcessListeners( resource, source, TransferEvent.REQUEST_PUT );
409
RepositoryPermissions permissions = getRepository().getPermissions();
411
if ( permissions != null && permissions.getGroup() != null )
413
executeCommand( "chgrp -f " + permissions.getGroup() + " " + basedir + "/" + resourceName + "\n",
417
if ( permissions != null && permissions.getFileMode() != null )
419
executeCommand( "chmod -f " + permissions.getFileMode() + " " + basedir + "/" + resourceName + "\n",
423
catch ( CommandExecutionException e )
425
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
427
throw new TransferFailedException( "Error executing command for transfer", e );
429
firePutCompleted( resource, source );
432
public void get( String resourceName, File destination )
433
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
435
String path = StringUtils.replace( resourceName, "\\", "/" );
437
Resource resource = new Resource( path );
439
fireGetInitiated( resource, destination );
441
createParentDirectories( destination );
443
fireGetStarted( resource, destination );
445
executeScpCommand( resource, destination, false );
447
postProcessListeners( resource, destination, TransferEvent.REQUEST_GET );
449
fireGetCompleted( resource, destination );
453
// these parameters are user specific, so should not be read from the repository itself.
454
// They can be configured by plexus, or directly on the instantiated object.
455
// Alternatively, we may later accept a generic parameters argument to connect, or some other configure(Properties)
456
// method on a Wagon.
459
public List<String> getFileList( String destinationDirectory )
460
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
462
return sshTool.getFileList( destinationDirectory, repository );
465
public void putDirectory( File sourceDirectory, String destinationDirectory )
466
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
468
sshTool.putDirectory( this, sourceDirectory, destinationDirectory );
471
public boolean resourceExists( String resourceName )
472
throws TransferFailedException, AuthorizationException
474
return sshTool.resourceExists( resourceName, repository );
477
public boolean supportsDirectoryCopy()
482
public String getScpExecutable()
484
return scpExecutable;
487
public void setScpExecutable( String scpExecutable )
489
this.scpExecutable = scpExecutable;
492
public String getSshExecutable()
494
return sshExecutable;
497
public void setSshExecutable( String sshExecutable )
499
this.sshExecutable = sshExecutable;
502
public String getScpArgs()
507
public void setScpArgs( String scpArgs )
509
this.scpArgs = scpArgs;
512
public String getSshArgs()
517
public void setSshArgs( String sshArgs )
519
this.sshArgs = sshArgs;