embulk-input-sftp 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 20b62b0b1e6694bdaa818d0a993c69a62762dfe0
4
- data.tar.gz: d2cc4ef2c4eb0605cbd5df857840f13559d98112
3
+ metadata.gz: df31c9ece3349f98ed7d6cb077ea54d93eb03983
4
+ data.tar.gz: 86a2988731cc55a81ed7e191eb453b952a3d13a4
5
5
  SHA512:
6
- metadata.gz: 50e595f0263b65e5e360128730ca743ef8d66c2ee0002219e24bd4ff18b55c99516de51399fa4e4b2b9a86998cc0403623d52182bedb8226e95d59bbab82a404
7
- data.tar.gz: 67182a732de5b5d001630e99724b9b7b7c24383c17dab5628c67c61e41f3b8fe3bf4904227a881d65aedd962e9a3422f581fdecc8a1b8bd23a9520b54cbe7e84
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
@@ -135,6 +135,7 @@ in:
135
135
 
136
136
  ```
137
137
  $ ./gradlew gem # -t to watch change of files and rebuild continuously
138
+ $ ./gradlew bintrayUpload # release embulk-input-sftp to Bintray maven repo
138
139
  ```
139
140
 
140
141
  ## Test
data/build.gradle CHANGED
@@ -1,5 +1,6 @@
1
1
  plugins {
2
- id "com.jfrog.bintray" version "1.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
- version = "0.2.3"
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'
@@ -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
- return new URI(uri.get()).getPath();
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 (URISyntaxException ex) {
160
- throw new ConfigException("Failed to generate last_path due to URI parse failure that contains invalid file path.", ex);
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 testGetRelativePath()
384
+ public void testGetRelativePathWithPassword()
378
385
  {
379
- ConfigSource conf = config();
380
- String expected = "/path/to/sample.csv";
386
+ ConfigSource conf = config.deepCopy();
387
+ String expected = "/path/to/sample !@#.csv";
381
388
 
382
389
  conf.set("password", "ABCDE");
383
- PluginTask task = config.loadConfig(PluginTask.class);
384
- String uri = SftpFileInput.getSftpFileUri(task, "/path/to/sample.csv");
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 = config.loadConfig(PluginTask.class);
389
- uri = SftpFileInput.getSftpFileUri(task, "/path/to/sample.csv");
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.3
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: 2016-09-30 00:00:00.000000000 Z
11
+ date: 2017-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- requirement: !ruby/object:Gem::Requirement
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
- name: bundler
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
- requirement: !ruby/object:Gem::Requirement
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
- name: rake
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.3.jar
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