embulk-input-remote 0.3.0 → 0.3.1
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 +4 -4
- data/.circleci/config.yml +31 -0
- data/.gitignore +7 -0
- data/build.gradle +23 -50
- data/classpath/annotations-13.0.jar +0 -0
- data/classpath/embulk-input-remote-0.3.1.jar +0 -0
- data/classpath/kotlin-stdlib-1.1.1.jar +0 -0
- data/config/checkstyle/checkstyle.xml +128 -0
- data/config/checkstyle/default.xml +108 -0
- data/gradle.properties +1 -0
- data/lib/embulk/input/remote.rb +1 -1
- data/src/main/kotlin/org/embulk/input/{RemoteFileInputPlugin.kt → remote/RemoteFileInputPlugin.kt} +69 -76
- data/src/main/kotlin/org/embulk/input/remote/SSHClient.kt +22 -21
- data/src/main/kotlin/org/embulk/input/remote/extensions.kt +11 -0
- data/src/test/kotlin/org/embulk/input/remote/TestRemoteFileInputPlugin.kt +186 -0
- metadata +15 -12
- data/.travis.yml +0 -21
- data/classpath/embulk-input-remote-0.3.0.jar +0 -0
- data/classpath/kotlin-runtime-1.0.6.jar +0 -0
- data/classpath/kotlin-stdlib-1.0.6.jar +0 -0
- data/settings.gradle +0 -3
- data/src/test/kotlin/org/embulk/input/TestRemoteFileInputPlugin.kt +0 -201
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d0aeb7be6efa2a882d28d13dc4a41c4d249042b
|
4
|
+
data.tar.gz: 7ffcbd5214290516ac430dea0149378b5b430a3c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2de2f136d2d4e521227c4551c299862e8554b999ba213650aee2dc53bab091cd5201cb72808985d07508c987df2ca14c426ebffdc1153063e8127023c0c1b60
|
7
|
+
data.tar.gz: 0f44d0e50cb93413c6d3afa0650695161a1fb4a5806e28665e29c9a566fe35f3feda6101d9df33d314b339b2b96647003adefa9fbf948b4ed158f6d0339eff0f
|
@@ -0,0 +1,31 @@
|
|
1
|
+
version: 2
|
2
|
+
jobs:
|
3
|
+
build:
|
4
|
+
machine:
|
5
|
+
enabled: true
|
6
|
+
working_directory: ~/embulk-input-remote
|
7
|
+
steps:
|
8
|
+
- checkout
|
9
|
+
|
10
|
+
- run: ssh-keygen -t ecdsa -f id_rsa_test -N ''
|
11
|
+
- run: docker-compose up -d
|
12
|
+
- run: docker-compose ps
|
13
|
+
|
14
|
+
- restore_cache:
|
15
|
+
key: embulk-input-remote-{{ checksum "build.gradle" }}
|
16
|
+
- run: ./gradlew checkstyle
|
17
|
+
- run: ./gradlew check --full-stacktrace
|
18
|
+
- save_cache:
|
19
|
+
paths:
|
20
|
+
- "~/.gradle"
|
21
|
+
key: embulk-input-remote-{{ checksum "build.gradle" }}
|
22
|
+
|
23
|
+
- deploy:
|
24
|
+
name: Push Gem to RubyGems.org and bump up
|
25
|
+
command: |
|
26
|
+
if [ "${CIRCLE_BRANCH}" == "release" ]; then
|
27
|
+
curl -f -u $RUBYGEMS_USER:$RUBYGEMS_PASSWORD https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
|
28
|
+
git checkout master
|
29
|
+
git reset --hard origin/master
|
30
|
+
./gradlew release -Prelease.useAutomaticVersion=true
|
31
|
+
fi
|
data/.gitignore
CHANGED
data/build.gradle
CHANGED
@@ -1,80 +1,53 @@
|
|
1
1
|
buildscript {
|
2
|
-
ext.
|
2
|
+
ext.kotlinVersion = '1.1.1'
|
3
3
|
repositories {
|
4
4
|
mavenCentral()
|
5
|
+
jcenter()
|
6
|
+
maven { url 'http://kamatama41.github.com/maven-repository/repository' }
|
5
7
|
}
|
6
8
|
dependencies {
|
7
|
-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
10
|
+
classpath "com.github.kamatama41:gradle-embulk-plugin:0.1.4"
|
11
|
+
classpath "net.researchgate:gradle-release:2.5.0"
|
8
12
|
}
|
9
13
|
}
|
10
14
|
|
11
|
-
plugins {
|
12
|
-
id "com.jfrog.bintray" version "1.1"
|
13
|
-
id "com.github.jruby-gradle.base" version "0.1.5"
|
14
|
-
id "java"
|
15
|
-
}
|
16
15
|
apply plugin: "kotlin"
|
16
|
+
apply plugin: "com.github.kamatama41.embulk"
|
17
|
+
apply plugin: "net.researchgate.release"
|
17
18
|
|
18
19
|
compileJava {
|
19
20
|
options.compilerArgs = ['-Xlint:all']
|
20
21
|
}
|
21
22
|
|
22
|
-
import com.github.jrubygradle.JRubyExec
|
23
23
|
repositories {
|
24
24
|
mavenCentral()
|
25
|
-
|
26
|
-
maven { url 'http://kamatama41.github.com/embulk-test-helpers/repository' }
|
27
|
-
}
|
28
|
-
configurations {
|
29
|
-
provided
|
25
|
+
maven { url 'http://kamatama41.github.com/maven-repository/repository' }
|
30
26
|
}
|
31
27
|
|
32
|
-
version = "0.3.0"
|
33
28
|
sourceCompatibility = 1.7
|
34
29
|
targetCompatibility = 1.7
|
35
30
|
|
36
31
|
dependencies {
|
37
|
-
compile "org.jetbrains.kotlin:kotlin-stdlib:$
|
38
|
-
compile "org.embulk:embulk-core:0.8.16"
|
32
|
+
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
39
33
|
compile "com.hierynomus:sshj:0.19.1"
|
40
34
|
compile "com.jcraft:jzlib:1.1.3"
|
41
|
-
|
42
|
-
testCompile 'com.kamatama41:embulk-test-helpers:0.1.2'
|
35
|
+
testCompile "com.github.kamatama41:embulk-test-helpers:0.3.2"
|
43
36
|
testCompile "com.github.docker-java:docker-java:3.0.7"
|
44
|
-
// Uncomment when using local embulk-test-helpers (and settings.gradle as well)
|
45
|
-
//testCompile project(':embulk-test-helpers')
|
46
|
-
}
|
47
|
-
|
48
|
-
task classpath(type: Copy, dependsOn: ["jar"]) {
|
49
|
-
doFirst { file("classpath").deleteDir() }
|
50
|
-
from (configurations.runtime - configurations.provided + files(jar.archivePath))
|
51
|
-
into "classpath"
|
52
37
|
}
|
53
|
-
clean { delete 'classpath' }
|
54
38
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
39
|
+
embulk {
|
40
|
+
version = "0.8.18"
|
41
|
+
category = "file-input"
|
42
|
+
name = "remote"
|
43
|
+
authors = ["Shinichi Ishimura"]
|
44
|
+
email = "shiketaudonko41@gmail.com"
|
45
|
+
description = "Reads files stored on Remote hosts by SCP."
|
46
|
+
licenses = ["MIT"]
|
47
|
+
homepage = "https://github.com/kamatama41/embulk-input-remote"
|
59
48
|
}
|
60
49
|
|
61
|
-
|
62
|
-
|
63
|
-
spec.name = "${project.name}"
|
64
|
-
spec.version = "${project.version}"
|
65
|
-
spec.authors = ["Shinichi Ishimura"]
|
66
|
-
spec.summary = %[Remote file input plugin for Embulk]
|
67
|
-
spec.description = %[Reads files stored on Remote hosts by SCP.]
|
68
|
-
spec.email = ["shiketaudonko41@gmail.com"]
|
69
|
-
spec.licenses = ["MIT"]
|
70
|
-
spec.homepage = "https://github.com/kamatama41/embulk-input-remote"
|
71
|
-
|
72
|
-
spec.files = `git ls-files`.split("\n") + Dir["classpath/*.jar"]
|
73
|
-
spec.test_files = spec.files.grep(%r"^(test|spec)/")
|
74
|
-
spec.require_paths = ["lib"]
|
75
|
-
|
76
|
-
spec.add_development_dependency 'bundler', ['~> 1.0']
|
77
|
-
spec.add_development_dependency 'rake', ['>= 10.0']
|
78
|
-
end
|
79
|
-
/$)
|
50
|
+
release {
|
51
|
+
git { requireBranch = 'master' }
|
80
52
|
}
|
53
|
+
afterReleaseBuild.dependsOn gemPush
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,128 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE module PUBLIC
|
3
|
+
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
4
|
+
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
5
|
+
<module name="Checker">
|
6
|
+
<!-- https://github.com/facebook/presto/blob/master/src/checkstyle/checks.xml -->
|
7
|
+
<module name="FileTabCharacter"/>
|
8
|
+
<module name="NewlineAtEndOfFile">
|
9
|
+
<property name="lineSeparator" value="lf"/>
|
10
|
+
</module>
|
11
|
+
<module name="RegexpMultiline">
|
12
|
+
<property name="format" value="\r"/>
|
13
|
+
<property name="message" value="Line contains carriage return"/>
|
14
|
+
</module>
|
15
|
+
<module name="RegexpMultiline">
|
16
|
+
<property name="format" value=" \n"/>
|
17
|
+
<property name="message" value="Line has trailing whitespace"/>
|
18
|
+
</module>
|
19
|
+
<module name="RegexpMultiline">
|
20
|
+
<property name="format" value="\{\n\n"/>
|
21
|
+
<property name="message" value="Blank line after opening brace"/>
|
22
|
+
</module>
|
23
|
+
<module name="RegexpMultiline">
|
24
|
+
<property name="format" value="\n\n\s*\}"/>
|
25
|
+
<property name="message" value="Blank line before closing brace"/>
|
26
|
+
</module>
|
27
|
+
<module name="RegexpMultiline">
|
28
|
+
<property name="format" value="\n\n\n"/>
|
29
|
+
<property name="message" value="Multiple consecutive blank lines"/>
|
30
|
+
</module>
|
31
|
+
<module name="RegexpMultiline">
|
32
|
+
<property name="format" value="\n\n\Z"/>
|
33
|
+
<property name="message" value="Blank line before end of file"/>
|
34
|
+
</module>
|
35
|
+
<module name="RegexpMultiline">
|
36
|
+
<property name="format" value="Preconditions\.checkNotNull"/>
|
37
|
+
<property name="message" value="Use of checkNotNull"/>
|
38
|
+
</module>
|
39
|
+
|
40
|
+
<module name="TreeWalker">
|
41
|
+
<module name="EmptyBlock">
|
42
|
+
<property name="option" value="text"/>
|
43
|
+
<property name="tokens" value="
|
44
|
+
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_IF,
|
45
|
+
LITERAL_FOR, LITERAL_TRY, LITERAL_WHILE, INSTANCE_INIT, STATIC_INIT"/>
|
46
|
+
</module>
|
47
|
+
<module name="EmptyStatement"/>
|
48
|
+
<module name="EmptyForInitializerPad"/>
|
49
|
+
<module name="EmptyForIteratorPad">
|
50
|
+
<property name="option" value="space"/>
|
51
|
+
</module>
|
52
|
+
<module name="MethodParamPad">
|
53
|
+
<property name="allowLineBreaks" value="true"/>
|
54
|
+
<property name="option" value="nospace"/>
|
55
|
+
</module>
|
56
|
+
<module name="ParenPad"/>
|
57
|
+
<module name="TypecastParenPad"/>
|
58
|
+
<module name="NeedBraces"/>
|
59
|
+
<module name="LeftCurly">
|
60
|
+
<property name="option" value="nl"/>
|
61
|
+
<property name="tokens" value="CLASS_DEF, CTOR_DEF, INTERFACE_DEF, METHOD_DEF"/>
|
62
|
+
</module>
|
63
|
+
<module name="LeftCurly">
|
64
|
+
<property name="option" value="eol"/>
|
65
|
+
<property name="tokens" value="
|
66
|
+
LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR,
|
67
|
+
LITERAL_IF, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE"/>
|
68
|
+
</module>
|
69
|
+
<module name="RightCurly">
|
70
|
+
<property name="option" value="alone"/>
|
71
|
+
</module>
|
72
|
+
<module name="GenericWhitespace"/>
|
73
|
+
<module name="WhitespaceAfter"/>
|
74
|
+
<module name="NoWhitespaceBefore"/>
|
75
|
+
|
76
|
+
<module name="UpperEll"/>
|
77
|
+
<module name="DefaultComesLast"/>
|
78
|
+
<module name="ArrayTypeStyle"/>
|
79
|
+
<module name="MultipleVariableDeclarations"/>
|
80
|
+
<module name="ModifierOrder"/>
|
81
|
+
<module name="OneStatementPerLine"/>
|
82
|
+
<module name="StringLiteralEquality"/>
|
83
|
+
<module name="MutableException"/>
|
84
|
+
<module name="EqualsHashCode"/>
|
85
|
+
<module name="InnerAssignment"/>
|
86
|
+
<module name="InterfaceIsType"/>
|
87
|
+
<module name="HideUtilityClassConstructor"/>
|
88
|
+
|
89
|
+
<module name="MemberName"/>
|
90
|
+
<module name="LocalVariableName"/>
|
91
|
+
<module name="LocalFinalVariableName"/>
|
92
|
+
<module name="TypeName"/>
|
93
|
+
<module name="PackageName"/>
|
94
|
+
<module name="ParameterName"/>
|
95
|
+
<module name="StaticVariableName"/>
|
96
|
+
<module name="ClassTypeParameterName">
|
97
|
+
<property name="format" value="^[A-Z][0-9]?$"/>
|
98
|
+
</module>
|
99
|
+
<module name="MethodTypeParameterName">
|
100
|
+
<property name="format" value="^[A-Z][0-9]?$"/>
|
101
|
+
</module>
|
102
|
+
|
103
|
+
<module name="AvoidStarImport"/>
|
104
|
+
<module name="RedundantImport"/>
|
105
|
+
<module name="UnusedImports"/>
|
106
|
+
<module name="ImportOrder">
|
107
|
+
<property name="groups" value="*,javax,java"/>
|
108
|
+
<property name="separated" value="true"/>
|
109
|
+
<property name="option" value="bottom"/>
|
110
|
+
<property name="sortStaticImportsAlphabetically" value="true"/>
|
111
|
+
</module>
|
112
|
+
|
113
|
+
<module name="WhitespaceAround">
|
114
|
+
<property name="allowEmptyConstructors" value="true"/>
|
115
|
+
<property name="allowEmptyMethods" value="true"/>
|
116
|
+
<property name="ignoreEnhancedForColon" value="false"/>
|
117
|
+
<property name="tokens" value="
|
118
|
+
ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN,
|
119
|
+
BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LE,
|
120
|
+
LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
121
|
+
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
122
|
+
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE,
|
123
|
+
LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL,
|
124
|
+
PLUS, PLUS_ASSIGN, QUESTION, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN,
|
125
|
+
STAR, STAR_ASSIGN, TYPE_EXTENSION_AND"/>
|
126
|
+
</module>
|
127
|
+
</module>
|
128
|
+
</module>
|
@@ -0,0 +1,108 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE module PUBLIC
|
3
|
+
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
4
|
+
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
5
|
+
<!--
|
6
|
+
This is a subset of ./checkstyle.xml which allows some loose styles
|
7
|
+
-->
|
8
|
+
<module name="Checker">
|
9
|
+
<module name="FileTabCharacter"/>
|
10
|
+
<module name="NewlineAtEndOfFile">
|
11
|
+
<property name="lineSeparator" value="lf"/>
|
12
|
+
</module>
|
13
|
+
<module name="RegexpMultiline">
|
14
|
+
<property name="format" value="\r"/>
|
15
|
+
<property name="message" value="Line contains carriage return"/>
|
16
|
+
</module>
|
17
|
+
<module name="RegexpMultiline">
|
18
|
+
<property name="format" value=" \n"/>
|
19
|
+
<property name="message" value="Line has trailing whitespace"/>
|
20
|
+
</module>
|
21
|
+
<module name="RegexpMultiline">
|
22
|
+
<property name="format" value="\n\n\n"/>
|
23
|
+
<property name="message" value="Multiple consecutive blank lines"/>
|
24
|
+
</module>
|
25
|
+
<module name="RegexpMultiline">
|
26
|
+
<property name="format" value="\n\n\Z"/>
|
27
|
+
<property name="message" value="Blank line before end of file"/>
|
28
|
+
</module>
|
29
|
+
|
30
|
+
<module name="TreeWalker">
|
31
|
+
<module name="EmptyBlock">
|
32
|
+
<property name="option" value="text"/>
|
33
|
+
<property name="tokens" value="
|
34
|
+
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_IF,
|
35
|
+
LITERAL_FOR, LITERAL_TRY, LITERAL_WHILE, INSTANCE_INIT, STATIC_INIT"/>
|
36
|
+
</module>
|
37
|
+
<module name="EmptyStatement"/>
|
38
|
+
<module name="EmptyForInitializerPad"/>
|
39
|
+
<module name="EmptyForIteratorPad">
|
40
|
+
<property name="option" value="space"/>
|
41
|
+
</module>
|
42
|
+
<module name="MethodParamPad">
|
43
|
+
<property name="allowLineBreaks" value="true"/>
|
44
|
+
<property name="option" value="nospace"/>
|
45
|
+
</module>
|
46
|
+
<module name="ParenPad"/>
|
47
|
+
<module name="TypecastParenPad"/>
|
48
|
+
<module name="NeedBraces"/>
|
49
|
+
<module name="LeftCurly">
|
50
|
+
<property name="option" value="nl"/>
|
51
|
+
<property name="tokens" value="CLASS_DEF, CTOR_DEF, INTERFACE_DEF, METHOD_DEF"/>
|
52
|
+
</module>
|
53
|
+
<module name="LeftCurly">
|
54
|
+
<property name="option" value="eol"/>
|
55
|
+
<property name="tokens" value="
|
56
|
+
LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR,
|
57
|
+
LITERAL_IF, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE"/>
|
58
|
+
</module>
|
59
|
+
<module name="RightCurly">
|
60
|
+
<property name="option" value="alone"/>
|
61
|
+
</module>
|
62
|
+
<module name="GenericWhitespace"/>
|
63
|
+
<module name="WhitespaceAfter"/>
|
64
|
+
<module name="NoWhitespaceBefore"/>
|
65
|
+
|
66
|
+
<module name="UpperEll"/>
|
67
|
+
<module name="DefaultComesLast"/>
|
68
|
+
<module name="ArrayTypeStyle"/>
|
69
|
+
<module name="MultipleVariableDeclarations"/>
|
70
|
+
<module name="ModifierOrder"/>
|
71
|
+
<module name="OneStatementPerLine"/>
|
72
|
+
<module name="StringLiteralEquality"/>
|
73
|
+
<module name="MutableException"/>
|
74
|
+
<module name="EqualsHashCode"/>
|
75
|
+
<module name="InnerAssignment"/>
|
76
|
+
<module name="InterfaceIsType"/>
|
77
|
+
<module name="HideUtilityClassConstructor"/>
|
78
|
+
|
79
|
+
<module name="MemberName"/>
|
80
|
+
<module name="LocalVariableName"/>
|
81
|
+
<module name="LocalFinalVariableName"/>
|
82
|
+
<module name="TypeName"/>
|
83
|
+
<module name="PackageName"/>
|
84
|
+
<module name="ParameterName"/>
|
85
|
+
<module name="StaticVariableName"/>
|
86
|
+
<module name="ClassTypeParameterName">
|
87
|
+
<property name="format" value="^[A-Z][0-9]?$"/>
|
88
|
+
</module>
|
89
|
+
<module name="MethodTypeParameterName">
|
90
|
+
<property name="format" value="^[A-Z][0-9]?$"/>
|
91
|
+
</module>
|
92
|
+
|
93
|
+
<module name="WhitespaceAround">
|
94
|
+
<property name="allowEmptyConstructors" value="true"/>
|
95
|
+
<property name="allowEmptyMethods" value="true"/>
|
96
|
+
<property name="ignoreEnhancedForColon" value="false"/>
|
97
|
+
<property name="tokens" value="
|
98
|
+
ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN,
|
99
|
+
BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LE,
|
100
|
+
LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
101
|
+
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
102
|
+
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE,
|
103
|
+
LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL,
|
104
|
+
PLUS, PLUS_ASSIGN, QUESTION, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN,
|
105
|
+
STAR, STAR_ASSIGN, TYPE_EXTENSION_AND"/>
|
106
|
+
</module>
|
107
|
+
</module>
|
108
|
+
</module>
|
data/gradle.properties
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
version=0.3.1
|
data/lib/embulk/input/remote.rb
CHANGED
data/src/main/kotlin/org/embulk/input/{RemoteFileInputPlugin.kt → remote/RemoteFileInputPlugin.kt}
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
package org.embulk.input
|
1
|
+
package org.embulk.input.remote
|
2
2
|
|
3
3
|
import com.fasterxml.jackson.annotation.JsonProperty
|
4
4
|
import com.google.common.base.Optional
|
@@ -10,7 +10,6 @@ import org.embulk.config.ConfigSource
|
|
10
10
|
import org.embulk.config.Task
|
11
11
|
import org.embulk.config.TaskReport
|
12
12
|
import org.embulk.config.TaskSource
|
13
|
-
import org.embulk.input.remote.SSHClient
|
14
13
|
import org.embulk.spi.BufferAllocator
|
15
14
|
import org.embulk.spi.Exec
|
16
15
|
import org.embulk.spi.FileInputPlugin
|
@@ -23,81 +22,78 @@ import java.io.IOException
|
|
23
22
|
import java.io.InputStream
|
24
23
|
import java.io.InputStreamReader
|
25
24
|
|
26
|
-
|
27
25
|
class RemoteFileInputPlugin : FileInputPlugin {
|
28
26
|
interface PluginTask : Task {
|
29
|
-
@Config("hosts")
|
30
|
-
@ConfigDefault("[]")
|
31
|
-
|
32
|
-
|
33
|
-
@Config("hosts_command")
|
34
|
-
@ConfigDefault("null")
|
35
|
-
fun getHostsCommand(): Optional<String>
|
27
|
+
@get:Config("hosts")
|
28
|
+
@get:ConfigDefault("[]")
|
29
|
+
val hosts: List<String>
|
36
30
|
|
37
|
-
@Config("
|
38
|
-
@ConfigDefault("
|
39
|
-
|
31
|
+
@get:Config("hosts_command")
|
32
|
+
@get:ConfigDefault("null")
|
33
|
+
val hostsCommand: Optional<String>
|
40
34
|
|
41
|
-
@Config("
|
42
|
-
@ConfigDefault("
|
43
|
-
|
35
|
+
@get:Config("hosts_separator")
|
36
|
+
@get:ConfigDefault("\" \"")
|
37
|
+
val hostsSeparator: String
|
44
38
|
|
45
|
-
@Config("
|
46
|
-
@ConfigDefault("
|
47
|
-
|
39
|
+
@get:Config("default_port")
|
40
|
+
@get:ConfigDefault("22")
|
41
|
+
val defaultPort: Int
|
48
42
|
|
49
|
-
@Config("
|
50
|
-
@ConfigDefault("
|
51
|
-
|
43
|
+
@get:Config("path")
|
44
|
+
@get:ConfigDefault("\"\"")
|
45
|
+
val path: String
|
52
46
|
|
53
|
-
@Config("
|
54
|
-
|
47
|
+
@get:Config("path_command")
|
48
|
+
@get:ConfigDefault("null")
|
49
|
+
val pathCommand: Optional<String>
|
55
50
|
|
56
|
-
@Config("
|
57
|
-
|
58
|
-
fun getIgnoreNotFoundHosts(): Boolean
|
51
|
+
@get:Config("auth")
|
52
|
+
val authConfig: AuthConfig
|
59
53
|
|
60
|
-
@Config("
|
61
|
-
@ConfigDefault("
|
62
|
-
|
54
|
+
@get:Config("ignore_not_found_hosts")
|
55
|
+
@get:ConfigDefault("false")
|
56
|
+
val ignoreNotFoundHosts: Boolean
|
63
57
|
|
64
|
-
|
58
|
+
@get:Config("done_targets")
|
59
|
+
@get:ConfigDefault("[]")
|
60
|
+
val doneTargets: List<Target>
|
65
61
|
|
66
|
-
|
62
|
+
var targets: List<Target>
|
67
63
|
|
68
|
-
@ConfigInject
|
69
|
-
|
64
|
+
@get:ConfigInject
|
65
|
+
val bufferAllocator: BufferAllocator
|
70
66
|
}
|
71
67
|
|
72
68
|
interface AuthConfig : Task {
|
73
|
-
@Config("type")
|
74
|
-
@ConfigDefault("\"public_key\"")
|
75
|
-
|
69
|
+
@get:Config("type")
|
70
|
+
@get:ConfigDefault("\"public_key\"")
|
71
|
+
val type: String
|
76
72
|
|
77
|
-
@Config("user")
|
78
|
-
@ConfigDefault("null")
|
79
|
-
|
73
|
+
@get:Config("user")
|
74
|
+
@get:ConfigDefault("null")
|
75
|
+
val user: Optional<String>
|
80
76
|
|
81
|
-
@Config("key_path")
|
82
|
-
@ConfigDefault("null")
|
83
|
-
|
77
|
+
@get:Config("key_path")
|
78
|
+
@get:ConfigDefault("null")
|
79
|
+
val keyPath: Optional<String>
|
84
80
|
|
85
|
-
@Config("password")
|
86
|
-
@ConfigDefault("null")
|
87
|
-
|
81
|
+
@get:Config("password")
|
82
|
+
@get:ConfigDefault("null")
|
83
|
+
val password: Optional<String>
|
88
84
|
|
89
|
-
@Config("skip_host_key_verification")
|
90
|
-
@ConfigDefault("false")
|
91
|
-
|
85
|
+
@get:Config("skip_host_key_verification")
|
86
|
+
@get:ConfigDefault("false")
|
87
|
+
val skipHostKeyVerification: Boolean
|
92
88
|
}
|
93
89
|
|
94
|
-
private val log =
|
90
|
+
private val log = getLogger()
|
95
91
|
|
96
92
|
override fun transaction(config: ConfigSource, control: FileInputPlugin.Control): ConfigDiff {
|
97
|
-
val task = config.loadConfig(
|
93
|
+
val task: PluginTask = config.loadConfig()
|
98
94
|
val targets = listTargets(task)
|
99
95
|
log.info("Loading targets $targets")
|
100
|
-
task.
|
96
|
+
task.targets = targets
|
101
97
|
|
102
98
|
// number of processors is same with number of targets
|
103
99
|
val taskCount = targets.size
|
@@ -105,21 +101,21 @@ class RemoteFileInputPlugin : FileInputPlugin {
|
|
105
101
|
}
|
106
102
|
|
107
103
|
override fun resume(taskSource: TaskSource, taskCount: Int, control: FileInputPlugin.Control): ConfigDiff {
|
108
|
-
val task = taskSource.loadTask(
|
104
|
+
val task: PluginTask = taskSource.loadTask()
|
109
105
|
|
110
106
|
control.run(taskSource, taskCount)
|
111
107
|
|
112
|
-
return Exec.newConfigDiff().set("done_targets", task.
|
108
|
+
return Exec.newConfigDiff().set("done_targets", task.targets)
|
113
109
|
}
|
114
110
|
|
115
111
|
override fun cleanup(taskSource: TaskSource, taskCount: Int, successTaskReports: MutableList<TaskReport>) {
|
116
112
|
}
|
117
113
|
|
118
114
|
override fun open(taskSource: TaskSource, taskIndex: Int): TransactionalFileInput {
|
119
|
-
val task = taskSource.loadTask(
|
120
|
-
val target = task.
|
115
|
+
val task: PluginTask = taskSource.loadTask()
|
116
|
+
val target = task.targets[taskIndex]
|
121
117
|
|
122
|
-
return object : InputStreamTransactionalFileInput(task.
|
118
|
+
return object : InputStreamTransactionalFileInput(task.bufferAllocator, { download(target, task) }) {
|
123
119
|
override fun abort() {
|
124
120
|
}
|
125
121
|
|
@@ -132,36 +128,34 @@ class RemoteFileInputPlugin : FileInputPlugin {
|
|
132
128
|
private fun listTargets(task: PluginTask): List<Target> {
|
133
129
|
val hosts = listHosts(task)
|
134
130
|
val path = getPath(task)
|
135
|
-
val doneTargets = task.
|
136
|
-
val ignoreNotFoundHosts = task.
|
131
|
+
val doneTargets = task.doneTargets
|
132
|
+
val ignoreNotFoundHosts = task.ignoreNotFoundHosts
|
137
133
|
|
138
134
|
return hosts.map {
|
139
135
|
val split = it.split(Regex(":"))
|
140
136
|
val host = split[0]
|
141
|
-
val port = if (split.size > 1) split[1].toInt() else task.
|
137
|
+
val port = if (split.size > 1) split[1].toInt() else task.defaultPort
|
142
138
|
Target(host, port, path)
|
143
139
|
}.filter {
|
144
140
|
!doneTargets.contains(it)
|
145
141
|
}.filter {
|
146
|
-
!ignoreNotFoundHosts || {
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
}
|
153
|
-
}()
|
142
|
+
!ignoreNotFoundHosts || try {
|
143
|
+
exists(it, task)
|
144
|
+
} catch (e: IOException) {
|
145
|
+
log.warn("failed to check the file exists. $it", e)
|
146
|
+
false
|
147
|
+
}
|
154
148
|
}
|
155
149
|
}
|
156
150
|
|
157
151
|
private fun listHosts(task: PluginTask): List<String> {
|
158
|
-
return task.
|
159
|
-
execCommand(it).split(task.
|
160
|
-
}.or(task.
|
152
|
+
return task.hostsCommand.transform {
|
153
|
+
execCommand(it).split(task.hostsSeparator.toRegex())
|
154
|
+
}.or(task.hosts)
|
161
155
|
}
|
162
156
|
|
163
157
|
private fun getPath(task: PluginTask): String {
|
164
|
-
return task.
|
158
|
+
return task.pathCommand.transform { execCommand(it) }.or(task.path)
|
165
159
|
}
|
166
160
|
|
167
161
|
private fun execCommand(command: String?): String {
|
@@ -183,9 +177,9 @@ class RemoteFileInputPlugin : FileInputPlugin {
|
|
183
177
|
}
|
184
178
|
|
185
179
|
private fun exists(target: Target, task: PluginTask): Boolean {
|
186
|
-
SSHClient.connect(target.host, target.port, task.
|
180
|
+
SSHClient.connect(target.host, target.port, task.authConfig).use { client ->
|
187
181
|
val checkCmd = "ls ${target.path}" // TODO: windows
|
188
|
-
val timeout = 5/*
|
182
|
+
val timeout = 5 /* seconds */
|
189
183
|
val commandResult = client.execCommand(checkCmd, timeout)
|
190
184
|
|
191
185
|
if (commandResult.status != 0) {
|
@@ -197,7 +191,7 @@ class RemoteFileInputPlugin : FileInputPlugin {
|
|
197
191
|
}
|
198
192
|
|
199
193
|
private fun download(target: Target, task: PluginTask): InputStream {
|
200
|
-
SSHClient.connect(target.host, target.port, task.
|
194
|
+
SSHClient.connect(target.host, target.port, task.authConfig).use { client ->
|
201
195
|
val stream = ByteArrayOutputStream()
|
202
196
|
client.scpDownload(target.path, stream)
|
203
197
|
return ByteArrayInputStream(stream.toByteArray())
|
@@ -208,7 +202,6 @@ class RemoteFileInputPlugin : FileInputPlugin {
|
|
208
202
|
@JsonProperty("host") val host: String,
|
209
203
|
@JsonProperty("port") val port: Int,
|
210
204
|
@JsonProperty("path") val path: String) {
|
211
|
-
|
212
205
|
override fun toString(): String {
|
213
206
|
return "$host:$port:$path"
|
214
207
|
}
|
@@ -1,47 +1,48 @@
|
|
1
1
|
package org.embulk.input.remote
|
2
2
|
|
3
3
|
import net.schmizz.sshj.DefaultConfig
|
4
|
+
import net.schmizz.sshj.SSHClient as SSHJ
|
4
5
|
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
|
5
6
|
import net.schmizz.sshj.xfer.InMemoryDestFile
|
6
7
|
import net.schmizz.sshj.xfer.LocalDestFile
|
7
|
-
import org.embulk.input.RemoteFileInputPlugin
|
8
8
|
import java.io.Closeable
|
9
9
|
import java.io.InputStream
|
10
10
|
import java.io.OutputStream
|
11
11
|
import java.util.concurrent.TimeUnit
|
12
12
|
|
13
|
-
class SSHClient private constructor(val client:
|
13
|
+
class SSHClient private constructor(val client: SSHJ) : Closeable {
|
14
14
|
companion object {
|
15
|
-
|
16
|
-
|
17
|
-
host: String, port: Int, authConfig: RemoteFileInputPlugin.AuthConfig
|
18
|
-
): SSHClient {
|
19
|
-
val client = SSHClient(net.schmizz.sshj.SSHClient(DefaultConfig()))
|
15
|
+
fun connect(host: String, port: Int, authConfig: RemoteFileInputPlugin.AuthConfig): SSHClient {
|
16
|
+
val client = SSHClient(SSHJ(DefaultConfig()))
|
20
17
|
client.connectToHost(host, port, authConfig)
|
21
18
|
return client
|
22
19
|
}
|
23
20
|
}
|
24
21
|
|
25
22
|
private fun connectToHost(host: String, port: Int, authConfig: RemoteFileInputPlugin.AuthConfig) {
|
26
|
-
if (authConfig.
|
23
|
+
if (authConfig.skipHostKeyVerification) {
|
27
24
|
client.addHostKeyVerifier(PromiscuousVerifier())
|
28
25
|
}
|
29
26
|
client.loadKnownHosts()
|
30
27
|
client.connect(host, port)
|
31
28
|
|
32
|
-
val type = authConfig.
|
33
|
-
val user = authConfig.
|
29
|
+
val type = authConfig.type
|
30
|
+
val user = authConfig.user.or(System.getProperty("user.name"))
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
when (type) {
|
33
|
+
"password" -> {
|
34
|
+
client.authPassword(user, authConfig.password.get())
|
35
|
+
}
|
36
|
+
"public_key" -> {
|
37
|
+
authConfig.keyPath.transform {
|
38
|
+
client.authPublickey(user, it)
|
39
|
+
}.or {
|
40
|
+
client.authPublickey(user)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
else -> {
|
44
|
+
throw UnsupportedOperationException("Unsupported auth type : $type")
|
42
45
|
}
|
43
|
-
} else {
|
44
|
-
throw UnsupportedOperationException("Unsupported auth type : " + type)
|
45
46
|
}
|
46
47
|
}
|
47
48
|
|
@@ -49,13 +50,13 @@ class SSHClient private constructor(val client: net.schmizz.sshj.SSHClient) : Cl
|
|
49
50
|
client.startSession().use { session ->
|
50
51
|
val cmd = session.exec(command)
|
51
52
|
cmd.join(timeoutSecond.toLong(), TimeUnit.SECONDS)
|
52
|
-
return CommandResult(cmd.exitStatus
|
53
|
+
return CommandResult(cmd.exitStatus, cmd.inputStream)
|
53
54
|
}
|
54
55
|
}
|
55
56
|
|
56
57
|
fun scpDownload(path: String, stream: OutputStream) {
|
57
58
|
client.useCompression()
|
58
|
-
client.newSCPFileTransfer().download(path, object :InMemoryDestFile() {
|
59
|
+
client.newSCPFileTransfer().download(path, object : InMemoryDestFile() {
|
59
60
|
override fun getOutputStream(): OutputStream {
|
60
61
|
return stream
|
61
62
|
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
package org.embulk.input.remote
|
2
|
+
|
3
|
+
import org.embulk.config.ConfigSource
|
4
|
+
import org.embulk.config.TaskSource
|
5
|
+
import org.embulk.spi.Exec
|
6
|
+
|
7
|
+
inline fun <reified T : Any> ConfigSource.loadConfig() = loadConfig(T::class.java)!!
|
8
|
+
|
9
|
+
inline fun <reified T : Any> TaskSource.loadTask() = loadTask(T::class.java)!!
|
10
|
+
|
11
|
+
fun Any.getLogger() = Exec.getLogger(javaClass)!!
|
@@ -0,0 +1,186 @@
|
|
1
|
+
package org.embulk.input.remote
|
2
|
+
|
3
|
+
import ch.qos.logback.classic.Level
|
4
|
+
import ch.qos.logback.classic.Logger
|
5
|
+
import com.github.dockerjava.core.DockerClientBuilder
|
6
|
+
import org.embulk.config.ConfigSource
|
7
|
+
import org.embulk.test.EmbulkPluginTest
|
8
|
+
import org.embulk.test.TestOutputPlugin.Matcher.assertRecords
|
9
|
+
import org.embulk.test.configFromResource
|
10
|
+
import org.embulk.test.record
|
11
|
+
import org.embulk.test.registerPlugin
|
12
|
+
import org.embulk.test.set
|
13
|
+
import org.hamcrest.CoreMatchers.`is`
|
14
|
+
import org.hamcrest.MatcherAssert.assertThat
|
15
|
+
import org.junit.Before
|
16
|
+
import org.junit.Ignore
|
17
|
+
import org.junit.Test
|
18
|
+
import org.slf4j.LoggerFactory
|
19
|
+
|
20
|
+
class TestRemoteFileInputPlugin : EmbulkPluginTest() {
|
21
|
+
@Before fun setup() {
|
22
|
+
builder.registerPlugin(RemoteFileInputPlugin::class)
|
23
|
+
|
24
|
+
// Setup docker container
|
25
|
+
startContainer(CONTAINER_ID_HOST1)
|
26
|
+
startContainer(CONTAINER_ID_HOST2)
|
27
|
+
|
28
|
+
System.getenv("LOG_LEVEL")?.let {
|
29
|
+
// Set log level
|
30
|
+
val rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as Logger
|
31
|
+
rootLogger.level = Level.toLevel(it)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
@Test fun loadFromRemote() {
|
36
|
+
runInput(baseConfig())
|
37
|
+
assertRecords(record(1, "user1"))
|
38
|
+
}
|
39
|
+
|
40
|
+
@Ignore("Cannot pass on TravisCI, although pass on Local Mac OS...")
|
41
|
+
@Test fun loadFromRemoteViaPublicKey() {
|
42
|
+
val keyPath = System.getenv("KEY_PATH") ?: "./id_rsa_test"
|
43
|
+
|
44
|
+
val publicKeyAuth = config().set("auth" to config().set(
|
45
|
+
"type" to "public_key",
|
46
|
+
"key_path" to keyPath
|
47
|
+
))
|
48
|
+
runInput(baseConfig().merge(publicKeyAuth))
|
49
|
+
|
50
|
+
assertRecords(record(1, "user1"))
|
51
|
+
}
|
52
|
+
|
53
|
+
@Test fun testMultiHosts() {
|
54
|
+
val multiHosts = config()
|
55
|
+
.set("hosts", listOf("localhost:10022", "localhost:10023"))
|
56
|
+
|
57
|
+
// Run
|
58
|
+
runInput(baseConfig().merge(multiHosts))
|
59
|
+
assertRecords(
|
60
|
+
record(1, "user1"),
|
61
|
+
record(2, "user2")
|
62
|
+
)
|
63
|
+
}
|
64
|
+
|
65
|
+
@Test fun loadAllFilesInDirectory() {
|
66
|
+
val directoryPath = config().set("path", "/mount")
|
67
|
+
|
68
|
+
runInput(baseConfig().merge(directoryPath))
|
69
|
+
assertRecords(
|
70
|
+
record(1L, "user1"),
|
71
|
+
record(1L, "command_user1")
|
72
|
+
)
|
73
|
+
}
|
74
|
+
|
75
|
+
@Test fun testDefaultPort() {
|
76
|
+
val defaultPort = config().set(
|
77
|
+
"hosts" to listOf("localhost"),
|
78
|
+
"default_port" to 10022
|
79
|
+
)
|
80
|
+
|
81
|
+
runInput(baseConfig().merge(defaultPort))
|
82
|
+
|
83
|
+
assertRecords(record(1L, "user1"))
|
84
|
+
}
|
85
|
+
|
86
|
+
@Test fun testConfDiff() {
|
87
|
+
val host2Config = config().set("hosts", listOf("localhost:10023"))
|
88
|
+
|
89
|
+
// Run
|
90
|
+
val runResult = runInput(baseConfig().merge(host2Config))
|
91
|
+
assertRecords(record(2, "user2"))
|
92
|
+
|
93
|
+
// Re-run with additional host1
|
94
|
+
val multiHost = config().set("hosts", listOf("localhost:10022", "localhost:10023"))
|
95
|
+
runInput(baseConfig().merge(multiHost), runResult.configDiff)
|
96
|
+
|
97
|
+
assertRecords(record(1, "user1"))
|
98
|
+
}
|
99
|
+
|
100
|
+
@Test fun testResume() {
|
101
|
+
// Stop host2 temporarily
|
102
|
+
stopContainer(CONTAINER_ID_HOST2)
|
103
|
+
|
104
|
+
// Run (but will fail)
|
105
|
+
val multiHost = config().set("hosts", listOf("localhost:10022", "localhost:10023"))
|
106
|
+
val config = baseConfig().merge(multiHost)
|
107
|
+
var resumableResult = resume(config)
|
108
|
+
|
109
|
+
assertThat(resumableResult.isSuccessful, `is`(false))
|
110
|
+
assertRecords(record(1, "user1"))
|
111
|
+
|
112
|
+
// Start host2 again
|
113
|
+
startContainer(CONTAINER_ID_HOST2)
|
114
|
+
|
115
|
+
// Resume
|
116
|
+
resumableResult = resume(config, resumableResult.resumeState)
|
117
|
+
|
118
|
+
assertThat(resumableResult.isSuccessful, `is`(true))
|
119
|
+
assertRecords(record(2, "user2"))
|
120
|
+
}
|
121
|
+
|
122
|
+
@Test fun testIgnoreNotFoundHosts() {
|
123
|
+
val ignoreNotFoundHosts = config().set(
|
124
|
+
"hosts" to listOf("localhost:10022", "localhost:10023"),
|
125
|
+
"ignore_not_found_hosts" to true
|
126
|
+
)
|
127
|
+
val config = baseConfig().merge(ignoreNotFoundHosts)
|
128
|
+
|
129
|
+
// Stop host2
|
130
|
+
stopContainer(CONTAINER_ID_HOST2)
|
131
|
+
|
132
|
+
// Run (host2 will be ignored)
|
133
|
+
val resumableResult = resume(config)
|
134
|
+
|
135
|
+
assertThat<Boolean>(resumableResult.isSuccessful, `is`(true))
|
136
|
+
assertRecords(record(1, "user1"))
|
137
|
+
}
|
138
|
+
|
139
|
+
@Test fun testCommandOptions() {
|
140
|
+
val ignoreNotFoundHosts = config().set(
|
141
|
+
"hosts_command" to "./src/test/resources/script/hosts.sh",
|
142
|
+
"hosts_separator" to "\n",
|
143
|
+
"path_command" to "echo '/mount/test_command.csv'"
|
144
|
+
)
|
145
|
+
runInput(baseConfig().merge(ignoreNotFoundHosts))
|
146
|
+
|
147
|
+
assertRecords(
|
148
|
+
record(1, "command_user1"),
|
149
|
+
record(2, "command_user2")
|
150
|
+
)
|
151
|
+
}
|
152
|
+
|
153
|
+
//////////////////////////////
|
154
|
+
// Helpers
|
155
|
+
//////////////////////////////
|
156
|
+
|
157
|
+
private fun baseConfig(): ConfigSource {
|
158
|
+
return configFromResource("yaml/base.yml")
|
159
|
+
}
|
160
|
+
|
161
|
+
companion object DockerUtils {
|
162
|
+
private val CONTAINER_ID_HOST1 = "embulkinputremote_host1_1"
|
163
|
+
private val CONTAINER_ID_HOST2 = "embulkinputremote_host2_1"
|
164
|
+
private val dockerClient = DockerClientBuilder.getInstance().build()
|
165
|
+
|
166
|
+
private fun stopContainer(containerId: String) {
|
167
|
+
if (isRunning(containerId)) {
|
168
|
+
dockerClient.stopContainerCmd(containerId).exec()
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
private fun startContainer(containerId: String) {
|
173
|
+
if (!isRunning(containerId)) {
|
174
|
+
dockerClient.startContainerCmd(containerId).exec()
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
private fun isRunning(containerId: String): Boolean {
|
179
|
+
return dockerClient.listContainersCmd().exec().any { container ->
|
180
|
+
container.names.any { name ->
|
181
|
+
name.contains(containerId)
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-remote
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shinichi Ishimura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,36 +45,39 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- .circleci/config.yml
|
48
49
|
- .gitignore
|
49
|
-
- .travis.yml
|
50
50
|
- Dockerfile
|
51
51
|
- LICENSE.txt
|
52
52
|
- README.md
|
53
53
|
- build.gradle
|
54
|
+
- config/checkstyle/checkstyle.xml
|
55
|
+
- config/checkstyle/default.xml
|
54
56
|
- docker-compose.yml
|
57
|
+
- gradle.properties
|
55
58
|
- gradle/wrapper/gradle-wrapper.jar
|
56
59
|
- gradle/wrapper/gradle-wrapper.properties
|
57
60
|
- gradlew
|
58
61
|
- gradlew.bat
|
59
62
|
- lib/embulk/input/remote.rb
|
60
|
-
-
|
61
|
-
- src/main/kotlin/org/embulk/input/RemoteFileInputPlugin.kt
|
63
|
+
- src/main/kotlin/org/embulk/input/remote/RemoteFileInputPlugin.kt
|
62
64
|
- src/main/kotlin/org/embulk/input/remote/SSHClient.kt
|
63
|
-
- src/
|
65
|
+
- src/main/kotlin/org/embulk/input/remote/extensions.kt
|
66
|
+
- src/test/kotlin/org/embulk/input/remote/TestRemoteFileInputPlugin.kt
|
64
67
|
- src/test/resources/input/host1/test.csv
|
65
68
|
- src/test/resources/input/host1/test_command.csv
|
66
69
|
- src/test/resources/input/host2/test.csv
|
67
70
|
- src/test/resources/input/host2/test_command.csv
|
68
71
|
- src/test/resources/script/hosts.sh
|
69
72
|
- src/test/resources/yaml/base.yml
|
70
|
-
- classpath/
|
73
|
+
- classpath/embulk-input-remote-0.3.1.jar
|
74
|
+
- classpath/sshj-0.19.1.jar
|
71
75
|
- classpath/bcprov-jdk15on-1.51.jar
|
72
|
-
- classpath/eddsa-0.1.0.jar
|
73
|
-
- classpath/embulk-input-remote-0.3.0.jar
|
74
76
|
- classpath/jzlib-1.1.3.jar
|
75
|
-
- classpath/
|
76
|
-
- classpath/
|
77
|
-
- classpath/
|
77
|
+
- classpath/annotations-13.0.jar
|
78
|
+
- classpath/bcpkix-jdk15on-1.51.jar
|
79
|
+
- classpath/eddsa-0.1.0.jar
|
80
|
+
- classpath/kotlin-stdlib-1.1.1.jar
|
78
81
|
homepage: https://github.com/kamatama41/embulk-input-remote
|
79
82
|
licenses:
|
80
83
|
- MIT
|
data/.travis.yml
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
sudo: required
|
2
|
-
|
3
|
-
language: java
|
4
|
-
|
5
|
-
jdk:
|
6
|
-
- oraclejdk8
|
7
|
-
|
8
|
-
env:
|
9
|
-
- >-
|
10
|
-
KEY_PATH=$PWD/id_rsa_test
|
11
|
-
|
12
|
-
services:
|
13
|
-
- docker
|
14
|
-
|
15
|
-
before_install:
|
16
|
-
- ssh-keygen -t ecdsa -f ${KEY_PATH} -N ''
|
17
|
-
- docker-compose up -d
|
18
|
-
- docker-compose ps
|
19
|
-
|
20
|
-
script:
|
21
|
-
- ./gradlew --info check
|
Binary file
|
Binary file
|
Binary file
|
data/settings.gradle
DELETED
@@ -1,201 +0,0 @@
|
|
1
|
-
package org.embulk.input
|
2
|
-
|
3
|
-
import ch.qos.logback.classic.Level
|
4
|
-
import ch.qos.logback.classic.Logger
|
5
|
-
import com.github.dockerjava.core.DockerClientBuilder
|
6
|
-
import org.embulk.config.ConfigSource
|
7
|
-
import org.embulk.spi.InputPlugin
|
8
|
-
import org.embulk.test.EmbulkPluginTest
|
9
|
-
import org.embulk.test.ExtendedEmbulkTests
|
10
|
-
import org.embulk.test.TestOutputPlugin.assertRecords
|
11
|
-
import org.embulk.test.TestingEmbulk
|
12
|
-
import org.embulk.test.Utils.record
|
13
|
-
import org.hamcrest.CoreMatchers.`is`
|
14
|
-
import org.hamcrest.MatcherAssert.assertThat
|
15
|
-
import org.junit.Ignore
|
16
|
-
import org.junit.Test
|
17
|
-
import org.slf4j.LoggerFactory
|
18
|
-
import java.util.Arrays
|
19
|
-
|
20
|
-
class TestRemoteFileInputPlugin : EmbulkPluginTest() {
|
21
|
-
private val CONTAINER_ID_HOST1 = "embulkinputremote_host1_1"
|
22
|
-
private val CONTAINER_ID_HOST2 = "embulkinputremote_host2_1"
|
23
|
-
private val dockerClient = DockerClientBuilder.getInstance().build()
|
24
|
-
|
25
|
-
override fun setup(builder: TestingEmbulk.Builder) {
|
26
|
-
builder.registerPlugin(InputPlugin::class.java, "remote", RemoteFileInputPlugin::class.java)
|
27
|
-
|
28
|
-
// Setup docker container
|
29
|
-
startContainer(CONTAINER_ID_HOST1)
|
30
|
-
startContainer(CONTAINER_ID_HOST2)
|
31
|
-
|
32
|
-
val logLevel = System.getenv("LOG_LEVEL")
|
33
|
-
if (logLevel != null) {
|
34
|
-
// Set log level
|
35
|
-
val rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as Logger
|
36
|
-
rootLogger.level = Level.toLevel(logLevel)
|
37
|
-
}
|
38
|
-
}
|
39
|
-
|
40
|
-
@Test fun loadFromRemote() {
|
41
|
-
runInput(baseConfig())
|
42
|
-
assertRecords(record(1, "user1"))
|
43
|
-
}
|
44
|
-
|
45
|
-
@Ignore("Cannot pass on TravisCI, although pass on Local Mac OS...")
|
46
|
-
@Test fun loadFromRemoteViaPublicKey() {
|
47
|
-
var keyPath: String? = System.getenv("KEY_PATH")
|
48
|
-
if (keyPath == null) {
|
49
|
-
keyPath = "./id_rsa_test"
|
50
|
-
}
|
51
|
-
|
52
|
-
val publicKeyAuth = newConfig().set("auth", newConfig()
|
53
|
-
.set("type", "public_key")
|
54
|
-
.set("key_path", keyPath)
|
55
|
-
)
|
56
|
-
runInput(baseConfig().merge(publicKeyAuth))
|
57
|
-
|
58
|
-
assertRecords(record(1, "user1"))
|
59
|
-
}
|
60
|
-
|
61
|
-
@Test fun testMultiHosts() {
|
62
|
-
val multiHosts = newConfig()
|
63
|
-
.set("hosts", Arrays.asList("localhost:10022", "localhost:10023"))
|
64
|
-
val config = baseConfig().merge(multiHosts)
|
65
|
-
|
66
|
-
// Run
|
67
|
-
runInput(config)
|
68
|
-
assertRecords(
|
69
|
-
record(1, "user1"),
|
70
|
-
record(2, "user2")
|
71
|
-
)
|
72
|
-
}
|
73
|
-
|
74
|
-
@Test fun loadAllFilesInDirectory() {
|
75
|
-
val directoryPath = newConfig()
|
76
|
-
.set("path", "/mount")
|
77
|
-
val config = baseConfig().merge(directoryPath)
|
78
|
-
|
79
|
-
runInput(config)
|
80
|
-
assertRecords(
|
81
|
-
record(1L, "user1"),
|
82
|
-
record(1L, "command_user1")
|
83
|
-
)
|
84
|
-
}
|
85
|
-
|
86
|
-
@Test fun testDefaultPort() {
|
87
|
-
val defaultPort = newConfig()
|
88
|
-
.set("hosts", listOf("localhost"))
|
89
|
-
.set("default_port", 10022)
|
90
|
-
|
91
|
-
runInput(baseConfig().merge(defaultPort))
|
92
|
-
|
93
|
-
assertRecords(record(1L, "user1"))
|
94
|
-
}
|
95
|
-
|
96
|
-
@Test fun testConfDiff() {
|
97
|
-
val host2Config = newConfig()
|
98
|
-
.set("hosts", listOf("localhost:10023"))
|
99
|
-
var config = baseConfig().merge(host2Config)
|
100
|
-
|
101
|
-
// Run
|
102
|
-
val runResult = runInput(config)
|
103
|
-
assertRecords(record(2, "user2"))
|
104
|
-
|
105
|
-
// Re-run with additional host1
|
106
|
-
val multiHost = newConfig()
|
107
|
-
.set("hosts", Arrays.asList("localhost:10022", "localhost:10023"))
|
108
|
-
config = baseConfig().merge(multiHost)
|
109
|
-
|
110
|
-
runInput(config, runResult.configDiff)
|
111
|
-
|
112
|
-
assertRecords(record(1, "user1"))
|
113
|
-
}
|
114
|
-
|
115
|
-
@Test fun testResume() {
|
116
|
-
val multiHost = newConfig()
|
117
|
-
.set("hosts", Arrays.asList("localhost:10022", "localhost:10023"))
|
118
|
-
val config = baseConfig().merge(multiHost)
|
119
|
-
|
120
|
-
// Stop host2 temporarily
|
121
|
-
stopContainer(CONTAINER_ID_HOST2)
|
122
|
-
|
123
|
-
// Run (but will fail)
|
124
|
-
var resumableResult = resume(config)
|
125
|
-
|
126
|
-
assertThat(resumableResult.isSuccessful, `is`(false))
|
127
|
-
assertRecords(record(1, "user1"))
|
128
|
-
|
129
|
-
// Start host2 again
|
130
|
-
startContainer(CONTAINER_ID_HOST2)
|
131
|
-
|
132
|
-
// Resume
|
133
|
-
resumableResult = resume(config, resumableResult.resumeState)
|
134
|
-
|
135
|
-
assertThat(resumableResult.isSuccessful, `is`(true))
|
136
|
-
assertRecords(record(2, "user2"))
|
137
|
-
}
|
138
|
-
|
139
|
-
@Test fun testIgnoreNotFoundHosts() {
|
140
|
-
val ignoreNotFoundHosts = newConfig()
|
141
|
-
.set("hosts", Arrays.asList("localhost:10022", "localhost:10023"))
|
142
|
-
.set("ignore_not_found_hosts", true)
|
143
|
-
val config = baseConfig().merge(ignoreNotFoundHosts)
|
144
|
-
|
145
|
-
// Stop host2
|
146
|
-
stopContainer(CONTAINER_ID_HOST2)
|
147
|
-
|
148
|
-
// Run (host2 will be ignored)
|
149
|
-
val resumableResult = resume(config)
|
150
|
-
|
151
|
-
assertThat<Boolean>(resumableResult.isSuccessful, `is`(true))
|
152
|
-
assertRecords(record(1, "user1"))
|
153
|
-
}
|
154
|
-
|
155
|
-
@Test fun testCommandOptions() {
|
156
|
-
val ignoreNotFoundHosts = newConfig()
|
157
|
-
.set("hosts_command", "./src/test/resources/script/hosts.sh")
|
158
|
-
.set("hosts_separator", "\n")
|
159
|
-
.set("path_command", "echo '/mount/test_command.csv'")
|
160
|
-
val config = baseConfig().merge(ignoreNotFoundHosts)
|
161
|
-
|
162
|
-
runInput(config)
|
163
|
-
|
164
|
-
assertRecords(
|
165
|
-
record(1, "command_user1"),
|
166
|
-
record(2, "command_user2")
|
167
|
-
)
|
168
|
-
}
|
169
|
-
|
170
|
-
//////////////////////////////
|
171
|
-
// Helpers
|
172
|
-
//////////////////////////////
|
173
|
-
|
174
|
-
private fun baseConfig(): ConfigSource {
|
175
|
-
return ExtendedEmbulkTests.configFromResource("yaml/base.yml")
|
176
|
-
}
|
177
|
-
|
178
|
-
//////////////////////////////
|
179
|
-
// Methods for Docker
|
180
|
-
//////////////////////////////
|
181
|
-
|
182
|
-
private fun stopContainer(containerId: String) {
|
183
|
-
if (isRunning(containerId)) {
|
184
|
-
dockerClient.stopContainerCmd(containerId).exec()
|
185
|
-
}
|
186
|
-
}
|
187
|
-
|
188
|
-
private fun startContainer(containerId: String) {
|
189
|
-
if (!isRunning(containerId)) {
|
190
|
-
dockerClient.startContainerCmd(containerId).exec()
|
191
|
-
}
|
192
|
-
}
|
193
|
-
|
194
|
-
private fun isRunning(containerId: String): Boolean {
|
195
|
-
return dockerClient.listContainersCmd().exec().any { container ->
|
196
|
-
container.names.any { name ->
|
197
|
-
name.contains(containerId)
|
198
|
-
}
|
199
|
-
}
|
200
|
-
}
|
201
|
-
}
|