embulk-input-sftp 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +1 -0
- data/build.gradle +62 -2
- data/classpath/embulk-input-sftp-0.2.4.jar +0 -0
- data/src/main/java/org/embulk/input/sftp/SftpFileInput.java +26 -67
- data/src/test/java/org/embulk/input/sftp/TestSftpFileInputPlugin.java +54 -10
- metadata +13 -13
- data/classpath/embulk-input-sftp-0.2.3.jar +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df31c9ece3349f98ed7d6cb077ea54d93eb03983
|
4
|
+
data.tar.gz: 86a2988731cc55a81ed7e191eb453b952a3d13a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5200a610d8dbcde6e46597d9749504da4e1afdeb44f1803c1ff162204ccb96f4eab73397ec889046111b6adf6677653521696fa4e88f4d35e5a0b9c4e3998fe2
|
7
|
+
data.tar.gz: 33a363400d78fd010f238a0270f7e086610ba9a9bc9ea25cf59dcf2d03fb66f09198f5df0aa35ea46545fea2dded4eaa5264cc9a56aaf2c604cb49b424617171
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 0.2.4 - 2017-06-05
|
2
|
+
|
3
|
+
* [maintenance] Improve logic for remote file search with path_prefix [#22](https://github.com/sakama/embulk-input-sftp/pull/22)
|
4
|
+
|
1
5
|
## 0.2.3 - 2016-09-30
|
2
6
|
|
3
7
|
* [maintenance] Fix auth failure while generating last_path under limited case [#20](https://github.com/sakama/embulk-input-sftp/pull/20)
|
data/README.md
CHANGED
data/build.gradle
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
plugins {
|
2
|
-
id "com.jfrog.bintray" version "1.
|
2
|
+
id "com.jfrog.bintray" version "1.7"
|
3
|
+
id "maven-publish"
|
3
4
|
id "com.github.jruby-gradle.base" version "0.1.5"
|
4
5
|
id "java"
|
5
6
|
id "checkstyle"
|
@@ -14,7 +15,8 @@ configurations {
|
|
14
15
|
provided
|
15
16
|
}
|
16
17
|
|
17
|
-
|
18
|
+
group = "org.embulk.input.sftp"
|
19
|
+
version = "0.2.4"
|
18
20
|
|
19
21
|
sourceCompatibility = 1.7
|
20
22
|
targetCompatibility = 1.7
|
@@ -33,6 +35,50 @@ dependencies {
|
|
33
35
|
testCompile "io.netty:netty-all:4.0.34.Final"
|
34
36
|
}
|
35
37
|
|
38
|
+
javadoc {
|
39
|
+
options {
|
40
|
+
locale = 'en_US'
|
41
|
+
encoding = 'UTF-8'
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
// bintray
|
46
|
+
bintray {
|
47
|
+
// write at your bintray user name and api key to ~/.gradle/gradle.properties file:
|
48
|
+
user = project.hasProperty('bintray_user') ? bintray_user : ''
|
49
|
+
key = project.hasProperty('bintray_api_key') ? bintray_api_key : ''
|
50
|
+
|
51
|
+
publications = ['bintrayMavenRelease']
|
52
|
+
publish = true
|
53
|
+
|
54
|
+
pkg {
|
55
|
+
userOrg = 'embulk-input-sftp'
|
56
|
+
repo = 'maven'
|
57
|
+
name = project.name
|
58
|
+
desc = 'SFTP file input plugin for Embulk'
|
59
|
+
websiteUrl = 'https://github.com/embulk/embulk-input-sftp'
|
60
|
+
issueTrackerUrl = 'https://github.com/embulk/embulk-input-sftp/issues'
|
61
|
+
vcsUrl = 'https://github.com/embulk/embulk-input-sftp.git'
|
62
|
+
licenses = ['Apache-2.0']
|
63
|
+
labels = ['embulk', 'java']
|
64
|
+
publicDownloadNumbers = true
|
65
|
+
|
66
|
+
version {
|
67
|
+
name = project.version
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
publishing {
|
72
|
+
publications {
|
73
|
+
bintrayMavenRelease(MavenPublication) {
|
74
|
+
from components.java
|
75
|
+
artifact testsJar
|
76
|
+
artifact sourcesJar
|
77
|
+
artifact javadocJar
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
36
82
|
task classpath(type: Copy, dependsOn: ["jar"]) {
|
37
83
|
doFirst { file("classpath").deleteDir() }
|
38
84
|
from (configurations.runtime - configurations.provided + files(jar.archivePath))
|
@@ -40,6 +86,20 @@ task classpath(type: Copy, dependsOn: ["jar"]) {
|
|
40
86
|
}
|
41
87
|
clean { delete "classpath" }
|
42
88
|
|
89
|
+
// add tests/javadoc/source jar tasks as artifacts to be released
|
90
|
+
task testsJar(type: Jar, dependsOn: classes) {
|
91
|
+
classifier = 'tests'
|
92
|
+
from sourceSets.test.output
|
93
|
+
}
|
94
|
+
task sourcesJar(type: Jar, dependsOn: classes) {
|
95
|
+
classifier = 'sources'
|
96
|
+
from sourceSets.main.allSource
|
97
|
+
}
|
98
|
+
task javadocJar(type: Jar, dependsOn: javadoc) {
|
99
|
+
classifier = 'javadoc'
|
100
|
+
from javadoc.destinationDir
|
101
|
+
}
|
102
|
+
|
43
103
|
checkstyle {
|
44
104
|
configFile = file("${project.rootDir}/config/checkstyle/checkstyle.xml")
|
45
105
|
toolVersion = '6.14.1'
|
Binary file
|
@@ -4,11 +4,15 @@ import com.google.common.base.Function;
|
|
4
4
|
import com.google.common.base.Optional;
|
5
5
|
import com.google.common.base.Throwables;
|
6
6
|
import org.apache.commons.io.FilenameUtils;
|
7
|
+
import org.apache.commons.lang3.StringUtils;
|
7
8
|
import org.apache.commons.vfs2.FileObject;
|
8
9
|
import org.apache.commons.vfs2.FileSystemException;
|
9
10
|
import org.apache.commons.vfs2.FileSystemOptions;
|
10
11
|
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
|
12
|
+
import org.apache.commons.vfs2.provider.UriParser;
|
13
|
+
import org.apache.commons.vfs2.provider.local.GenericFileNameParser;
|
11
14
|
import org.apache.commons.vfs2.provider.sftp.IdentityInfo;
|
15
|
+
import org.apache.commons.vfs2.provider.sftp.SftpFileNameParser;
|
12
16
|
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;
|
13
17
|
import org.embulk.config.ConfigException;
|
14
18
|
import org.embulk.config.TaskReport;
|
@@ -19,7 +23,6 @@ import org.embulk.spi.util.InputStreamFileInput;
|
|
19
23
|
import org.embulk.spi.util.RetryExecutor.RetryGiveupException;
|
20
24
|
import org.embulk.spi.util.RetryExecutor.Retryable;
|
21
25
|
import org.slf4j.Logger;
|
22
|
-
import static org.embulk.spi.util.RetryExecutor.retryExecutor;
|
23
26
|
|
24
27
|
import java.io.File;
|
25
28
|
import java.io.IOException;
|
@@ -27,6 +30,8 @@ import java.net.URI;
|
|
27
30
|
import java.net.URISyntaxException;
|
28
31
|
import java.util.Arrays;
|
29
32
|
|
33
|
+
import static org.embulk.spi.util.RetryExecutor.retryExecutor;
|
34
|
+
|
30
35
|
public class SftpFileInput
|
31
36
|
extends InputStreamFileInput
|
32
37
|
implements TransactionalFileInput
|
@@ -149,76 +154,23 @@ public class SftpFileInput
|
|
149
154
|
if (!uri.isPresent()) {
|
150
155
|
return null;
|
151
156
|
}
|
152
|
-
else if (!task.getSecretKeyFile().isPresent() && task.getPassword().isPresent()) {
|
153
|
-
return getRelativePathFromURIwithPassword(task, uri);
|
154
|
-
}
|
155
157
|
else {
|
156
|
-
|
158
|
+
String uriString = uri.get();
|
159
|
+
String scheme = UriParser.extractScheme(uriString);
|
160
|
+
if (StringUtils.isEmpty(scheme)) {
|
161
|
+
return GenericFileNameParser.getInstance().parseUri(null, null, uriString).getPath();
|
162
|
+
}
|
163
|
+
else if (scheme.equals("sftp")) {
|
164
|
+
return SftpFileNameParser.getInstance().parseUri(null, null, uriString).getPath();
|
165
|
+
}
|
166
|
+
else {
|
167
|
+
throw new ConfigException("SFTP Plugin only support SFTP scheme");
|
168
|
+
}
|
157
169
|
}
|
158
170
|
}
|
159
|
-
catch (
|
160
|
-
throw new ConfigException("Failed to generate last_path due to
|
161
|
-
}
|
162
|
-
}
|
163
|
-
|
164
|
-
private static String getRelativePathFromURIwithPassword(final PluginTask task, final Optional<String> uri)
|
165
|
-
{
|
166
|
-
try {
|
167
|
-
return retryExecutor()
|
168
|
-
.withRetryLimit(task.getMaxConnectionRetry())
|
169
|
-
.withInitialRetryWait(500)
|
170
|
-
.withMaxRetryWait(30 * 1000)
|
171
|
-
.runInterruptible(new Retryable<String>() {
|
172
|
-
@Override
|
173
|
-
public String call() throws URISyntaxException, IOException
|
174
|
-
{
|
175
|
-
log.info("Creating last_path from URI contains password in FileList.");
|
176
|
-
StandardFileSystemManager manager = initializeStandardFileSystemManager();
|
177
|
-
|
178
|
-
String prefix = new URI("sftp", initializeUserInfo(task), task.getHost(), task.getPort(), null, null, null).toString();
|
179
|
-
prefix = manager.resolveFile(prefix).toString();
|
180
|
-
// To avoid URI parse failure when password contains special characters
|
181
|
-
String newUri = uri.get().replace(prefix, "sftp://user:password@example.com/");
|
182
|
-
|
183
|
-
return new URI(newUri).getPath();
|
184
|
-
}
|
185
|
-
|
186
|
-
@Override
|
187
|
-
public boolean isRetryableException(Exception exception)
|
188
|
-
{
|
189
|
-
if (exception instanceof URISyntaxException) {
|
190
|
-
// Don't throw cause because URISyntaxException shows password
|
191
|
-
throw new ConfigException("Failed to generate last_path due to URI parse failure that contains invalid file path or password.");
|
192
|
-
}
|
193
|
-
return true;
|
194
|
-
}
|
195
|
-
|
196
|
-
@Override
|
197
|
-
public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait) throws RetryGiveupException
|
198
|
-
{
|
199
|
-
String message = String.format("SFTP List request failed. Retrying %d/%d after %d seconds. Message: %s",
|
200
|
-
retryCount, retryLimit, retryWait / 1000, exception.getMessage());
|
201
|
-
if (retryCount % 3 == 0) {
|
202
|
-
log.warn(message, exception);
|
203
|
-
}
|
204
|
-
else {
|
205
|
-
log.warn(message);
|
206
|
-
}
|
207
|
-
}
|
208
|
-
|
209
|
-
@Override
|
210
|
-
public void onGiveup(Exception firstException, Exception lastException) throws RetryGiveupException
|
211
|
-
{
|
212
|
-
}
|
213
|
-
});
|
214
|
-
}
|
215
|
-
catch (RetryGiveupException ex) {
|
216
|
-
throw new ConfigException("Failed to generate last_path due to SFTP connection failure");
|
217
|
-
}
|
218
|
-
catch (InterruptedException ex) {
|
219
|
-
Throwables.propagate(ex);
|
171
|
+
catch (FileSystemException ex) {
|
172
|
+
throw new ConfigException("Failed to generate last_path due to sftp file name parse failure", ex);
|
220
173
|
}
|
221
|
-
return null;
|
222
174
|
}
|
223
175
|
|
224
176
|
public static FileList listFilesByPrefix(final PluginTask task)
|
@@ -247,6 +199,7 @@ public class SftpFileInput
|
|
247
199
|
FileObject files = manager.resolveFile(getSftpFileUri(task, task.getPathPrefix()), fsOptions);
|
248
200
|
String basename = FilenameUtils.getBaseName(task.getPathPrefix());
|
249
201
|
if (files.isFolder()) {
|
202
|
+
//path_prefix is a folder, we add everything in that folder
|
250
203
|
FileObject[] children = files.getChildren();
|
251
204
|
Arrays.sort(children);
|
252
205
|
for (FileObject f : children) {
|
@@ -255,7 +208,13 @@ public class SftpFileInput
|
|
255
208
|
}
|
256
209
|
}
|
257
210
|
}
|
211
|
+
else if (files.isFile()) {
|
212
|
+
//path_prefix is a file then we just need to add that file
|
213
|
+
addFileToList(builder, files.toString(), files.getContent().getSize(), "", lastKey);
|
214
|
+
}
|
258
215
|
else {
|
216
|
+
// path_prefix is neither file or folder, then we scan the parent folder to file path
|
217
|
+
// that match the path_prefix basename
|
259
218
|
FileObject parent = files.getParent();
|
260
219
|
FileObject[] children = parent.getChildren();
|
261
220
|
Arrays.sort(children);
|
@@ -68,6 +68,9 @@ public class TestSftpFileInputPlugin
|
|
68
68
|
@Rule
|
69
69
|
public TemporaryFolder testFolder = new TemporaryFolder();
|
70
70
|
|
71
|
+
@Rule
|
72
|
+
public TemporaryFolder sshDirFolder = new TemporaryFolder();
|
73
|
+
|
71
74
|
private Logger log = runtime.getExec().getLogger(TestSftpFileInputPlugin.class);
|
72
75
|
private ConfigSource config;
|
73
76
|
private SftpFileInputPlugin plugin;
|
@@ -87,11 +90,12 @@ public class TestSftpFileInputPlugin
|
|
87
90
|
@Before
|
88
91
|
public void createResources() throws Exception
|
89
92
|
{
|
93
|
+
// set system property for ssh_dir
|
94
|
+
System.setProperty("vfs.sftp.sshdir", sshDirFolder.getRoot().getPath());
|
90
95
|
config = config();
|
91
96
|
plugin = new SftpFileInputPlugin();
|
92
97
|
runner = new FileInputRunner(runtime.getInstance(SftpFileInputPlugin.class));
|
93
98
|
output = new MockPageOutput();
|
94
|
-
|
95
99
|
if (!log.isDebugEnabled()) {
|
96
100
|
// TODO: change logging format: org.apache.commons.logging.Log
|
97
101
|
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
|
@@ -162,7 +166,7 @@ public class TestSftpFileInputPlugin
|
|
162
166
|
public void testResume()
|
163
167
|
{
|
164
168
|
PluginTask task = config.loadConfig(PluginTask.class);
|
165
|
-
task.setFiles(createFileList(Arrays.asList("in/aa/a"), task));
|
169
|
+
task.setFiles(createFileList(Arrays.asList("/in/aa/a"), task));
|
166
170
|
ConfigDiff configDiff = plugin.resume(task.dump(), 0, new FileInputPlugin.Control()
|
167
171
|
{
|
168
172
|
@Override
|
@@ -171,7 +175,7 @@ public class TestSftpFileInputPlugin
|
|
171
175
|
return emptyTaskReports(taskCount);
|
172
176
|
}
|
173
177
|
});
|
174
|
-
assertEquals("in/aa/a", configDiff.get(String.class, "last_path"));
|
178
|
+
assertEquals("/in/aa/a", configDiff.get(String.class, "last_path"));
|
175
179
|
}
|
176
180
|
|
177
181
|
@Test
|
@@ -373,23 +377,63 @@ public class TestSftpFileInputPlugin
|
|
373
377
|
assertEquals(SftpFileSystemConfigBuilder.PROXY_STREAM, builder.getProxyType(fsOptions));
|
374
378
|
}
|
375
379
|
|
380
|
+
/**
|
381
|
+
* Test get relative path with special character password
|
382
|
+
*/
|
376
383
|
@Test
|
377
|
-
public void
|
384
|
+
public void testGetRelativePathWithPassword()
|
378
385
|
{
|
379
|
-
ConfigSource conf = config();
|
380
|
-
String expected = "/path/to/sample
|
386
|
+
ConfigSource conf = config.deepCopy();
|
387
|
+
String expected = "/path/to/sample !@#.csv";
|
381
388
|
|
382
389
|
conf.set("password", "ABCDE");
|
383
|
-
PluginTask task =
|
384
|
-
String uri = SftpFileInput.getSftpFileUri(task, "/path/to/sample
|
390
|
+
PluginTask task = conf.loadConfig(PluginTask.class);
|
391
|
+
String uri = SftpFileInput.getSftpFileUri(task, "/path/to/sample !@#.csv");
|
385
392
|
assertEquals(expected, SftpFileInput.getRelativePath(task, Optional.of(uri)));
|
386
393
|
|
387
394
|
conf.set("password", "ABCD#$¥!%'\"@?<>\\&/_^~|-=+-,{}[]()");
|
388
|
-
task =
|
389
|
-
uri = SftpFileInput.getSftpFileUri(task, "/path/to/sample
|
395
|
+
task = conf.loadConfig(PluginTask.class);
|
396
|
+
uri = SftpFileInput.getSftpFileUri(task, "/path/to/sample !@#.csv");
|
390
397
|
assertEquals(expected, SftpFileInput.getRelativePath(task, Optional.of(uri)));
|
391
398
|
}
|
392
399
|
|
400
|
+
@Test
|
401
|
+
public void testGetRelativePath()
|
402
|
+
{
|
403
|
+
String expected = "/path/to/sample !@#.csv";
|
404
|
+
String path = "/path/to/sample !@#.csv";
|
405
|
+
config.loadConfig(PluginTask.class);
|
406
|
+
assertEquals(expected, SftpFileInput.getRelativePath(null, Optional.of(path)));
|
407
|
+
}
|
408
|
+
|
409
|
+
@Test(expected = ConfigException.class)
|
410
|
+
public void testGetRelativePathWithHttpScheme()
|
411
|
+
{
|
412
|
+
String path = "http://host/path/to/sample !@#.csv";
|
413
|
+
config.loadConfig(PluginTask.class);
|
414
|
+
SftpFileInput.getRelativePath(null, Optional.of(path));
|
415
|
+
}
|
416
|
+
|
417
|
+
/**
|
418
|
+
* When user explicitly set path_prefix to a single file. we should add that file only
|
419
|
+
* @throws Exception
|
420
|
+
*/
|
421
|
+
@Test
|
422
|
+
public void testListByPrefixWithSpecificPathPrefix() throws Exception
|
423
|
+
{
|
424
|
+
ConfigSource conf = config.deepCopy();
|
425
|
+
conf.set("path_prefix", REMOTE_DIRECTORY + "sample_01.csv");
|
426
|
+
PluginTask pluginTask = conf.loadConfig(PluginTask.class);
|
427
|
+
uploadFile(Resources.getResource("sample_01.csv").getPath(), REMOTE_DIRECTORY + "sample_01.csv", true);
|
428
|
+
uploadFile(Resources.getResource("sample_01.csv").getPath(), REMOTE_DIRECTORY + "sample_01 .csv", true);
|
429
|
+
uploadFile(Resources.getResource("sample_01.csv").getPath(), REMOTE_DIRECTORY + "sample_01ABC.csv", true);
|
430
|
+
uploadFile(Resources.getResource("sample_01.csv").getPath(), REMOTE_DIRECTORY + "sample_01DEF!@#.csv", true);
|
431
|
+
FileList fileList = SftpFileInput.listFilesByPrefix(pluginTask);
|
432
|
+
assertEquals(1, fileList.getTaskCount());
|
433
|
+
assertEquals("sftp://username:password@127.0.0.1:20022/home/username/unittest/sample_01.csv", fileList.get
|
434
|
+
(0).get(0));
|
435
|
+
}
|
436
|
+
|
393
437
|
private SshServer createSshServer(String host, int port, final String sshUsername, final String sshPassword)
|
394
438
|
{
|
395
439
|
// setup a mock sftp server
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-sftp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Satoshi Akama
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
|
14
|
+
name: bundler
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
15
16
|
requirements:
|
16
17
|
- - ~>
|
17
18
|
- !ruby/object:Gem::Version
|
18
19
|
version: '1.0'
|
19
|
-
|
20
|
-
prerelease: false
|
21
|
-
type: :development
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirement: !ruby/object:Gem::Requirement
|
23
21
|
requirements:
|
24
22
|
- - ~>
|
25
23
|
- !ruby/object:Gem::Version
|
26
24
|
version: '1.0'
|
25
|
+
prerelease: false
|
26
|
+
type: :development
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
|
28
|
+
name: rake
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
31
|
- - '>='
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: '10.0'
|
33
|
-
|
34
|
-
prerelease: false
|
35
|
-
type: :development
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
37
35
|
requirements:
|
38
36
|
- - '>='
|
39
37
|
- !ruby/object:Gem::Version
|
40
38
|
version: '10.0'
|
39
|
+
prerelease: false
|
40
|
+
type: :development
|
41
41
|
description: Reads files stored on remote server using SFTP.
|
42
42
|
email:
|
43
43
|
- satoshiakama@gmail.com
|
@@ -72,7 +72,7 @@ files:
|
|
72
72
|
- classpath/commons-io-1.3.2.jar
|
73
73
|
- classpath/commons-logging-1.2.jar
|
74
74
|
- classpath/commons-vfs2-2.1.jar
|
75
|
-
- classpath/embulk-input-sftp-0.2.
|
75
|
+
- classpath/embulk-input-sftp-0.2.4.jar
|
76
76
|
- classpath/jsch-0.1.53.jar
|
77
77
|
homepage: https://github.com/embulk/embulk-input-sftp
|
78
78
|
licenses:
|
Binary file
|