embulk-output-gcs 0.3.0 → 0.4.0
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/build.gradle +23 -5
- data/config/checkstyle/checkstyle.xml +130 -0
- data/config/checkstyle/default.xml +110 -0
- data/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/gradle/wrapper/gradle-wrapper.properties +2 -2
- data/src/main/java/org/embulk/output/GcsAuthentication.java +184 -96
- data/src/main/java/org/embulk/output/GcsOutputPlugin.java +408 -278
- data/src/test/java/org/embulk/output/TestGcsAuthentication.java +161 -163
- data/src/test/java/org/embulk/output/TestGcsOutputPlugin.java +399 -399
- metadata +5 -3
@@ -1,30 +1,40 @@
|
|
1
1
|
package org.embulk.output;
|
2
2
|
|
3
3
|
import com.google.api.client.http.InputStreamContent;
|
4
|
+
import com.google.api.client.repackaged.org.apache.commons.codec.binary.Base64;
|
4
5
|
import com.google.api.services.storage.Storage;
|
5
6
|
import com.google.api.services.storage.model.StorageObject;
|
6
7
|
import com.google.common.base.Function;
|
7
8
|
import com.google.common.base.Optional;
|
8
9
|
import com.google.common.base.Throwables;
|
9
|
-
import org.embulk.config.TaskReport;
|
10
10
|
import org.embulk.config.Config;
|
11
11
|
import org.embulk.config.ConfigDefault;
|
12
12
|
import org.embulk.config.ConfigDiff;
|
13
|
-
import org.embulk.config.ConfigSource;
|
14
13
|
import org.embulk.config.ConfigException;
|
14
|
+
import org.embulk.config.ConfigSource;
|
15
15
|
import org.embulk.config.Task;
|
16
|
+
import org.embulk.config.TaskReport;
|
16
17
|
import org.embulk.config.TaskSource;
|
17
18
|
import org.embulk.spi.Buffer;
|
18
19
|
import org.embulk.spi.Exec;
|
19
20
|
import org.embulk.spi.FileOutputPlugin;
|
20
21
|
import org.embulk.spi.TransactionalFileOutput;
|
21
22
|
import org.embulk.spi.unit.LocalFile;
|
23
|
+
import org.embulk.spi.util.RetryExecutor.RetryGiveupException;
|
24
|
+
import org.embulk.spi.util.RetryExecutor.Retryable;
|
22
25
|
import org.slf4j.Logger;
|
26
|
+
import static org.embulk.spi.util.RetryExecutor.retryExecutor;
|
23
27
|
|
28
|
+
import java.io.BufferedInputStream;
|
29
|
+
import java.io.BufferedOutputStream;
|
30
|
+
import java.io.File;
|
31
|
+
import java.io.FileInputStream;
|
32
|
+
import java.io.FileOutputStream;
|
24
33
|
import java.io.IOException;
|
25
|
-
import java.io.
|
26
|
-
import java.io.PipedOutputStream;
|
34
|
+
import java.io.InterruptedIOException;
|
27
35
|
import java.security.GeneralSecurityException;
|
36
|
+
import java.security.MessageDigest;
|
37
|
+
import java.security.NoSuchAlgorithmException;
|
28
38
|
import java.util.ArrayList;
|
29
39
|
import java.util.List;
|
30
40
|
import java.util.concurrent.Callable;
|
@@ -33,278 +43,398 @@ import java.util.concurrent.ExecutorService;
|
|
33
43
|
import java.util.concurrent.Executors;
|
34
44
|
import java.util.concurrent.Future;
|
35
45
|
|
36
|
-
public class GcsOutputPlugin implements FileOutputPlugin
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
46
|
+
public class GcsOutputPlugin implements FileOutputPlugin
|
47
|
+
{
|
48
|
+
private static final Logger logger = Exec.getLogger(GcsOutputPlugin.class);
|
49
|
+
|
50
|
+
public interface PluginTask extends Task
|
51
|
+
{
|
52
|
+
@Config("bucket")
|
53
|
+
String getBucket();
|
54
|
+
|
55
|
+
@Config("path_prefix")
|
56
|
+
String getPathPrefix();
|
57
|
+
|
58
|
+
@Config("file_ext")
|
59
|
+
String getFileNameExtension();
|
60
|
+
|
61
|
+
@Config("sequence_format")
|
62
|
+
@ConfigDefault("\".%03d.%02d\"")
|
63
|
+
String getSequenceFormat();
|
64
|
+
|
65
|
+
@Config("content_type")
|
66
|
+
@ConfigDefault("\"application/octet-stream\"")
|
67
|
+
String getContentType();
|
68
|
+
|
69
|
+
@Config("auth_method")
|
70
|
+
@ConfigDefault("\"private_key\"")
|
71
|
+
AuthMethod getAuthMethod();
|
72
|
+
|
73
|
+
@Config("service_account_email")
|
74
|
+
@ConfigDefault("null")
|
75
|
+
Optional<String> getServiceAccountEmail();
|
76
|
+
|
77
|
+
// kept for backward compatibility
|
78
|
+
@Config("p12_keyfile_path")
|
79
|
+
@ConfigDefault("null")
|
80
|
+
Optional<String> getP12KeyfilePath();
|
81
|
+
|
82
|
+
@Config("p12_keyfile")
|
83
|
+
@ConfigDefault("null")
|
84
|
+
Optional<LocalFile> getP12Keyfile();
|
85
|
+
void setP12Keyfile(Optional<LocalFile> p12Keyfile);
|
86
|
+
|
87
|
+
@Config("json_keyfile")
|
88
|
+
@ConfigDefault("null")
|
89
|
+
Optional<LocalFile> getJsonKeyfile();
|
90
|
+
|
91
|
+
@Config("application_name")
|
92
|
+
@ConfigDefault("\"embulk-output-gcs\"")
|
93
|
+
String getApplicationName();
|
94
|
+
|
95
|
+
@Config("max_connection_retry")
|
96
|
+
@ConfigDefault("10") // 10 times retry to connect GCS server if failed.
|
97
|
+
int getMaxConnectionRetry();
|
98
|
+
}
|
99
|
+
|
100
|
+
@Override
|
101
|
+
public ConfigDiff transaction(ConfigSource config,
|
102
|
+
int taskCount,
|
103
|
+
FileOutputPlugin.Control control)
|
104
|
+
{
|
105
|
+
PluginTask task = config.loadConfig(PluginTask.class);
|
106
|
+
|
107
|
+
if (task.getP12KeyfilePath().isPresent()) {
|
108
|
+
if (task.getP12Keyfile().isPresent()) {
|
109
|
+
throw new ConfigException("Setting both p12_keyfile_path and p12_keyfile is invalid");
|
110
|
+
}
|
111
|
+
try {
|
112
|
+
task.setP12Keyfile(Optional.of(LocalFile.of(task.getP12KeyfilePath().get())));
|
113
|
+
}
|
114
|
+
catch (IOException ex) {
|
115
|
+
throw Throwables.propagate(ex);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
if (task.getAuthMethod().getString().equals("json_key")) {
|
120
|
+
if (!task.getJsonKeyfile().isPresent()) {
|
121
|
+
throw new ConfigException("If auth_method is json_key, you have to set json_keyfile");
|
122
|
+
}
|
123
|
+
}
|
124
|
+
else if (task.getAuthMethod().getString().equals("private_key")) {
|
125
|
+
if (!task.getP12Keyfile().isPresent() || !task.getServiceAccountEmail().isPresent()) {
|
126
|
+
throw new ConfigException("If auth_method is private_key, you have to set both service_account_email and p12_keyfile");
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
return resume(task.dump(), taskCount, control);
|
131
|
+
}
|
132
|
+
|
133
|
+
@Override
|
134
|
+
public ConfigDiff resume(TaskSource taskSource,
|
135
|
+
int taskCount,
|
136
|
+
FileOutputPlugin.Control control)
|
137
|
+
{
|
138
|
+
control.run(taskSource);
|
139
|
+
return Exec.newConfigDiff();
|
140
|
+
}
|
141
|
+
|
142
|
+
@Override
|
143
|
+
public void cleanup(TaskSource taskSource,
|
144
|
+
int taskCount,
|
145
|
+
List<TaskReport> successTaskReports)
|
146
|
+
{
|
147
|
+
}
|
148
|
+
|
149
|
+
@Override
|
150
|
+
public TransactionalFileOutput open(TaskSource taskSource, final int taskIndex)
|
151
|
+
{
|
152
|
+
PluginTask task = taskSource.loadTask(PluginTask.class);
|
153
|
+
|
154
|
+
Storage client = createClient(task);
|
155
|
+
return new TransactionalGcsFileOutput(task, client, taskIndex);
|
156
|
+
}
|
157
|
+
|
158
|
+
private GcsAuthentication newGcsAuth(PluginTask task)
|
159
|
+
{
|
160
|
+
try {
|
161
|
+
return new GcsAuthentication(
|
162
|
+
task.getAuthMethod().getString(),
|
163
|
+
task.getServiceAccountEmail(),
|
164
|
+
task.getP12Keyfile().transform(localFileToPathString()),
|
165
|
+
task.getJsonKeyfile().transform(localFileToPathString()),
|
166
|
+
task.getApplicationName()
|
167
|
+
);
|
168
|
+
}
|
169
|
+
catch (GeneralSecurityException | IOException ex) {
|
170
|
+
throw new ConfigException(ex);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
private Storage createClient(final PluginTask task)
|
175
|
+
{
|
176
|
+
try {
|
177
|
+
GcsAuthentication auth = newGcsAuth(task);
|
178
|
+
return auth.getGcsClient(task.getBucket(), task.getMaxConnectionRetry());
|
179
|
+
}
|
180
|
+
catch (ConfigException | IOException ex) {
|
181
|
+
throw Throwables.propagate(ex);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
private Function<LocalFile, String> localFileToPathString()
|
186
|
+
{
|
187
|
+
return new Function<LocalFile, String>()
|
188
|
+
{
|
189
|
+
public String apply(LocalFile file)
|
190
|
+
{
|
191
|
+
return file.getPath().toString();
|
192
|
+
}
|
193
|
+
};
|
194
|
+
}
|
195
|
+
|
196
|
+
static class TransactionalGcsFileOutput implements TransactionalFileOutput
|
197
|
+
{
|
198
|
+
private final int taskIndex;
|
199
|
+
private final Storage client;
|
200
|
+
private final String bucket;
|
201
|
+
private final String pathPrefix;
|
202
|
+
private final String pathSuffix;
|
203
|
+
private final String sequenceFormat;
|
204
|
+
private final String contentType;
|
205
|
+
private final int maxConnectionRetry;
|
206
|
+
private final List<StorageObject> storageObjects = new ArrayList<>();
|
207
|
+
|
208
|
+
private int fileIndex = 0;
|
209
|
+
private int callCount = 0;
|
210
|
+
private BufferedOutputStream currentStream = null;
|
211
|
+
private Future<StorageObject> currentUpload = null;
|
212
|
+
private File tempFile = null;
|
213
|
+
|
214
|
+
TransactionalGcsFileOutput(PluginTask task, Storage client, int taskIndex)
|
215
|
+
{
|
216
|
+
this.taskIndex = taskIndex;
|
217
|
+
this.client = client;
|
218
|
+
this.bucket = task.getBucket();
|
219
|
+
this.pathPrefix = task.getPathPrefix();
|
220
|
+
this.pathSuffix = task.getFileNameExtension();
|
221
|
+
this.sequenceFormat = task.getSequenceFormat();
|
222
|
+
this.contentType = task.getContentType();
|
223
|
+
this.maxConnectionRetry = task.getMaxConnectionRetry();
|
224
|
+
}
|
225
|
+
|
226
|
+
public void nextFile()
|
227
|
+
{
|
228
|
+
closeCurrentUpload();
|
229
|
+
try {
|
230
|
+
tempFile = Exec.getTempFileSpace().createTempFile();
|
231
|
+
currentStream = new BufferedOutputStream(new FileOutputStream(tempFile));
|
232
|
+
fileIndex++;
|
233
|
+
}
|
234
|
+
catch (IOException ex) {
|
235
|
+
Throwables.propagate(ex);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
@Override
|
240
|
+
public void add(Buffer buffer)
|
241
|
+
{
|
242
|
+
try {
|
243
|
+
logger.debug("#add called {} times for taskIndex {}", callCount, taskIndex);
|
244
|
+
currentStream.write(buffer.array(), buffer.offset(), buffer.limit());
|
245
|
+
callCount++;
|
246
|
+
}
|
247
|
+
catch (IOException ex) {
|
248
|
+
throw new RuntimeException(ex);
|
249
|
+
}
|
250
|
+
finally {
|
251
|
+
buffer.release();
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
@Override
|
256
|
+
public void finish()
|
257
|
+
{
|
258
|
+
String path = pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + pathSuffix;
|
259
|
+
close();
|
260
|
+
if (tempFile != null) {
|
261
|
+
currentUpload = startUpload(path);
|
262
|
+
}
|
263
|
+
|
264
|
+
closeCurrentUpload();
|
265
|
+
}
|
266
|
+
|
267
|
+
@Override
|
268
|
+
public void close()
|
269
|
+
{
|
270
|
+
try {
|
271
|
+
if (currentStream != null) {
|
272
|
+
currentStream.close();
|
273
|
+
currentStream = null;
|
274
|
+
}
|
275
|
+
}
|
276
|
+
catch (IOException ex) {
|
277
|
+
throw Throwables.propagate(ex);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
@Override
|
282
|
+
public void abort()
|
283
|
+
{
|
284
|
+
}
|
285
|
+
|
286
|
+
@Override
|
287
|
+
public TaskReport commit()
|
288
|
+
{
|
289
|
+
TaskReport report = Exec.newTaskReport();
|
290
|
+
report.set("files", storageObjects);
|
291
|
+
return report;
|
292
|
+
}
|
293
|
+
|
294
|
+
private void closeCurrentUpload()
|
295
|
+
{
|
296
|
+
try {
|
297
|
+
if (currentUpload != null) {
|
298
|
+
StorageObject obj = currentUpload.get();
|
299
|
+
storageObjects.add(obj);
|
300
|
+
logger.info("Uploaded '{}/{}' to {}bytes", obj.getBucket(), obj.getName(), obj.getSize());
|
301
|
+
currentUpload = null;
|
302
|
+
}
|
303
|
+
|
304
|
+
callCount = 0;
|
305
|
+
}
|
306
|
+
catch (InterruptedException | ExecutionException ex) {
|
307
|
+
throw Throwables.propagate(ex);
|
308
|
+
}
|
309
|
+
}
|
310
|
+
|
311
|
+
private Future<StorageObject> startUpload(final String path)
|
312
|
+
{
|
313
|
+
try {
|
314
|
+
final ExecutorService executor = Executors.newCachedThreadPool();
|
315
|
+
final String hash = getLocalMd5hash(tempFile.getAbsolutePath());
|
316
|
+
|
317
|
+
return executor.submit(new Callable<StorageObject>() {
|
318
|
+
@Override
|
319
|
+
public StorageObject call() throws IOException
|
320
|
+
{
|
321
|
+
try {
|
322
|
+
logger.info("Uploading '{}/{}'", bucket, path);
|
323
|
+
return execUploadWithRetry(path, hash);
|
324
|
+
}
|
325
|
+
finally {
|
326
|
+
executor.shutdown();
|
327
|
+
}
|
328
|
+
}
|
329
|
+
});
|
330
|
+
}
|
331
|
+
catch (IOException ex) {
|
332
|
+
throw Throwables.propagate(ex);
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
private StorageObject execUploadWithRetry(final String path, final String localHash) throws IOException
|
337
|
+
{
|
338
|
+
try {
|
339
|
+
return retryExecutor()
|
340
|
+
.withRetryLimit(maxConnectionRetry)
|
341
|
+
.withInitialRetryWait(500)
|
342
|
+
.withMaxRetryWait(30 * 1000)
|
343
|
+
.runInterruptible(new Retryable<StorageObject>() {
|
344
|
+
@Override
|
345
|
+
public StorageObject call() throws IOException, RetryGiveupException
|
346
|
+
{
|
347
|
+
try (final BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(tempFile))) {
|
348
|
+
InputStreamContent mediaContent = new InputStreamContent(contentType, inputStream);
|
349
|
+
mediaContent.setCloseInputStream(true);
|
350
|
+
|
351
|
+
StorageObject objectMetadata = new StorageObject();
|
352
|
+
objectMetadata.setName(path);
|
353
|
+
|
354
|
+
final Storage.Objects.Insert insert = client.objects().insert(bucket, objectMetadata, mediaContent);
|
355
|
+
insert.setDisableGZipContent(true);
|
356
|
+
StorageObject obj = insert.execute();
|
357
|
+
|
358
|
+
logger.info(String.format("Local Hash(MD5): %s / Remote Hash(MD5): %s", localHash, obj.getMd5Hash()));
|
359
|
+
return obj;
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
@Override
|
364
|
+
public boolean isRetryableException(Exception exception)
|
365
|
+
{
|
366
|
+
return true;
|
367
|
+
}
|
368
|
+
|
369
|
+
@Override
|
370
|
+
public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait) throws RetryGiveupException
|
371
|
+
{
|
372
|
+
String message = String.format("GCS put request failed. Retrying %d/%d after %d seconds. Message: %s: %s",
|
373
|
+
retryCount, retryLimit, retryWait / 1000, exception.getClass(), exception.getMessage());
|
374
|
+
if (retryCount % 3 == 0) {
|
375
|
+
logger.warn(message, exception);
|
376
|
+
}
|
377
|
+
else {
|
378
|
+
logger.warn(message);
|
379
|
+
}
|
380
|
+
}
|
381
|
+
|
382
|
+
@Override
|
383
|
+
public void onGiveup(Exception firstException, Exception lastException) throws RetryGiveupException
|
384
|
+
{
|
385
|
+
}
|
386
|
+
});
|
387
|
+
}
|
388
|
+
catch (RetryGiveupException ex) {
|
389
|
+
throw Throwables.propagate(ex.getCause());
|
390
|
+
}
|
391
|
+
catch (InterruptedException ex) {
|
392
|
+
throw new InterruptedIOException();
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
/*
|
397
|
+
MD5 hash sum on GCS bucket is encoded with base64.
|
398
|
+
You can get same hash with following commands.
|
399
|
+
$ openssl dgst -md5 -binary /path/to/file.txt | openssl enc -base64
|
400
|
+
or
|
401
|
+
$ gsutil hash -m /path/to/file.txt
|
402
|
+
*/
|
403
|
+
private String getLocalMd5hash(String filePath) throws IOException
|
404
|
+
{
|
405
|
+
try {
|
406
|
+
MessageDigest md = MessageDigest.getInstance("MD5");
|
407
|
+
try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(new File(filePath)))) {
|
408
|
+
byte[] buffer = new byte[256];
|
409
|
+
int len;
|
410
|
+
while ((len = input.read(buffer, 0, buffer.length)) >= 0) {
|
411
|
+
md.update(buffer, 0, len);
|
412
|
+
}
|
413
|
+
return new String(Base64.encodeBase64(md.digest()));
|
414
|
+
}
|
415
|
+
}
|
416
|
+
catch (NoSuchAlgorithmException ex) {
|
417
|
+
throw new ConfigException("MD5 algorism not found");
|
418
|
+
}
|
419
|
+
}
|
420
|
+
}
|
421
|
+
|
422
|
+
public enum AuthMethod
|
423
|
+
{
|
424
|
+
private_key("private_key"),
|
425
|
+
compute_engine("compute_engine"),
|
426
|
+
json_key("json_key");
|
427
|
+
|
428
|
+
private final String string;
|
429
|
+
|
430
|
+
AuthMethod(String string)
|
431
|
+
{
|
432
|
+
this.string = string;
|
433
|
+
}
|
434
|
+
|
435
|
+
public String getString()
|
436
|
+
{
|
437
|
+
return string;
|
438
|
+
}
|
439
|
+
}
|
310
440
|
}
|