embulk-output-azure_blob_storage 0.1.0 → 0.1.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/.travis.yml +5 -0
- data/README.md +53 -0
- data/build.gradle +5 -2
- data/classpath/embulk-output-azure_blob_storage-0.1.1.jar +0 -0
- data/embulk-output-azure_blob_storage.gemspec +18 -0
- data/src/main/java/org/embulk/output/azure_blob_storage/AzureBlobStorageFileOutputPlugin.java +97 -75
- data/src/test/java/org/embulk/output/azure_blob_storage/TestAzureBlobStorageFileOutputPlugin.java +374 -0
- data/src/test/resources/sample_01.csv +5 -0
- data/src/test/resources/sample_02.csv +5 -0
- metadata +7 -3
- data/classpath/embulk-output-azure_blob_storage-0.1.0.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: 780301179e35e336690948c80eb1f0be40969fda
|
4
|
+
data.tar.gz: 0820d6fe2606320543583146126c0df179ff8dcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77f5049f878c6a82809226ebe49873ec31e6a16d475dd7991b9a729d3348be98885052bfa83c5bf97777bac053014532dec2c028e9add5b2f21d3729abe919f0
|
7
|
+
data.tar.gz: 75dee87d6947f777e5a8e0211bbbb8dc9922bd6cbb7784a2853bb4a9f907f66ce05e572bb431e2d34a7ffe23c096b24b04a30c40d0075007b3f301f5e3b5f8b5
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -50,3 +50,56 @@ out:
|
|
50
50
|
```
|
51
51
|
$ ./gradlew gem # -t to watch change of files and rebuild continuously
|
52
52
|
```
|
53
|
+
|
54
|
+
|
55
|
+
## Test
|
56
|
+
|
57
|
+
```
|
58
|
+
$ ./gradlew test # -t to watch change of files and rebuild continuously
|
59
|
+
```
|
60
|
+
|
61
|
+
To run unit tests, we need to configure the following environment variables.
|
62
|
+
|
63
|
+
Additionally, following files will be needed to upload to existing GCS bucket.
|
64
|
+
* [sample_01.csv](./src/test/resources/sample_01.csv)
|
65
|
+
* [sample_02.csv](./src/test/resources/sample_02.csv)
|
66
|
+
|
67
|
+
When environment variables are not set, skip some test cases.
|
68
|
+
|
69
|
+
```
|
70
|
+
AZURE_ACCOUNT_NAME
|
71
|
+
AZURE_ACCOUNT_KEY
|
72
|
+
AZURE_CONTAINER
|
73
|
+
AZURE_CONTAINER_DIRECTORY (optional, if needed)
|
74
|
+
```
|
75
|
+
|
76
|
+
If you're using Mac OS X El Capitan and GUI Applications(IDE), like as follows.
|
77
|
+
```
|
78
|
+
$ vi ~/Library/LaunchAgents/environment.plist
|
79
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
80
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
81
|
+
<plist version="1.0">
|
82
|
+
<dict>
|
83
|
+
<key>Label</key>
|
84
|
+
<string>my.startup</string>
|
85
|
+
<key>ProgramArguments</key>
|
86
|
+
<array>
|
87
|
+
<string>sh</string>
|
88
|
+
<string>-c</string>
|
89
|
+
<string>
|
90
|
+
launchctl setenv AZURE_ACCOUNT_NAME my-account-name
|
91
|
+
launchctl setenv AZURE_ACCOUNT_KEY my-account-key
|
92
|
+
launchctl setenv AZURE_CONTAINER my-container
|
93
|
+
launchctl setenv AZURE_CONTAINER_DIRECTORY unittests
|
94
|
+
</string>
|
95
|
+
</array>
|
96
|
+
<key>RunAtLoad</key>
|
97
|
+
<true/>
|
98
|
+
</dict>
|
99
|
+
</plist>
|
100
|
+
|
101
|
+
$ launchctl load ~/Library/LaunchAgents/environment.plist
|
102
|
+
$ launchctl getenv AZURE_ACCOUNT_NAME //try to get value.
|
103
|
+
|
104
|
+
Then start your applications.
|
105
|
+
```
|
data/build.gradle
CHANGED
@@ -2,6 +2,7 @@ plugins {
|
|
2
2
|
id "com.jfrog.bintray" version "1.1"
|
3
3
|
id "com.github.jruby-gradle.base" version "0.1.5"
|
4
4
|
id "java"
|
5
|
+
id "jacoco"
|
5
6
|
}
|
6
7
|
import com.github.jrubygradle.JRubyExec
|
7
8
|
repositories {
|
@@ -15,7 +16,7 @@ configurations {
|
|
15
16
|
sourceCompatibility = 1.7
|
16
17
|
targetCompatibility = 1.7
|
17
18
|
|
18
|
-
version = "0.1.
|
19
|
+
version = "0.1.1"
|
19
20
|
|
20
21
|
dependencies {
|
21
22
|
compile "org.embulk:embulk-core:0.7.5"
|
@@ -23,7 +24,9 @@ dependencies {
|
|
23
24
|
|
24
25
|
compile "com.microsoft.azure:azure-storage:4.0.0"
|
25
26
|
|
26
|
-
testCompile "junit:junit:4
|
27
|
+
testCompile "junit:junit:4.12"
|
28
|
+
testCompile "org.embulk:embulk-core:0.7.5:tests"
|
29
|
+
testCompile "org.embulk:embulk-standards:0.7.5"
|
27
30
|
}
|
28
31
|
|
29
32
|
task classpath(type: Copy, dependsOn: ["jar"]) {
|
Binary file
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |spec|
|
3
|
+
spec.name = "embulk-output-azure_blob_storage"
|
4
|
+
spec.version = "0.1.1"
|
5
|
+
spec.authors = ["Satoshi Akama"]
|
6
|
+
spec.summary = %[Microsoft Azure blob Storage file output plugin for Embulk]
|
7
|
+
spec.description = %[Stores files on Microsoft Azure blob Storage.]
|
8
|
+
spec.email = ["satoshiakama@gmail.com"]
|
9
|
+
spec.licenses = ["Apache-2.0"]
|
10
|
+
spec.homepage = "https://github.com/sakama/embulk-output-azure_blob_storage"
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split("\n") + Dir["classpath/*.jar"]
|
13
|
+
spec.test_files = spec.files.grep(%r"^(test|spec)/")
|
14
|
+
spec.require_paths = ["lib"]
|
15
|
+
|
16
|
+
spec.add_development_dependency 'bundler', ['~> 1.0']
|
17
|
+
spec.add_development_dependency 'rake', ['>= 10.0']
|
18
|
+
end
|
data/src/main/java/org/embulk/output/azure_blob_storage/AzureBlobStorageFileOutputPlugin.java
CHANGED
@@ -53,24 +53,22 @@ public class AzureBlobStorageFileOutputPlugin
|
|
53
53
|
}
|
54
54
|
|
55
55
|
private static final Logger log = Exec.getLogger(AzureBlobStorageFileOutputPlugin.class);
|
56
|
-
private static CloudBlobClient blobClient;
|
57
|
-
private static CloudBlobContainer container;
|
58
56
|
|
59
57
|
@Override
|
60
58
|
public ConfigDiff transaction(ConfigSource config, int taskCount,
|
61
59
|
FileOutputPlugin.Control control)
|
62
60
|
{
|
63
61
|
PluginTask task = config.loadConfig(PluginTask.class);
|
64
|
-
blobClient = newAzureClient(task.getAccountName(), task.getAccountKey());
|
65
|
-
String containerName = task.getContainer();
|
66
62
|
|
67
63
|
try {
|
68
|
-
|
64
|
+
CloudBlobClient blobClient = newAzureClient(task.getAccountName(), task.getAccountKey());
|
65
|
+
String containerName = task.getContainer();
|
66
|
+
CloudBlobContainer container = blobClient.getContainerReference(containerName);
|
69
67
|
if (!container.exists()) {
|
70
68
|
log.info(String.format("container [%s] is not exists and created.", containerName));
|
71
69
|
container.createIfNotExists();
|
72
70
|
}
|
73
|
-
} catch (StorageException | URISyntaxException ex) {
|
71
|
+
} catch (StorageException | URISyntaxException | ConfigException ex) {
|
74
72
|
Throwables.propagate(ex);
|
75
73
|
}
|
76
74
|
|
@@ -106,94 +104,118 @@ public class AzureBlobStorageFileOutputPlugin
|
|
106
104
|
}
|
107
105
|
|
108
106
|
@Override
|
109
|
-
public TransactionalFileOutput open(TaskSource taskSource, final int taskIndex)
|
110
|
-
{
|
107
|
+
public TransactionalFileOutput open(TaskSource taskSource, final int taskIndex) {
|
111
108
|
final PluginTask task = taskSource.loadTask(PluginTask.class);
|
109
|
+
return new AzureFileOutput(task, taskIndex);
|
110
|
+
}
|
112
111
|
|
113
|
-
|
114
|
-
|
115
|
-
final String
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
112
|
+
public static class AzureFileOutput implements TransactionalFileOutput
|
113
|
+
{
|
114
|
+
private final String pathPrefix;
|
115
|
+
private final String sequenceFormat;
|
116
|
+
private final String pathSuffix;
|
117
|
+
private final CloudBlobClient client;
|
118
|
+
private CloudBlobContainer container = null;
|
119
|
+
private BufferedOutputStream output = null;
|
120
|
+
private int fileIndex;
|
121
|
+
private File file;
|
122
|
+
private String filePath;
|
123
|
+
private int taskIndex;
|
124
|
+
|
125
|
+
public AzureFileOutput(PluginTask task, int taskIndex)
|
126
|
+
{
|
127
|
+
this.taskIndex = taskIndex;
|
128
|
+
this.pathPrefix = task.getPathPrefix();
|
129
|
+
this.sequenceFormat = task.getSequenceFormat();
|
130
|
+
this.pathSuffix = task.getFileNameExtension();
|
131
|
+
this.client = newAzureClient(task.getAccountName(), task.getAccountKey());
|
132
|
+
try {
|
133
|
+
this.container = client.getContainerReference(task.getContainer());
|
134
|
+
} catch (URISyntaxException | StorageException ex) {
|
135
|
+
Throwables.propagate(ex);
|
136
|
+
}
|
137
|
+
}
|
122
138
|
|
123
|
-
|
124
|
-
|
125
|
-
|
139
|
+
@Override
|
140
|
+
public void nextFile()
|
141
|
+
{
|
142
|
+
closeFile();
|
126
143
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
}
|
132
|
-
filePath = pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + suffix;
|
133
|
-
file = new File(filePath);
|
134
|
-
|
135
|
-
String parentPath = file.getParent();
|
136
|
-
File dir = new File(parentPath);
|
137
|
-
if (!dir.exists()) {
|
138
|
-
dir.mkdir();
|
139
|
-
}
|
140
|
-
log.info(String.format("Writing local file [%s]", filePath));
|
141
|
-
output = new BufferedOutputStream(new FileOutputStream(filePath));
|
142
|
-
} catch (FileNotFoundException ex) {
|
143
|
-
throw Throwables.propagate(ex);
|
144
|
+
try {
|
145
|
+
String suffix = pathSuffix;
|
146
|
+
if (!suffix.startsWith(".")) {
|
147
|
+
suffix = "." + suffix;
|
144
148
|
}
|
145
|
-
|
149
|
+
filePath = pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + suffix;
|
150
|
+
file = new File(filePath);
|
146
151
|
|
147
|
-
|
148
|
-
|
149
|
-
if (
|
150
|
-
|
151
|
-
output.close();
|
152
|
-
} catch (IOException ex) {
|
153
|
-
throw Throwables.propagate(ex);
|
154
|
-
}
|
152
|
+
String parentPath = file.getParent();
|
153
|
+
File dir = new File(parentPath);
|
154
|
+
if (!dir.exists()) {
|
155
|
+
dir.mkdir();
|
155
156
|
}
|
157
|
+
log.info(String.format("Writing local file [%s]", filePath));
|
158
|
+
output = new BufferedOutputStream(new FileOutputStream(filePath));
|
159
|
+
} catch (FileNotFoundException ex) {
|
160
|
+
throw Throwables.propagate(ex);
|
156
161
|
}
|
162
|
+
}
|
157
163
|
|
158
|
-
|
159
|
-
|
164
|
+
private void closeFile()
|
165
|
+
{
|
166
|
+
if (output != null) {
|
160
167
|
try {
|
161
|
-
output.
|
168
|
+
output.close();
|
169
|
+
fileIndex++;
|
162
170
|
} catch (IOException ex) {
|
163
171
|
throw Throwables.propagate(ex);
|
164
|
-
} finally {
|
165
|
-
buffer.release();
|
166
172
|
}
|
167
173
|
}
|
174
|
+
}
|
168
175
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
file.delete();
|
179
|
-
log.info(String.format("Delete completed local file [%s]", filePath));
|
180
|
-
} catch (StorageException | URISyntaxException | IOException ex) {
|
181
|
-
Throwables.propagate(ex);
|
182
|
-
}
|
183
|
-
}
|
176
|
+
@Override
|
177
|
+
public void add(Buffer buffer)
|
178
|
+
{
|
179
|
+
try {
|
180
|
+
output.write(buffer.array(), buffer.offset(), buffer.limit());
|
181
|
+
} catch (IOException ex) {
|
182
|
+
throw Throwables.propagate(ex);
|
183
|
+
} finally {
|
184
|
+
buffer.release();
|
184
185
|
}
|
186
|
+
}
|
185
187
|
|
186
|
-
|
187
|
-
|
188
|
-
|
188
|
+
@Override
|
189
|
+
public void finish()
|
190
|
+
{
|
191
|
+
closeFile();
|
192
|
+
if (filePath != null) {
|
193
|
+
try {
|
194
|
+
CloudBlockBlob blob = container.getBlockBlobReference(filePath);
|
195
|
+
log.info(String.format("Upload start [%s]", filePath));
|
196
|
+
blob.upload(new FileInputStream(file), file.length());
|
197
|
+
log.info(String.format("Upload completed [%s]", filePath));
|
198
|
+
file.delete();
|
199
|
+
log.info(String.format("Delete completed local file [%s]", filePath));
|
200
|
+
} catch (StorageException | URISyntaxException | IOException ex) {
|
201
|
+
Throwables.propagate(ex);
|
202
|
+
}
|
189
203
|
}
|
204
|
+
}
|
190
205
|
|
191
|
-
|
206
|
+
@Override
|
207
|
+
public void close()
|
208
|
+
{
|
209
|
+
closeFile();
|
210
|
+
}
|
192
211
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
212
|
+
@Override
|
213
|
+
public void abort() {}
|
214
|
+
|
215
|
+
@Override
|
216
|
+
public TaskReport commit()
|
217
|
+
{
|
218
|
+
return Exec.newTaskReport();
|
219
|
+
}
|
198
220
|
}
|
199
221
|
}
|
data/src/test/java/org/embulk/output/azure_blob_storage/TestAzureBlobStorageFileOutputPlugin.java
CHANGED
@@ -1,5 +1,379 @@
|
|
1
1
|
package org.embulk.output.azure_blob_storage;
|
2
2
|
|
3
|
+
import java.io.BufferedReader;
|
4
|
+
import java.io.ByteArrayOutputStream;
|
5
|
+
import java.io.FileInputStream;
|
6
|
+
import java.io.IOException;
|
7
|
+
import java.io.InputStream;
|
8
|
+
import java.io.InputStreamReader;
|
9
|
+
import java.lang.reflect.Method;
|
10
|
+
import java.security.GeneralSecurityException;
|
11
|
+
import java.util.Arrays;
|
12
|
+
import java.util.List;
|
13
|
+
|
14
|
+
import com.google.common.collect.ImmutableList;
|
15
|
+
import com.google.common.collect.ImmutableMap;
|
16
|
+
import com.google.common.collect.Lists;
|
17
|
+
|
18
|
+
import org.embulk.EmbulkTestRuntime;
|
19
|
+
import org.embulk.config.ConfigDiff;
|
20
|
+
import org.embulk.config.ConfigSource;
|
21
|
+
import org.embulk.config.TaskReport;
|
22
|
+
import org.embulk.config.TaskSource;
|
23
|
+
import org.embulk.spi.Buffer;
|
24
|
+
import org.embulk.spi.Exec;
|
25
|
+
import org.embulk.spi.Schema;
|
26
|
+
import org.embulk.spi.OutputPlugin;
|
27
|
+
import org.embulk.spi.FileOutputRunner;
|
28
|
+
import org.embulk.spi.FileOutputPlugin;
|
29
|
+
import org.embulk.spi.TransactionalFileOutput;
|
30
|
+
import org.embulk.standards.CsvParserPlugin;
|
31
|
+
|
32
|
+
import org.junit.BeforeClass;
|
33
|
+
import org.junit.Before;
|
34
|
+
import org.junit.Rule;
|
35
|
+
import org.junit.Test;
|
36
|
+
import static org.junit.Assert.assertEquals;
|
37
|
+
import static org.junit.Assume.assumeNotNull;
|
38
|
+
|
39
|
+
import com.microsoft.azure.storage.blob.CloudBlob;
|
40
|
+
import com.microsoft.azure.storage.blob.CloudBlobClient;
|
41
|
+
import com.microsoft.azure.storage.blob.CloudBlobContainer;
|
42
|
+
import org.embulk.output.azure_blob_storage.AzureBlobStorageFileOutputPlugin.PluginTask;
|
43
|
+
|
3
44
|
public class TestAzureBlobStorageFileOutputPlugin
|
4
45
|
{
|
46
|
+
private static String AZURE_ACCOUNT_NAME;
|
47
|
+
private static String AZURE_ACCOUNT_KEY;
|
48
|
+
private static String AZURE_CONTAINER;
|
49
|
+
private static String AZURE_CONTAINER_DIRECTORY;
|
50
|
+
private static String AZURE_PATH_PREFIX;
|
51
|
+
private static String LOCAL_PATH_PREFIX;
|
52
|
+
private FileOutputRunner runner;
|
53
|
+
|
54
|
+
/*
|
55
|
+
* This test case requires environment variables
|
56
|
+
* AZURE_ACCOUNT_NAME
|
57
|
+
* AZURE_ACCOUNT_KEY
|
58
|
+
* AZURE_CONTAINER
|
59
|
+
* AZURE_CONTAINER_DIRECTORY
|
60
|
+
*/
|
61
|
+
@BeforeClass
|
62
|
+
public static void initializeConstant()
|
63
|
+
{
|
64
|
+
AZURE_ACCOUNT_NAME = System.getenv("AZURE_ACCOUNT_NAME");
|
65
|
+
AZURE_ACCOUNT_KEY = System.getenv("AZURE_ACCOUNT_KEY");
|
66
|
+
AZURE_CONTAINER = System.getenv("AZURE_CONTAINER");
|
67
|
+
// skip test cases, if environment variables are not set.
|
68
|
+
assumeNotNull(AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY, AZURE_CONTAINER);
|
69
|
+
|
70
|
+
AZURE_CONTAINER_DIRECTORY = System.getenv("AZURE_CONTAINER_DIRECTORY") != null ? getDirectory(System.getenv("AZURE_CONTAINER_DIRECTORY")) : getDirectory("");
|
71
|
+
AZURE_PATH_PREFIX = AZURE_CONTAINER_DIRECTORY + "sample_";
|
72
|
+
LOCAL_PATH_PREFIX = AzureBlobStorageFileOutputPlugin.class.getClassLoader().getResource("sample_01.csv").getPath();
|
73
|
+
}
|
74
|
+
|
75
|
+
@Rule
|
76
|
+
public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
|
77
|
+
private AzureBlobStorageFileOutputPlugin plugin;
|
78
|
+
|
79
|
+
@Before
|
80
|
+
public void createResources() throws GeneralSecurityException, NoSuchMethodException, IOException
|
81
|
+
{
|
82
|
+
plugin = new AzureBlobStorageFileOutputPlugin();
|
83
|
+
runner = new FileOutputRunner(runtime.getInstance(AzureBlobStorageFileOutputPlugin.class));
|
84
|
+
}
|
85
|
+
|
86
|
+
@Test
|
87
|
+
public void checkDefaultValues()
|
88
|
+
{
|
89
|
+
ConfigSource config = Exec.newConfigSource()
|
90
|
+
.set("in", inputConfig())
|
91
|
+
.set("parser", parserConfig(schemaConfig()))
|
92
|
+
.set("type", "azure_blob_storage")
|
93
|
+
.set("account_name", AZURE_ACCOUNT_NAME)
|
94
|
+
.set("account_key", AZURE_ACCOUNT_KEY)
|
95
|
+
.set("container", AZURE_CONTAINER)
|
96
|
+
.set("path_prefix", "my-prefix")
|
97
|
+
.set("file_ext", ".csv")
|
98
|
+
.set("formatter", formatterConfig());
|
99
|
+
|
100
|
+
PluginTask task = config.loadConfig(PluginTask.class);
|
101
|
+
assertEquals(AZURE_ACCOUNT_NAME, task.getAccountName().toString());
|
102
|
+
}
|
103
|
+
|
104
|
+
@Test
|
105
|
+
public void testTransaction()
|
106
|
+
{
|
107
|
+
ConfigSource config = Exec.newConfigSource()
|
108
|
+
.set("in", inputConfig())
|
109
|
+
.set("parser", parserConfig(schemaConfig()))
|
110
|
+
.set("type", "azure_blob_storage")
|
111
|
+
.set("account_name", AZURE_ACCOUNT_NAME)
|
112
|
+
.set("account_key", AZURE_ACCOUNT_KEY)
|
113
|
+
.set("container", AZURE_CONTAINER)
|
114
|
+
.set("path_prefix", "my-prefix")
|
115
|
+
.set("file_ext", ".csv")
|
116
|
+
.set("formatter", formatterConfig());
|
117
|
+
|
118
|
+
Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
119
|
+
|
120
|
+
runner.transaction(config, schema, 0, new Control());
|
121
|
+
}
|
122
|
+
|
123
|
+
@Test
|
124
|
+
public void testTransactionCreateNonexistsContainer() throws Exception
|
125
|
+
{
|
126
|
+
String container = "non-exists-container";
|
127
|
+
deleteContainerIfExists(container);
|
128
|
+
|
129
|
+
assertEquals(false, isExistsContainer(container));
|
130
|
+
|
131
|
+
ConfigSource config = Exec.newConfigSource()
|
132
|
+
.set("in", inputConfig())
|
133
|
+
.set("parser", parserConfig(schemaConfig()))
|
134
|
+
.set("type", "azure_blob_storage")
|
135
|
+
.set("account_name", AZURE_ACCOUNT_NAME)
|
136
|
+
.set("account_key", AZURE_ACCOUNT_KEY)
|
137
|
+
.set("container", container)
|
138
|
+
.set("path_prefix", "my-prefix")
|
139
|
+
.set("file_ext", ".csv")
|
140
|
+
.set("formatter", formatterConfig());
|
141
|
+
|
142
|
+
Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
143
|
+
|
144
|
+
runner.transaction(config, schema, 0, new Control());
|
145
|
+
|
146
|
+
assertEquals(true, isExistsContainer(container));
|
147
|
+
deleteContainerIfExists(container);
|
148
|
+
}
|
149
|
+
|
150
|
+
@Test
|
151
|
+
public void testResume()
|
152
|
+
{
|
153
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
154
|
+
ConfigDiff configDiff = plugin.resume(task.dump(), 0, new FileOutputPlugin.Control()
|
155
|
+
{
|
156
|
+
@Override
|
157
|
+
public List<TaskReport> run(TaskSource taskSource)
|
158
|
+
{
|
159
|
+
return Lists.newArrayList(Exec.newTaskReport());
|
160
|
+
}
|
161
|
+
});
|
162
|
+
//assertEquals("in/aa/a", configDiff.get(String.class, "last_path"));
|
163
|
+
}
|
164
|
+
|
165
|
+
@Test
|
166
|
+
public void testCleanup()
|
167
|
+
{
|
168
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
169
|
+
plugin.cleanup(task.dump(), 0, Lists.<TaskReport>newArrayList()); // no errors happens
|
170
|
+
}
|
171
|
+
|
172
|
+
@Test(expected = RuntimeException.class)
|
173
|
+
public void testCreateAzureClientThrowsConfigException()
|
174
|
+
{
|
175
|
+
ConfigSource config = Exec.newConfigSource()
|
176
|
+
.set("in", inputConfig())
|
177
|
+
.set("parser", parserConfig(schemaConfig()))
|
178
|
+
.set("type", "azure_blob_storage")
|
179
|
+
.set("account_name", "invalid-account-name")
|
180
|
+
.set("account_key", AZURE_ACCOUNT_KEY)
|
181
|
+
.set("container", AZURE_CONTAINER)
|
182
|
+
.set("path_prefix", "my-prefix")
|
183
|
+
.set("file_ext", ".csv")
|
184
|
+
.set("formatter", formatterConfig());
|
185
|
+
|
186
|
+
Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
187
|
+
|
188
|
+
runner.transaction(config, schema, 0, new Control());
|
189
|
+
}
|
190
|
+
|
191
|
+
@Test
|
192
|
+
public void testAzureFileOutputByOpen() throws Exception
|
193
|
+
{
|
194
|
+
ConfigSource configSource = config();
|
195
|
+
PluginTask task = configSource.loadConfig(PluginTask.class);
|
196
|
+
Schema schema = configSource.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
197
|
+
runner.transaction(configSource, schema, 0, new Control());
|
198
|
+
|
199
|
+
TransactionalFileOutput output = plugin.open(task.dump(), 0);
|
200
|
+
|
201
|
+
output.nextFile();
|
202
|
+
|
203
|
+
FileInputStream is = new FileInputStream(LOCAL_PATH_PREFIX);
|
204
|
+
byte[] bytes = convertInputStreamToByte(is);
|
205
|
+
Buffer buffer = Buffer.wrap(bytes);
|
206
|
+
output.add(buffer);
|
207
|
+
|
208
|
+
output.finish();
|
209
|
+
output.commit();
|
210
|
+
|
211
|
+
String remotePath = AZURE_PATH_PREFIX + String.format(task.getSequenceFormat(), 0, 0) + task.getFileNameExtension();
|
212
|
+
assertRecords(remotePath);
|
213
|
+
}
|
214
|
+
|
215
|
+
public ConfigSource config()
|
216
|
+
{
|
217
|
+
return Exec.newConfigSource()
|
218
|
+
.set("in", inputConfig())
|
219
|
+
.set("parser", parserConfig(schemaConfig()))
|
220
|
+
.set("type", "azure_blob_storage")
|
221
|
+
.set("account_name", AZURE_ACCOUNT_NAME)
|
222
|
+
.set("account_key", AZURE_ACCOUNT_KEY)
|
223
|
+
.set("container", AZURE_CONTAINER)
|
224
|
+
.set("path_prefix", AZURE_PATH_PREFIX)
|
225
|
+
.set("last_path", "")
|
226
|
+
.set("file_ext", ".csv")
|
227
|
+
.set("formatter", formatterConfig());
|
228
|
+
}
|
229
|
+
|
230
|
+
private class Control
|
231
|
+
implements OutputPlugin.Control
|
232
|
+
{
|
233
|
+
@Override
|
234
|
+
public List<TaskReport> run(TaskSource taskSource)
|
235
|
+
{
|
236
|
+
return Lists.newArrayList(Exec.newTaskReport());
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
private ImmutableMap<String, Object> inputConfig()
|
241
|
+
{
|
242
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
243
|
+
builder.put("type", "file");
|
244
|
+
builder.put("path_prefix", LOCAL_PATH_PREFIX);
|
245
|
+
builder.put("last_path", "");
|
246
|
+
return builder.build();
|
247
|
+
}
|
248
|
+
|
249
|
+
private ImmutableMap<String, Object> parserConfig(ImmutableList<Object> schemaConfig)
|
250
|
+
{
|
251
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
252
|
+
builder.put("type", "csv");
|
253
|
+
builder.put("newline", "CRLF");
|
254
|
+
builder.put("delimiter", ",");
|
255
|
+
builder.put("quote", "\"");
|
256
|
+
builder.put("escape", "\"");
|
257
|
+
builder.put("trim_if_not_quoted", false);
|
258
|
+
builder.put("skip_header_lines", 1);
|
259
|
+
builder.put("allow_extra_columns", false);
|
260
|
+
builder.put("allow_optional_columns", false);
|
261
|
+
builder.put("columns", schemaConfig);
|
262
|
+
return builder.build();
|
263
|
+
}
|
264
|
+
|
265
|
+
private ImmutableList<Object> schemaConfig()
|
266
|
+
{
|
267
|
+
ImmutableList.Builder<Object> builder = new ImmutableList.Builder<>();
|
268
|
+
builder.add(ImmutableMap.of("name", "id", "type", "long"));
|
269
|
+
builder.add(ImmutableMap.of("name", "account", "type", "long"));
|
270
|
+
builder.add(ImmutableMap.of("name", "time", "type", "timestamp", "format", "%Y-%m-%d %H:%M:%S"));
|
271
|
+
builder.add(ImmutableMap.of("name", "purchase", "type", "timestamp", "format", "%Y%m%d"));
|
272
|
+
builder.add(ImmutableMap.of("name", "comment", "type", "string"));
|
273
|
+
return builder.build();
|
274
|
+
}
|
275
|
+
|
276
|
+
private ImmutableMap<String, Object> formatterConfig()
|
277
|
+
{
|
278
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
279
|
+
builder.put("type", "csv");
|
280
|
+
builder.put("header_line", "false");
|
281
|
+
builder.put("timezone", "Asia/Tokyo");
|
282
|
+
return builder.build();
|
283
|
+
}
|
284
|
+
|
285
|
+
private void assertRecords(String azurePath) throws Exception
|
286
|
+
{
|
287
|
+
ImmutableList<List<String>> records = getFileContentsFromAzure(azurePath);
|
288
|
+
assertEquals(5, records.size());
|
289
|
+
{
|
290
|
+
List<String> record = records.get(1);
|
291
|
+
assertEquals("1", record.get(0));
|
292
|
+
assertEquals("32864", record.get(1));
|
293
|
+
assertEquals("2015-01-27 19:23:49", record.get(2));
|
294
|
+
assertEquals("20150127", record.get(3));
|
295
|
+
assertEquals("embulk", record.get(4));
|
296
|
+
}
|
297
|
+
|
298
|
+
{
|
299
|
+
List<String> record = records.get(2);
|
300
|
+
assertEquals("2", record.get(0));
|
301
|
+
assertEquals("14824", record.get(1));
|
302
|
+
assertEquals("2015-01-27 19:01:23", record.get(2));
|
303
|
+
assertEquals("20150127", record.get(3));
|
304
|
+
assertEquals("embulk jruby", record.get(4));
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
private ImmutableList<List<String>> getFileContentsFromAzure(String path) throws Exception
|
309
|
+
{
|
310
|
+
Method method = AzureBlobStorageFileOutputPlugin.class.getDeclaredMethod("newAzureClient", String.class, String.class);
|
311
|
+
method.setAccessible(true);
|
312
|
+
CloudBlobClient client = (CloudBlobClient) method.invoke(plugin, AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
|
313
|
+
CloudBlobContainer container = client.getContainerReference(AZURE_CONTAINER);
|
314
|
+
CloudBlob blob = container.getBlockBlobReference(path);
|
315
|
+
|
316
|
+
ImmutableList.Builder<List<String>> builder = new ImmutableList.Builder<>();
|
317
|
+
|
318
|
+
InputStream is = blob.openInputStream();
|
319
|
+
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
320
|
+
String line;
|
321
|
+
while ((line = reader.readLine()) != null) {
|
322
|
+
List<String> records = Arrays.asList(line.split(",", 0));
|
323
|
+
|
324
|
+
builder.add(records);
|
325
|
+
}
|
326
|
+
return builder.build();
|
327
|
+
}
|
328
|
+
|
329
|
+
private boolean isExistsContainer(String containerName) throws Exception
|
330
|
+
{
|
331
|
+
Method method = AzureBlobStorageFileOutputPlugin.class.getDeclaredMethod("newAzureClient", String.class, String.class);
|
332
|
+
method.setAccessible(true);
|
333
|
+
CloudBlobClient client = (CloudBlobClient) method.invoke(plugin, AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
|
334
|
+
CloudBlobContainer container = client.getContainerReference(containerName);
|
335
|
+
if (container.exists()) {
|
336
|
+
return true;
|
337
|
+
}
|
338
|
+
|
339
|
+
return false;
|
340
|
+
}
|
341
|
+
|
342
|
+
private void deleteContainerIfExists(String containerName) throws Exception
|
343
|
+
{
|
344
|
+
Method method = AzureBlobStorageFileOutputPlugin.class.getDeclaredMethod("newAzureClient", String.class, String.class);
|
345
|
+
method.setAccessible(true);
|
346
|
+
CloudBlobClient client = (CloudBlobClient) method.invoke(plugin, AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
|
347
|
+
CloudBlobContainer container = client.getContainerReference(containerName);
|
348
|
+
if (container.exists()) {
|
349
|
+
container.delete();
|
350
|
+
// container could not create same name after deletion.
|
351
|
+
Thread.sleep(30000);
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
private static String getDirectory(String dir)
|
356
|
+
{
|
357
|
+
if (dir != null && !dir.endsWith("/")) {
|
358
|
+
dir = dir + "/";
|
359
|
+
}
|
360
|
+
if (dir.startsWith("/")) {
|
361
|
+
dir = dir.replaceFirst("/", "");
|
362
|
+
}
|
363
|
+
return dir;
|
364
|
+
}
|
365
|
+
|
366
|
+
private byte[] convertInputStreamToByte(InputStream is) throws IOException
|
367
|
+
{
|
368
|
+
ByteArrayOutputStream bo = new ByteArrayOutputStream();
|
369
|
+
byte [] buffer = new byte[1024];
|
370
|
+
while(true) {
|
371
|
+
int len = is.read(buffer);
|
372
|
+
if(len < 0) {
|
373
|
+
break;
|
374
|
+
}
|
375
|
+
bo.write(buffer, 0, len);
|
376
|
+
}
|
377
|
+
return bo.toByteArray();
|
378
|
+
}
|
5
379
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-output-azure_blob_storage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Satoshi Akama
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -46,8 +46,10 @@ extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
48
|
- .gitignore
|
49
|
+
- .travis.yml
|
49
50
|
- README.md
|
50
51
|
- build.gradle
|
52
|
+
- embulk-output-azure_blob_storage.gemspec
|
51
53
|
- gradle/wrapper/gradle-wrapper.jar
|
52
54
|
- gradle/wrapper/gradle-wrapper.properties
|
53
55
|
- gradlew
|
@@ -55,9 +57,11 @@ files:
|
|
55
57
|
- lib/embulk/output/azure_blob_storage.rb
|
56
58
|
- src/main/java/org/embulk/output/azure_blob_storage/AzureBlobStorageFileOutputPlugin.java
|
57
59
|
- src/test/java/org/embulk/output/azure_blob_storage/TestAzureBlobStorageFileOutputPlugin.java
|
60
|
+
- src/test/resources/sample_01.csv
|
61
|
+
- src/test/resources/sample_02.csv
|
58
62
|
- classpath/azure-storage-4.0.0.jar
|
59
63
|
- classpath/commons-lang3-3.4.jar
|
60
|
-
- classpath/embulk-output-azure_blob_storage-0.1.
|
64
|
+
- classpath/embulk-output-azure_blob_storage-0.1.1.jar
|
61
65
|
- classpath/jackson-core-2.6.0.jar
|
62
66
|
homepage: https://github.com/sakama/embulk-output-azure_blob_storage
|
63
67
|
licenses:
|
Binary file
|