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.
@@ -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.PipedInputStream;
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
- private static final Logger logger = Exec.getLogger(GcsOutputPlugin.class);
38
-
39
- public interface PluginTask extends Task {
40
- @Config("bucket")
41
- String getBucket();
42
-
43
- @Config("path_prefix")
44
- String getPathPrefix();
45
-
46
- @Config("file_ext")
47
- String getFileNameExtension();
48
-
49
- @Config("sequence_format")
50
- @ConfigDefault("\".%03d.%02d\"")
51
- String getSequenceFormat();
52
-
53
- @Config("content_type")
54
- @ConfigDefault("\"application/octet-stream\"")
55
- String getContentType();
56
-
57
- @Config("auth_method")
58
- @ConfigDefault("\"private_key\"")
59
- AuthMethod getAuthMethod();
60
-
61
- @Config("service_account_email")
62
- @ConfigDefault("null")
63
- Optional<String> getServiceAccountEmail();
64
-
65
- // kept for backward compatibility
66
- @Config("p12_keyfile_path")
67
- @ConfigDefault("null")
68
- Optional<String> getP12KeyfilePath();
69
-
70
- @Config("p12_keyfile")
71
- @ConfigDefault("null")
72
- Optional<LocalFile> getP12Keyfile();
73
- void setP12Keyfile(Optional<LocalFile> p12Keyfile);
74
-
75
- @Config("json_keyfile")
76
- @ConfigDefault("null")
77
- Optional<LocalFile> getJsonKeyfile();
78
-
79
- @Config("application_name")
80
- @ConfigDefault("\"embulk-output-gcs\"")
81
- String getApplicationName();
82
- }
83
-
84
- @Override
85
- public ConfigDiff transaction(ConfigSource config,
86
- int taskCount,
87
- FileOutputPlugin.Control control) {
88
- PluginTask task = config.loadConfig(PluginTask.class);
89
-
90
- if (task.getP12KeyfilePath().isPresent()) {
91
- if (task.getP12Keyfile().isPresent()) {
92
- throw new ConfigException("Setting both p12_keyfile_path and p12_keyfile is invalid");
93
- }
94
- try {
95
- task.setP12Keyfile(Optional.of(LocalFile.of(task.getP12KeyfilePath().get())));
96
- } catch (IOException ex) {
97
- throw Throwables.propagate(ex);
98
- }
99
- }
100
-
101
- if (task.getAuthMethod().getString().equals("json_key")) {
102
- if (!task.getJsonKeyfile().isPresent()) {
103
- throw new ConfigException("If auth_method is json_key, you have to set json_keyfile");
104
- }
105
- } else if (task.getAuthMethod().getString().equals("private_key")) {
106
- if (!task.getP12Keyfile().isPresent() || !task.getServiceAccountEmail().isPresent()) {
107
- throw new ConfigException("If auth_method is private_key, you have to set both service_account_email and p12_keyfile");
108
- }
109
- }
110
-
111
- return resume(task.dump(), taskCount, control);
112
- }
113
-
114
- @Override
115
- public ConfigDiff resume(TaskSource taskSource,
116
- int taskCount,
117
- FileOutputPlugin.Control control) {
118
- control.run(taskSource);
119
- return Exec.newConfigDiff();
120
- }
121
-
122
- @Override
123
- public void cleanup(TaskSource taskSource,
124
- int taskCount,
125
- List<TaskReport> successTaskReports) {
126
- }
127
-
128
- @Override
129
- public TransactionalFileOutput open(TaskSource taskSource, final int taskIndex) {
130
- PluginTask task = taskSource.loadTask(PluginTask.class);
131
-
132
- Storage client = createClient(task);
133
- return new TransactionalGcsFileOutput(task, client, taskIndex);
134
- }
135
-
136
- private GcsAuthentication newGcsAuth(PluginTask task)
137
- {
138
- try {
139
- return new GcsAuthentication(
140
- task.getAuthMethod().getString(),
141
- task.getServiceAccountEmail(),
142
- task.getP12Keyfile().transform(localFileToPathString()),
143
- task.getJsonKeyfile().transform(localFileToPathString()),
144
- task.getApplicationName()
145
- );
146
- } catch (GeneralSecurityException | IOException ex) {
147
- throw new ConfigException(ex);
148
- }
149
- }
150
-
151
- private Storage createClient(final PluginTask task) {
152
- Storage client = null;
153
- try {
154
- GcsAuthentication auth = newGcsAuth(task);
155
- client = auth.getGcsClient(task.getBucket());
156
- } catch (ConfigException | IOException ex) {
157
- throw new ConfigException(ex);
158
- }
159
-
160
- return client;
161
- }
162
-
163
- private Function<LocalFile, String> localFileToPathString() {
164
- return new Function<LocalFile, String>()
165
- {
166
- public String apply(LocalFile file)
167
- {
168
- return file.getPath().toString();
169
- }
170
- };
171
- }
172
-
173
- static class TransactionalGcsFileOutput implements TransactionalFileOutput {
174
- private final int taskIndex;
175
- private final Storage client;
176
- private final String bucket;
177
- private final String pathPrefix;
178
- private final String pathSuffix;
179
- private final String sequenceFormat;
180
- private final String contentType;
181
- private final List<StorageObject> storageObjects = new ArrayList<>();
182
-
183
- private int fileIndex = 0;
184
- private int callCount = 0;
185
- private PipedOutputStream currentStream = null;
186
- private Future<StorageObject> currentUpload = null;
187
-
188
- TransactionalGcsFileOutput(PluginTask task, Storage client, int taskIndex) {
189
- this.taskIndex = taskIndex;
190
- this.client = client;
191
- this.bucket = task.getBucket();
192
- this.pathPrefix = task.getPathPrefix();
193
- this.pathSuffix = task.getFileNameExtension();
194
- this.sequenceFormat = task.getSequenceFormat();
195
- this.contentType = task.getContentType();
196
- }
197
-
198
- public void nextFile() {
199
- closeCurrentUpload();
200
- currentStream = new PipedOutputStream();
201
- String path = pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + pathSuffix;
202
- logger.info("Uploading '{}/{}'", bucket, path);
203
- currentUpload = startUpload(path, contentType, currentStream);
204
- fileIndex++;
205
- }
206
-
207
- @Override
208
- public void add(Buffer buffer) {
209
- try {
210
- logger.debug("#add called {} times for taskIndex {}", callCount, taskIndex);
211
- currentStream.write(buffer.array(), buffer.offset(), buffer.limit());
212
- callCount++;
213
- } catch (IOException ex) {
214
- throw new RuntimeException(ex);
215
- } finally {
216
- buffer.release();
217
- }
218
- }
219
-
220
- @Override
221
- public void finish() {
222
- closeCurrentUpload();
223
- }
224
-
225
- @Override
226
- public void close() {
227
- closeCurrentUpload();
228
- }
229
-
230
- @Override
231
- public void abort() {
232
- }
233
-
234
- @Override
235
- public TaskReport commit() {
236
- TaskReport report = Exec.newTaskReport();
237
- report.set("files", storageObjects);
238
- return report;
239
- }
240
-
241
- private void closeCurrentUpload() {
242
- try {
243
- if (currentStream != null) {
244
- currentStream.close();
245
- currentStream = null;
246
- }
247
-
248
- if (currentUpload != null) {
249
- StorageObject obj = currentUpload.get();
250
- storageObjects.add(obj);
251
- logger.info("Uploaded '{}/{}' to {}bytes", obj.getBucket(), obj.getName(), obj.getSize());
252
- currentUpload = null;
253
- }
254
-
255
- callCount = 0;
256
- } catch (InterruptedException | ExecutionException | IOException ex) {
257
- throw Throwables.propagate(ex);
258
- }
259
- }
260
-
261
- private Future<StorageObject> startUpload(String path, String contentType, PipedOutputStream output) {
262
- try {
263
- final ExecutorService executor = Executors.newCachedThreadPool();
264
-
265
- PipedInputStream inputStream = new PipedInputStream(output);
266
- InputStreamContent mediaContent = new InputStreamContent(contentType, inputStream);
267
- mediaContent.setCloseInputStream(true);
268
-
269
- StorageObject objectMetadata = new StorageObject();
270
- objectMetadata.setName(path);
271
-
272
- final Storage.Objects.Insert insert = client.objects().insert(bucket, objectMetadata, mediaContent);
273
- insert.setDisableGZipContent(true);
274
- return executor.submit(new Callable<StorageObject>() {
275
- @Override
276
- public StorageObject call() throws InterruptedException {
277
- try {
278
- return insert.execute();
279
- } catch (IOException ex) {
280
- throw Throwables.propagate(ex);
281
- } finally {
282
- executor.shutdown();
283
- }
284
- }
285
- });
286
- } catch (IOException ex) {
287
- throw Throwables.propagate(ex);
288
- }
289
- }
290
- }
291
-
292
- public enum AuthMethod
293
- {
294
- private_key("private_key"),
295
- compute_engine("compute_engine"),
296
- json_key("json_key");
297
-
298
- private final String string;
299
-
300
- AuthMethod(String string)
301
- {
302
- this.string = string;
303
- }
304
-
305
- public String getString()
306
- {
307
- return string;
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
  }