embulk-output-sftp 0.0.9 → 0.1.1
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 +3 -3
- data/build.gradle +68 -7
- data/classpath/commons-io-2.5.jar +0 -0
- data/classpath/embulk-output-sftp-0.1.1.jar +0 -0
- data/src/main/java/org/embulk/output/sftp/SftpFileOutput.java +84 -116
- metadata +19 -16
- data/classpath/embulk-output-sftp-0.0.9.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: 831b167ddd205c7ad62b1306a47c634eb7c52aa5
|
4
|
+
data.tar.gz: 2de997335de69c887b58507cd820be9593a06483
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfa0bf45de009ad868e66ded095e693f53aa3a5db03d166e6460ec686668e09018106868b055bf2fbe7aaa18522beb80701de7a85768910fcbb4bfa6331860e7
|
7
|
+
data.tar.gz: e7ea2ee48cc39c457a57dc368ac87918697aa85c1e372fe64a7740cbaccc360ec0ce10814bad6b7029bf51799b610b7d2c441a26a38edf7b8aeb141f35bee1d1
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
#
|
2
|
-
[![Build Status](https://travis-ci.org/
|
3
|
-
[![Coverage Status](https://coveralls.io/repos/civitaspo/embulk-output-sftp/badge.svg?branch=master&service=github)](https://coveralls.io/github/civitaspo/embulk-output-sftp?branch=master)
|
1
|
+
# SFTP file output plugin for Embulk
|
2
|
+
[![Build Status](https://travis-ci.org/embulk/embulk-output-sftp.svg)](https://travis-ci.org/embulk/embulk-output-sftp)
|
4
3
|
|
5
4
|
Stores files on a SFTP Server
|
6
5
|
|
@@ -125,6 +124,7 @@ $ embulk run -Ilib example/sample.yml
|
|
125
124
|
|
126
125
|
```
|
127
126
|
$ ./gradlew gem # -t to watch change of files and rebuild continuously
|
127
|
+
$ ./gradlew bintrayUpload # release embulk-output-sftp to Bintray maven repo
|
128
128
|
```
|
129
129
|
|
130
130
|
## Note
|
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 "com.github.kt3k.coveralls" version "2.4.0"
|
5
6
|
id "jacoco"
|
@@ -15,7 +16,8 @@ configurations {
|
|
15
16
|
provided
|
16
17
|
}
|
17
18
|
|
18
|
-
|
19
|
+
group = "org.embulk.output.sftp"
|
20
|
+
version = "0.1.1"
|
19
21
|
sourceCompatibility = 1.7
|
20
22
|
targetCompatibility = 1.7
|
21
23
|
|
@@ -24,6 +26,7 @@ dependencies {
|
|
24
26
|
provided "org.embulk:embulk-core:0.8.6"
|
25
27
|
// compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION"
|
26
28
|
compile "org.apache.commons:commons-vfs2:2.1.1660580.2"
|
29
|
+
compile "commons-io:commons-io:2.5"
|
27
30
|
compile "com.jcraft:jsch:0.1.53"
|
28
31
|
testCompile "junit:junit:4.+"
|
29
32
|
testCompile "org.embulk:embulk-core:0.8.6:tests"
|
@@ -40,6 +43,50 @@ jacocoTestReport {
|
|
40
43
|
}
|
41
44
|
}
|
42
45
|
|
46
|
+
javadoc {
|
47
|
+
options {
|
48
|
+
locale = 'en_US'
|
49
|
+
encoding = 'UTF-8'
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// bintray
|
54
|
+
bintray {
|
55
|
+
// write at your bintray user name and api key to ~/.gradle/gradle.properties file:
|
56
|
+
user = project.hasProperty('bintray_user') ? bintray_user : ''
|
57
|
+
key = project.hasProperty('bintray_api_key') ? bintray_api_key : ''
|
58
|
+
|
59
|
+
publications = ['bintrayMavenRelease']
|
60
|
+
publish = true
|
61
|
+
|
62
|
+
pkg {
|
63
|
+
userOrg = 'embulk-output-sftp'
|
64
|
+
repo = 'maven'
|
65
|
+
name = project.name
|
66
|
+
desc = 'SFTP output plugin for Embulk'
|
67
|
+
websiteUrl = 'https://github.com/embulk/embulk-output-sftp'
|
68
|
+
issueTrackerUrl = 'https://github.com/embulk/embulk-output-sftp/issues'
|
69
|
+
vcsUrl = 'https://github.com/embulk/embulk-output-sftp.git'
|
70
|
+
licenses = ['MIT']
|
71
|
+
labels = ['embulk', 'java']
|
72
|
+
publicDownloadNumbers = true
|
73
|
+
|
74
|
+
version {
|
75
|
+
name = project.version
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
publishing {
|
80
|
+
publications {
|
81
|
+
bintrayMavenRelease(MavenPublication) {
|
82
|
+
from components.java
|
83
|
+
artifact testsJar
|
84
|
+
artifact sourcesJar
|
85
|
+
artifact javadocJar
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
43
90
|
task classpath(type: Copy, dependsOn: ["jar"]) {
|
44
91
|
doFirst { file("classpath").deleteDir() }
|
45
92
|
from (configurations.runtime - configurations.provided + files(jar.archivePath))
|
@@ -47,6 +94,20 @@ task classpath(type: Copy, dependsOn: ["jar"]) {
|
|
47
94
|
}
|
48
95
|
clean { delete "classpath" }
|
49
96
|
|
97
|
+
// add tests/javadoc/source jar tasks as artifacts to be released
|
98
|
+
task testsJar(type: Jar, dependsOn: classes) {
|
99
|
+
classifier = 'tests'
|
100
|
+
from sourceSets.test.output
|
101
|
+
}
|
102
|
+
task sourcesJar(type: Jar, dependsOn: classes) {
|
103
|
+
classifier = 'sources'
|
104
|
+
from sourceSets.main.allSource
|
105
|
+
}
|
106
|
+
task javadocJar(type: Jar, dependsOn: javadoc) {
|
107
|
+
classifier = 'javadoc'
|
108
|
+
from javadoc.destinationDir
|
109
|
+
}
|
110
|
+
|
50
111
|
checkstyle {
|
51
112
|
configFile = file("${project.rootDir}/config/checkstyle/checkstyle.xml")
|
52
113
|
toolVersion = '6.14.1'
|
@@ -88,12 +149,12 @@ task gemspec {
|
|
88
149
|
Gem::Specification.new do |spec|
|
89
150
|
spec.name = "${project.name}"
|
90
151
|
spec.version = "${project.version}"
|
91
|
-
spec.authors = ["Civitaspo"]
|
92
|
-
spec.summary = %[
|
93
|
-
spec.description = %[Stores files on
|
94
|
-
spec.email = ["civitaspo@gmail.com"]
|
152
|
+
spec.authors = ["Civitaspo", "Satoshi Akama"]
|
153
|
+
spec.summary = %[SFTP file output plugin for Embulk]
|
154
|
+
spec.description = %[Stores files on SFTP server.]
|
155
|
+
spec.email = ["civitaspo@gmail.com", "satoshiakama@gmail.com"]
|
95
156
|
spec.licenses = ["MIT"]
|
96
|
-
spec.homepage = "https://github.com/
|
157
|
+
spec.homepage = "https://github.com/embulk/embulk-output-sftp"
|
97
158
|
|
98
159
|
spec.files = `git ls-files`.split("\n") + Dir["classpath/*.jar"]
|
99
160
|
spec.test_files = spec.files.grep(%r"^(test|spec)/")
|
Binary file
|
Binary file
|
@@ -2,6 +2,7 @@ package org.embulk.output.sftp;
|
|
2
2
|
|
3
3
|
import com.google.common.base.Function;
|
4
4
|
import com.google.common.base.Throwables;
|
5
|
+
import org.apache.commons.io.IOUtils;
|
5
6
|
import org.apache.commons.vfs2.FileObject;
|
6
7
|
import org.apache.commons.vfs2.FileSystemException;
|
7
8
|
import org.apache.commons.vfs2.FileSystemOptions;
|
@@ -15,15 +16,22 @@ import org.embulk.spi.Exec;
|
|
15
16
|
import org.embulk.spi.FileOutput;
|
16
17
|
import org.embulk.spi.TransactionalFileOutput;
|
17
18
|
import org.embulk.spi.unit.LocalFile;
|
19
|
+
import org.embulk.spi.util.RetryExecutor.RetryGiveupException;
|
20
|
+
import org.embulk.spi.util.RetryExecutor.Retryable;
|
18
21
|
import org.slf4j.Logger;
|
19
22
|
|
23
|
+
import java.io.BufferedInputStream;
|
24
|
+
import java.io.BufferedOutputStream;
|
20
25
|
import java.io.File;
|
26
|
+
import java.io.FileInputStream;
|
27
|
+
import java.io.FileNotFoundException;
|
28
|
+
import java.io.FileOutputStream;
|
21
29
|
import java.io.IOException;
|
22
|
-
import java.io.OutputStream;
|
23
30
|
import java.net.URI;
|
24
31
|
import java.net.URISyntaxException;
|
25
32
|
|
26
33
|
import static org.embulk.output.sftp.SftpFileOutputPlugin.PluginTask;
|
34
|
+
import static org.embulk.spi.util.RetryExecutor.retryExecutor;
|
27
35
|
|
28
36
|
/**
|
29
37
|
* Created by takahiro.nakayama on 10/20/15.
|
@@ -44,8 +52,8 @@ public class SftpFileOutput
|
|
44
52
|
|
45
53
|
private final int taskIndex;
|
46
54
|
private int fileIndex = 0;
|
47
|
-
private
|
48
|
-
private
|
55
|
+
private File tempFile;
|
56
|
+
private BufferedOutputStream localOutput = null;
|
49
57
|
|
50
58
|
private StandardFileSystemManager initializeStandardFileSystemManager()
|
51
59
|
{
|
@@ -144,11 +152,10 @@ public class SftpFileOutput
|
|
144
152
|
closeCurrentFile();
|
145
153
|
|
146
154
|
try {
|
147
|
-
|
148
|
-
|
149
|
-
logger.info("new sftp file: {}", currentFile.getPublicURIString());
|
155
|
+
tempFile = Exec.getTempFileSpace().createTempFile();
|
156
|
+
localOutput = new BufferedOutputStream(new FileOutputStream(tempFile));
|
150
157
|
}
|
151
|
-
catch (
|
158
|
+
catch (FileNotFoundException e) {
|
152
159
|
logger.error(e.getMessage());
|
153
160
|
Throwables.propagate(e);
|
154
161
|
}
|
@@ -157,28 +164,11 @@ public class SftpFileOutput
|
|
157
164
|
@Override
|
158
165
|
public void add(final Buffer buffer)
|
159
166
|
{
|
160
|
-
if (currentFile == null) {
|
161
|
-
throw new IllegalStateException("nextFile() must be called before poll()");
|
162
|
-
}
|
163
|
-
|
164
167
|
try {
|
165
|
-
|
166
|
-
public Void execute() throws IOException
|
167
|
-
{
|
168
|
-
currentFileOutputStream.write(buffer.array(), buffer.offset(), buffer.limit());
|
169
|
-
return null;
|
170
|
-
}
|
171
|
-
};
|
172
|
-
try {
|
173
|
-
withConnectionRetry(retriable);
|
174
|
-
}
|
175
|
-
catch (Exception e) {
|
176
|
-
throw (IOException) e;
|
177
|
-
}
|
168
|
+
localOutput.write(buffer.array(), buffer.offset(), buffer.limit());
|
178
169
|
}
|
179
|
-
catch (IOException
|
180
|
-
|
181
|
-
Throwables.propagate(e);
|
170
|
+
catch (IOException ex) {
|
171
|
+
throw Throwables.propagate(ex);
|
182
172
|
}
|
183
173
|
finally {
|
184
174
|
buffer.release();
|
@@ -189,6 +179,7 @@ public class SftpFileOutput
|
|
189
179
|
public void finish()
|
190
180
|
{
|
191
181
|
closeCurrentFile();
|
182
|
+
uploadFile(getOutputFilePath());
|
192
183
|
}
|
193
184
|
|
194
185
|
@Override
|
@@ -211,27 +202,68 @@ public class SftpFileOutput
|
|
211
202
|
|
212
203
|
private void closeCurrentFile()
|
213
204
|
{
|
214
|
-
if (
|
215
|
-
|
205
|
+
if (localOutput != null) {
|
206
|
+
try {
|
207
|
+
localOutput.close();
|
208
|
+
}
|
209
|
+
catch (IOException ex) {
|
210
|
+
throw Throwables.propagate(ex);
|
211
|
+
}
|
216
212
|
}
|
213
|
+
}
|
217
214
|
|
215
|
+
private Void uploadFile(final String remotePath)
|
216
|
+
{
|
218
217
|
try {
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
218
|
+
return retryExecutor()
|
219
|
+
.withRetryLimit(maxConnectionRetry)
|
220
|
+
.withInitialRetryWait(500)
|
221
|
+
.withMaxRetryWait(30 * 1000)
|
222
|
+
.runInterruptible(new Retryable<Void>() {
|
223
|
+
@Override
|
224
|
+
public Void call() throws IOException
|
225
|
+
{
|
226
|
+
FileObject remoteFile = newSftpFile(getSftpFileUri(remotePath));
|
227
|
+
logger.info("new sftp file: {}", remoteFile.getPublicURIString());
|
228
|
+
try (BufferedOutputStream outputStream = new BufferedOutputStream(remoteFile.getContent().getOutputStream())) {
|
229
|
+
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(tempFile))) {
|
230
|
+
IOUtils.copy(inputStream, outputStream);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
return null;
|
234
|
+
}
|
235
|
+
|
236
|
+
@Override
|
237
|
+
public boolean isRetryableException(Exception exception)
|
238
|
+
{
|
239
|
+
return true;
|
240
|
+
}
|
241
|
+
|
242
|
+
@Override
|
243
|
+
public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait) throws RetryGiveupException
|
244
|
+
{
|
245
|
+
String message = String.format("SFTP output failed. Retrying %d/%d after %d seconds. Message: %s",
|
246
|
+
retryCount, retryLimit, retryWait / 1000, exception.getMessage());
|
247
|
+
if (retryCount % 3 == 0) {
|
248
|
+
logger.warn(message, exception);
|
249
|
+
}
|
250
|
+
else {
|
251
|
+
logger.warn(message);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
@Override
|
256
|
+
public void onGiveup(Exception firstException, Exception lastException) throws RetryGiveupException
|
257
|
+
{
|
258
|
+
}
|
259
|
+
});
|
223
260
|
}
|
224
|
-
|
225
|
-
|
226
|
-
currentFile.close();
|
261
|
+
catch (RetryGiveupException ex) {
|
262
|
+
throw Throwables.propagate(ex.getCause());
|
227
263
|
}
|
228
|
-
catch (
|
229
|
-
|
264
|
+
catch (InterruptedException ex) {
|
265
|
+
throw Throwables.propagate(ex);
|
230
266
|
}
|
231
|
-
|
232
|
-
fileIndex++;
|
233
|
-
currentFile = null;
|
234
|
-
currentFileOutputStream = null;
|
235
267
|
}
|
236
268
|
|
237
269
|
private URI getSftpFileUri(String remoteFilePath)
|
@@ -250,84 +282,20 @@ public class SftpFileOutput
|
|
250
282
|
return pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + fileNameExtension;
|
251
283
|
}
|
252
284
|
|
253
|
-
|
254
|
-
{
|
255
|
-
/**
|
256
|
-
* Execute the operation with the given (or null) return value.
|
257
|
-
* @return any return value from the operation
|
258
|
-
* @throws Exception
|
259
|
-
*/
|
260
|
-
public T execute() throws Exception;
|
261
|
-
}
|
262
|
-
|
263
|
-
private <T> T withConnectionRetry(final Retriable<T> op)
|
264
|
-
throws Exception
|
285
|
+
private FileObject newSftpFile(final URI sftpUri) throws FileSystemException
|
265
286
|
{
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
return op.execute();
|
270
|
-
}
|
271
|
-
catch (final Exception e) {
|
272
|
-
if (++count > maxConnectionRetry) {
|
273
|
-
throw e;
|
274
|
-
}
|
275
|
-
logger.warn("failed to connect sftp server: " + e.getMessage(), e);
|
276
|
-
|
277
|
-
try {
|
278
|
-
long sleepTime = ((long) Math.pow(2, count) * 1000);
|
279
|
-
logger.warn("sleep in next connection retry: {} milliseconds", sleepTime);
|
280
|
-
Thread.sleep(sleepTime); // milliseconds
|
281
|
-
}
|
282
|
-
catch (InterruptedException e1) {
|
283
|
-
// Ignore this exception because this exception is just about `sleep`.
|
284
|
-
logger.warn(e1.getMessage(), e1);
|
285
|
-
}
|
286
|
-
logger.warn("retry to connect sftp server: " + count + " times");
|
287
|
-
}
|
287
|
+
FileObject file = manager.resolveFile(sftpUri.toString(), fsOptions);
|
288
|
+
if (file.exists()) {
|
289
|
+
file.delete();
|
288
290
|
}
|
289
|
-
|
290
|
-
|
291
|
-
private FileObject newSftpFile(final URI sftpUri)
|
292
|
-
throws FileSystemException
|
293
|
-
{
|
294
|
-
Retriable<FileObject> retriable = new Retriable<FileObject>() {
|
295
|
-
public FileObject execute() throws FileSystemException
|
296
|
-
{
|
297
|
-
FileObject file = manager.resolveFile(sftpUri.toString(), fsOptions);
|
298
|
-
if (file.getParent().exists()) {
|
299
|
-
logger.info("parent directory {} exists there", file.getParent().getPublicURIString());
|
300
|
-
}
|
301
|
-
else {
|
302
|
-
logger.info("trying to create parent directory {}", file.getParent().getPublicURIString());
|
303
|
-
file.getParent().createFolder();
|
304
|
-
}
|
305
|
-
return file;
|
306
|
-
}
|
307
|
-
};
|
308
|
-
try {
|
309
|
-
return withConnectionRetry(retriable);
|
310
|
-
}
|
311
|
-
catch (Exception e) {
|
312
|
-
throw (FileSystemException) e;
|
313
|
-
}
|
314
|
-
}
|
315
|
-
|
316
|
-
private OutputStream newSftpOutputStream(final FileObject file)
|
317
|
-
throws FileSystemException
|
318
|
-
{
|
319
|
-
Retriable<OutputStream> retriable = new Retriable<OutputStream>() {
|
320
|
-
public OutputStream execute() throws FileSystemException
|
321
|
-
{
|
322
|
-
return file.getContent().getOutputStream();
|
323
|
-
}
|
324
|
-
};
|
325
|
-
try {
|
326
|
-
return withConnectionRetry(retriable);
|
291
|
+
if (file.getParent().exists()) {
|
292
|
+
logger.info("parent directory {} exists there", file.getParent().getPublicURIString());
|
327
293
|
}
|
328
|
-
|
329
|
-
|
294
|
+
else {
|
295
|
+
logger.info("trying to create parent directory {}", file.getParent().getPublicURIString());
|
296
|
+
file.getParent().createFolder();
|
330
297
|
}
|
298
|
+
return file;
|
331
299
|
}
|
332
300
|
|
333
301
|
private Function<LocalFile, String> localFileToPathString()
|
metadata
CHANGED
@@ -1,46 +1,48 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-output-sftp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Civitaspo
|
8
|
+
- Satoshi Akama
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2017-
|
12
|
+
date: 2017-05-29 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
|
-
|
15
|
+
name: bundler
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
15
17
|
requirements:
|
16
18
|
- - ~>
|
17
19
|
- !ruby/object:Gem::Version
|
18
20
|
version: '1.0'
|
19
|
-
|
20
|
-
prerelease: false
|
21
|
-
type: :development
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - ~>
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '1.0'
|
26
|
+
prerelease: false
|
27
|
+
type: :development
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
|
-
|
29
|
+
name: rake
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
31
|
requirements:
|
30
32
|
- - '>='
|
31
33
|
- !ruby/object:Gem::Version
|
32
34
|
version: '10.0'
|
33
|
-
|
34
|
-
prerelease: false
|
35
|
-
type: :development
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - '>='
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '10.0'
|
41
|
-
|
40
|
+
prerelease: false
|
41
|
+
type: :development
|
42
|
+
description: Stores files on SFTP server.
|
42
43
|
email:
|
43
44
|
- civitaspo@gmail.com
|
45
|
+
- satoshiakama@gmail.com
|
44
46
|
executables: []
|
45
47
|
extensions: []
|
46
48
|
extra_rdoc_files: []
|
@@ -67,11 +69,12 @@ files:
|
|
67
69
|
- src/test/java/org/embulk/output/sftp/TestSftpFileOutputPlugin.java
|
68
70
|
- src/test/resources/id_rsa
|
69
71
|
- src/test/resources/id_rsa.pub
|
72
|
+
- classpath/commons-io-2.5.jar
|
70
73
|
- classpath/commons-logging-1.2.jar
|
71
74
|
- classpath/commons-vfs2-2.1.1660580.2.jar
|
72
|
-
- classpath/embulk-output-sftp-0.
|
75
|
+
- classpath/embulk-output-sftp-0.1.1.jar
|
73
76
|
- classpath/jsch-0.1.53.jar
|
74
|
-
homepage: https://github.com/
|
77
|
+
homepage: https://github.com/embulk/embulk-output-sftp
|
75
78
|
licenses:
|
76
79
|
- MIT
|
77
80
|
metadata: {}
|
@@ -94,5 +97,5 @@ rubyforge_project:
|
|
94
97
|
rubygems_version: 2.1.9
|
95
98
|
signing_key:
|
96
99
|
specification_version: 4
|
97
|
-
summary:
|
100
|
+
summary: SFTP file output plugin for Embulk
|
98
101
|
test_files: []
|
Binary file
|