Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.tradefed.util; |
| 18 | |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 19 | import com.android.tradefed.log.LogUtil.CLog; |
| 20 | |
| 21 | import java.io.File; |
| 22 | import java.io.IOException; |
| 23 | import java.nio.file.Path; |
| 24 | import java.nio.file.Paths; |
| 25 | import java.util.ArrayList; |
xingdai | 1adb0d0 | 2018-08-01 21:20:18 -0700 | [diff] [blame] | 26 | import java.util.Arrays; |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 27 | import java.util.List; |
| 28 | import java.util.concurrent.TimeUnit; |
xingdai | 1432ffe | 2018-08-24 14:44:11 -0700 | [diff] [blame] | 29 | import java.util.regex.Matcher; |
| 30 | import java.util.regex.Pattern; |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 31 | |
| 32 | /** |
| 33 | * File manager to download and upload files from Google Cloud Storage (GCS). |
| 34 | * |
Xing Dai | 842a176 | 2019-01-02 12:13:10 -0800 | [diff] [blame^] | 35 | * <p>This class should NOT be used from the scope of a test (i.e., IRemoteTest). |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 36 | */ |
Xing Dai | 842a176 | 2019-01-02 12:13:10 -0800 | [diff] [blame^] | 37 | @Deprecated |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 38 | public class GCSBucketUtil { |
| 39 | |
| 40 | // https://cloud.google.com/storage/docs/gsutil |
| 41 | |
| 42 | private static final String CMD_COPY = "cp"; |
| 43 | private static final String CMD_MAKE_BUCKET = "mb"; |
xingdai | 1adb0d0 | 2018-08-01 21:20:18 -0700 | [diff] [blame] | 44 | private static final String CMD_LS = "ls"; |
xingdai | 1432ffe | 2018-08-24 14:44:11 -0700 | [diff] [blame] | 45 | private static final String CMD_STAT = "stat"; |
| 46 | private static final String CMD_HASH = "hash"; |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 47 | private static final String CMD_REMOVE = "rm"; |
| 48 | private static final String CMD_REMOVE_BUCKET = "rb"; |
| 49 | private static final String CMD_VERSION = "-v"; |
| 50 | private static final String ENV_BOTO_PATH = "BOTO_PATH"; |
| 51 | private static final String ENV_BOTO_CONFIG = "BOTO_CONFIG"; |
| 52 | private static final String FILENAME_STDOUT = "-"; |
| 53 | private static final String FLAG_FORCE = "-f"; |
| 54 | private static final String FLAG_NO_CLOBBER = "-n"; |
| 55 | private static final String FLAG_PARALLEL = "-m"; |
| 56 | private static final String FLAG_PROJECT_ID = "-p"; |
| 57 | private static final String FLAG_RECURSIVE = "-r"; |
| 58 | private static final String GCS_SCHEME = "gs"; |
| 59 | private static final String GSUTIL = "gsutil"; |
| 60 | |
| 61 | /** |
| 62 | * Whether gsutil is verified to be installed |
| 63 | */ |
| 64 | private static boolean mCheckedGsutil = false; |
| 65 | |
| 66 | /** |
| 67 | * Number of attempts for gsutil operations. |
| 68 | * |
| 69 | * @see RunUtil#runTimedCmdRetry |
| 70 | */ |
| 71 | private int mAttempts = 1; |
| 72 | |
| 73 | /** |
| 74 | * Path to the .boto files to use, set via environment variable $BOTO_PATH. |
| 75 | * |
| 76 | * @see <a href="https://cloud.google.com/storage/docs/gsutil/commands/config"> |
| 77 | * gsutil documentation</a> |
| 78 | */ |
| 79 | private String mBotoPath = null; |
| 80 | |
| 81 | /** |
| 82 | * Path to the .boto file to use, set via environment variable $BOTO_CONFIG. |
| 83 | * |
| 84 | * @see <a href="https://cloud.google.com/storage/docs/gsutil/commands/config"> |
| 85 | * gsutil documentation</a> |
| 86 | */ |
| 87 | private String mBotoConfig = null; |
| 88 | |
| 89 | /** |
| 90 | * Name of the GCS bucket. |
| 91 | */ |
| 92 | private String mBucketName = null; |
| 93 | |
| 94 | /** |
| 95 | * Whether to use the "-n" flag to avoid clobbering files. |
| 96 | */ |
| 97 | private boolean mNoClobber = false; |
| 98 | |
| 99 | /** |
| 100 | * Whether to use the "-m" flag to parallelize large operations. |
| 101 | */ |
| 102 | private boolean mParallel = false; |
| 103 | |
| 104 | /** |
| 105 | * Whether to use the "-r" flag to perform a recursive copy. |
| 106 | */ |
| 107 | private boolean mRecursive = true; |
| 108 | |
| 109 | /** |
| 110 | * Retry interval for gsutil operations. |
| 111 | * |
| 112 | * @see RunUtil#runTimedCmdRetry |
| 113 | */ |
| 114 | private long mRetryInterval = 0; |
| 115 | |
| 116 | /** |
| 117 | * Timeout for gsutil operations. |
| 118 | * |
| 119 | * @see RunUtil#runTimedCmdRetry |
| 120 | */ |
| 121 | private long mTimeoutMs = 0; |
| 122 | |
| 123 | public GCSBucketUtil(String bucketName) { |
| 124 | setBucketName(bucketName); |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Verify that gsutil is installed. |
| 129 | */ |
| 130 | void checkGSUtil() throws IOException { |
| 131 | if (mCheckedGsutil) { |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | // N.B. We don't use retry / attempts here, since this doesn't involve any RPC. |
| 136 | CommandResult res = getRunUtil() |
| 137 | .runTimedCmd(mTimeoutMs, GSUTIL, CMD_VERSION); |
| 138 | |
| 139 | if (!CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 140 | throw new IOException( |
| 141 | "gsutil is not installed.\n" |
| 142 | + "https://cloud.google.com/storage/docs/gsutil for instructions."); |
| 143 | } |
| 144 | |
| 145 | mCheckedGsutil = true; |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Copy a file or directory to or from the bucket. |
| 150 | * |
| 151 | * @param source Source file or pattern |
| 152 | * @param dest Destination file or pattern |
| 153 | * @return {@link CommandResult} result of the operation. |
| 154 | */ |
| 155 | public CommandResult copy(String source, String dest) throws IOException { |
| 156 | checkGSUtil(); |
| 157 | CLog.d("Copying %s => %s", source, dest); |
| 158 | |
| 159 | IRunUtil run = getRunUtil(); |
| 160 | List<String> command = new ArrayList<>(); |
| 161 | |
| 162 | command.add(GSUTIL); |
| 163 | |
| 164 | if (mParallel) { |
| 165 | command.add(FLAG_PARALLEL); |
| 166 | } |
| 167 | |
| 168 | command.add(CMD_COPY); |
| 169 | |
| 170 | if (mRecursive) { |
| 171 | command.add(FLAG_RECURSIVE); |
| 172 | } |
| 173 | |
| 174 | if (mNoClobber) { |
| 175 | command.add(FLAG_NO_CLOBBER); |
| 176 | } |
| 177 | |
| 178 | command.add(source); |
| 179 | command.add(dest); |
| 180 | |
| 181 | String[] commandAsStr = command.toArray(new String[0]); |
| 182 | |
| 183 | CommandResult res = run |
| 184 | .runTimedCmdRetry(mTimeoutMs, mRetryInterval, mAttempts, commandAsStr); |
| 185 | if (!CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 186 | throw new IOException( |
| 187 | String.format( |
| 188 | "Failed to copy '%s' -> '%s' with %s\nstdout: %s\nstderr: %s", |
| 189 | source, |
| 190 | dest, |
| 191 | res.getStatus(), |
| 192 | res.getStdout(), |
| 193 | res.getStderr())); |
| 194 | } |
| 195 | return res; |
| 196 | } |
| 197 | |
| 198 | public int getAttempts() { |
| 199 | return mAttempts; |
| 200 | } |
| 201 | |
| 202 | public String getBotoConfig() { |
| 203 | return mBotoConfig; |
| 204 | } |
| 205 | |
| 206 | public String getBotoPath() { |
| 207 | return mBotoPath; |
| 208 | } |
| 209 | |
| 210 | public String getBucketName() { |
| 211 | return mBucketName; |
| 212 | } |
| 213 | |
| 214 | public boolean getNoClobber() { |
| 215 | return mNoClobber; |
| 216 | } |
| 217 | |
| 218 | public boolean getParallel() { |
| 219 | return mParallel; |
| 220 | } |
| 221 | |
| 222 | public boolean getRecursive() { |
| 223 | return mRecursive; |
| 224 | } |
| 225 | |
| 226 | public long getRetryInterval() { |
| 227 | return mRetryInterval; |
| 228 | } |
| 229 | |
| 230 | protected IRunUtil getRunUtil() { |
| 231 | IRunUtil run = new RunUtil(); |
| 232 | |
| 233 | if (mBotoPath != null) { |
| 234 | run.setEnvVariable(ENV_BOTO_PATH, mBotoPath); |
| 235 | } |
| 236 | |
| 237 | if (mBotoConfig != null) { |
| 238 | run.setEnvVariable(ENV_BOTO_CONFIG, mBotoConfig); |
| 239 | } |
| 240 | |
| 241 | return run; |
| 242 | } |
| 243 | |
| 244 | public long getTimeout() { |
| 245 | return mTimeoutMs; |
| 246 | } |
| 247 | |
| 248 | /** |
| 249 | * Retrieve the gs://bucket/path URI |
| 250 | */ |
| 251 | String getUriForGcsPath(Path path) { |
| 252 | // N.B. Would just use java.net.URI, but it doesn't allow e.g. underscores, |
| 253 | // which are valid in GCS bucket names. |
| 254 | if (!path.isAbsolute()) { |
| 255 | path = Paths.get("/").resolve(path); |
| 256 | } |
| 257 | return String.format("%s://%s%s", GCS_SCHEME, mBucketName, path.toString()); |
| 258 | } |
| 259 | |
| 260 | /** |
| 261 | * Make the GCS bucket. |
| 262 | * |
| 263 | * @return {@link CommandResult} result of the operation. |
| 264 | * @throws IOException |
| 265 | */ |
| 266 | public CommandResult makeBucket(String projectId) throws IOException { |
| 267 | checkGSUtil(); |
| 268 | CLog.d("Making bucket %s for project %s", mBucketName, projectId); |
| 269 | |
| 270 | List<String> command = new ArrayList<>(); |
| 271 | command.add(GSUTIL); |
| 272 | command.add(CMD_MAKE_BUCKET); |
| 273 | |
| 274 | if (projectId != null) { |
| 275 | command.add(FLAG_PROJECT_ID); |
| 276 | command.add(projectId); |
| 277 | } |
| 278 | |
| 279 | command.add(getUriForGcsPath(Paths.get("/"))); |
| 280 | |
| 281 | CommandResult res = getRunUtil() |
| 282 | .runTimedCmdRetry(mTimeoutMs, mRetryInterval, mAttempts, |
| 283 | command.toArray(new String[0])); |
| 284 | |
| 285 | if (!CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 286 | throw new IOException( |
| 287 | String.format( |
| 288 | "Failed to create bucket '%s' with %s\nstdout: %s\nstderr: %s", |
| 289 | mBucketName, |
| 290 | res.getStatus(), |
| 291 | res.getStdout(), |
| 292 | res.getStderr())); |
| 293 | } |
| 294 | |
| 295 | return res; |
| 296 | } |
| 297 | |
| 298 | /** |
xingdai | 1adb0d0 | 2018-08-01 21:20:18 -0700 | [diff] [blame] | 299 | * List files under a GCS path. |
| 300 | * |
| 301 | * @param bucketPath the GCS path |
| 302 | * @return a list of {@link String}s that are files under the GCS path |
| 303 | * @throws IOException |
| 304 | */ |
| 305 | public List<String> ls(Path bucketPath) throws IOException { |
| 306 | checkGSUtil(); |
| 307 | CLog.d("Check stat of %s %s", mBucketName, bucketPath); |
| 308 | |
| 309 | List<String> command = new ArrayList<>(); |
| 310 | command.add(GSUTIL); |
| 311 | command.add(CMD_LS); |
| 312 | |
| 313 | command.add(getUriForGcsPath(bucketPath)); |
| 314 | |
| 315 | CommandResult res = |
| 316 | getRunUtil() |
| 317 | .runTimedCmdRetry( |
| 318 | mTimeoutMs, |
| 319 | mRetryInterval, |
| 320 | mAttempts, |
| 321 | command.toArray(new String[0])); |
| 322 | |
| 323 | if (!CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 324 | throw new IOException( |
| 325 | String.format( |
| 326 | "Failed to list path '%s %s' with %s\nstdout: %s\nstderr: %s", |
| 327 | mBucketName, |
| 328 | bucketPath, |
| 329 | res.getStatus(), |
| 330 | res.getStdout(), |
| 331 | res.getStderr())); |
| 332 | } |
| 333 | return Arrays.asList(res.getStdout().split("\n")); |
| 334 | } |
| 335 | |
| 336 | /** |
| 337 | * Check a GCS file is a file or not a file (a folder). |
| 338 | * |
| 339 | * <p>If the filename ends with '/', then it's a folder. gsutil ls gs://filename should return |
| 340 | * the gs://filename if it's a file. gsutil ls gs://folder name should return the files in the |
| 341 | * folder if there are files in the folder. And it will return gs://folder/ if there is no files |
| 342 | * in the folder. |
| 343 | * |
| 344 | * @param path the path relative to bucket.. |
| 345 | * @return it's a file or not a file. |
| 346 | * @throws IOException |
| 347 | */ |
| 348 | public boolean isFile(String path) throws IOException { |
| 349 | if (path.endsWith("/")) { |
| 350 | return false; |
| 351 | } |
| 352 | List<String> files = ls(Paths.get(path)); |
| 353 | if (files.size() > 1) { |
| 354 | return false; |
| 355 | } |
| 356 | if (files.size() == 1) { |
| 357 | return files.get(0).equals(getUriForGcsPath(Paths.get(path))); |
| 358 | } |
| 359 | return false; |
| 360 | } |
| 361 | |
xingdai | 1432ffe | 2018-08-24 14:44:11 -0700 | [diff] [blame] | 362 | /** Simple wrapper for file info in GCS. */ |
| 363 | public static class GCSFileMetadata { |
| 364 | public String mName; |
| 365 | public String mMd5Hash = null; |
| 366 | |
| 367 | private GCSFileMetadata() {} |
| 368 | |
| 369 | /** |
| 370 | * Parse a string to a {@link GCSFileMetadata} object. |
| 371 | * |
| 372 | * @param statOutput |
| 373 | * @return {@link GCSFileMetadata} |
| 374 | */ |
| 375 | public static GCSFileMetadata parseStat(String statOutput) { |
| 376 | GCSFileMetadata info = new GCSFileMetadata(); |
| 377 | String[] infoLines = statOutput.split("\n"); |
| 378 | // Remove the trail ':' |
| 379 | info.mName = infoLines[0].substring(0, infoLines[0].length() - 1); |
| 380 | for (String line : infoLines) { |
| 381 | String[] keyValue = line.split(":", 2); |
| 382 | String key = keyValue[0].trim(); |
| 383 | String value = keyValue[1].trim(); |
| 384 | |
| 385 | if ("Hash (md5)".equals(key)) { |
| 386 | info.mMd5Hash = value; |
| 387 | } |
| 388 | } |
| 389 | return info; |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | /** |
| 394 | * Get the state of the file for the GCS path. |
| 395 | * |
| 396 | * @param bucketPath the GCS path |
| 397 | * @return {@link GCSFileMetadata} for the GCS path |
| 398 | * @throws IOException |
| 399 | */ |
| 400 | public GCSFileMetadata stat(Path bucketPath) throws IOException { |
| 401 | checkGSUtil(); |
| 402 | CLog.d("Check stat of %s %s", mBucketName, bucketPath); |
| 403 | |
| 404 | List<String> command = new ArrayList<>(); |
| 405 | command.add(GSUTIL); |
| 406 | command.add(CMD_STAT); |
| 407 | |
| 408 | command.add(getUriForGcsPath(bucketPath)); |
| 409 | |
| 410 | // The stat output will be something like: |
| 411 | // gs://bucketName/file.txt: |
| 412 | // Creation time: Tue, 14 Aug 2018 00:20:48 GMT |
| 413 | // Update time: Tue, 14 Aug 2018 16:58:39 GMT |
| 414 | // Storage class: STANDARD |
| 415 | // Content-Length: 1097 |
| 416 | // Content-Type: text/x-sh |
| 417 | // Hash (crc32c): WutM7Q== |
| 418 | // Hash (md5): GZX0xHUXtGnoKIGTDk6Pbg== |
| 419 | // ETag: CKKNu/Si69wCEAU= |
| 420 | // Generation: 1534206048913058 |
| 421 | // Metageneration: 5 |
| 422 | CommandResult res = |
| 423 | getRunUtil() |
| 424 | .runTimedCmdRetry( |
| 425 | mTimeoutMs, |
| 426 | mRetryInterval, |
| 427 | mAttempts, |
| 428 | command.toArray(new String[0])); |
| 429 | |
| 430 | if (!CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 431 | throw new IOException( |
| 432 | String.format( |
| 433 | "Failed to stat path '%s %s' with %s\nstdout: %s\nstderr: %s", |
| 434 | mBucketName, |
| 435 | bucketPath, |
| 436 | res.getStatus(), |
| 437 | res.getStdout(), |
| 438 | res.getStderr())); |
| 439 | } |
| 440 | return GCSFileMetadata.parseStat(res.getStdout()); |
| 441 | } |
| 442 | |
| 443 | /** |
| 444 | * Calculate the md5 hash for the local file. |
| 445 | * |
| 446 | * @param localFile a local file |
| 447 | * @return the md5 hash for the local file. |
| 448 | * @throws IOException |
| 449 | */ |
| 450 | public String md5Hash(File localFile) throws IOException { |
| 451 | checkGSUtil(); |
| 452 | List<String> command = new ArrayList<>(); |
| 453 | command.add(GSUTIL); |
| 454 | command.add(CMD_HASH); |
| 455 | command.add("-m"); |
| 456 | command.add(localFile.getAbsolutePath()); |
| 457 | |
| 458 | CommandResult res = |
| 459 | getRunUtil() |
| 460 | .runTimedCmdRetry( |
| 461 | mTimeoutMs, |
| 462 | mRetryInterval, |
| 463 | mAttempts, |
| 464 | command.toArray(new String[0])); |
| 465 | |
| 466 | if (CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 467 | // An example output of "gustil hash -m file": |
| 468 | // Hashes [base64] for error_prone_rules.mk: |
| 469 | // Hash (md5): eHfvTtNyH/x3GcyfApEIDQ== |
| 470 | // |
| 471 | // Operation completed over 1 objects/2.0 KiB. |
| 472 | Pattern md5Pattern = |
| 473 | Pattern.compile( |
| 474 | ".*Hash\\s*\\(md5\\)\\:\\s*(.*?)\n.*", |
| 475 | Pattern.MULTILINE | Pattern.DOTALL); |
| 476 | Matcher matcher = md5Pattern.matcher(res.getStdout()); |
| 477 | if (matcher.find()) { |
| 478 | return matcher.group(1); |
| 479 | } |
| 480 | } |
| 481 | throw new IOException( |
| 482 | String.format( |
| 483 | "Failed to calculate md5 hash for '%s' with %s\nstdout: %s\nstderr: %s", |
| 484 | localFile.getAbsoluteFile(), |
| 485 | res.getStatus(), |
| 486 | res.getStdout(), |
| 487 | res.getStderr())); |
| 488 | } |
| 489 | |
xingdai | 1adb0d0 | 2018-08-01 21:20:18 -0700 | [diff] [blame] | 490 | /** |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 491 | * Download a file or directory from a GCS bucket to the current directory. |
| 492 | * |
| 493 | * @param bucketPath File path in the GCS bucket |
| 494 | * @return {@link CommandResult} result of the operation. |
| 495 | */ |
| 496 | public CommandResult pull(Path bucketPath) throws IOException { |
| 497 | return copy(getUriForGcsPath(bucketPath), "."); |
| 498 | } |
| 499 | |
| 500 | /** |
| 501 | * Download a file or directory from a GCS bucket. |
| 502 | * |
| 503 | * @param bucketPath File path in the GCS bucket |
| 504 | * @param localFile Local destination path |
| 505 | * @return {@link CommandResult} result of the operation. |
| 506 | */ |
| 507 | public CommandResult pull(Path bucketPath, File localFile) throws IOException { |
| 508 | return copy(getUriForGcsPath(bucketPath), localFile.getPath()); |
| 509 | } |
| 510 | |
| 511 | /** |
| 512 | * Download a file from a GCS bucket, and extract its contents. |
| 513 | * |
| 514 | * @param bucketPath File path in the GCS bucket |
| 515 | * @return String contents of the file |
| 516 | */ |
| 517 | public String pullContents(Path bucketPath) throws IOException { |
| 518 | CommandResult res = copy(getUriForGcsPath(bucketPath), FILENAME_STDOUT); |
| 519 | return res.getStdout(); |
| 520 | } |
| 521 | |
| 522 | /** |
| 523 | * Upload a local file or directory to a GCS bucket. |
| 524 | * |
| 525 | * @param localFile Local file or directory |
| 526 | * @return {@link CommandResult} result of the operation. |
| 527 | */ |
| 528 | public CommandResult push(File localFile) throws IOException { |
| 529 | return push(localFile, Paths.get("/")); |
| 530 | } |
| 531 | |
| 532 | /** |
| 533 | * Upload a local file or directory to a GCS bucket with a specific path. |
| 534 | * |
| 535 | * @param localFile Local file or directory |
| 536 | * @param bucketPath File path in the GCS bucket |
| 537 | * @return {@link CommandResult} result of the operation. |
| 538 | */ |
| 539 | public CommandResult push(File localFile, Path bucketPath) throws IOException { |
| 540 | return copy(localFile.getAbsolutePath(), getUriForGcsPath(bucketPath)); |
| 541 | } |
| 542 | |
| 543 | /** |
| 544 | * Upload a String to a GCS bucket. |
| 545 | * |
| 546 | * @param contents File contents, as a string |
| 547 | * @param bucketPath File path in the GCS bucket |
| 548 | * @return {@link CommandResult} result of the operation. |
| 549 | */ |
| 550 | public CommandResult pushString(String contents, Path bucketPath) throws IOException { |
| 551 | File localFile = null; |
| 552 | try { |
| 553 | localFile = FileUtil.createTempFile(mBucketName, null); |
| 554 | FileUtil.writeToFile(contents, localFile); |
| 555 | return copy(localFile.getAbsolutePath(), getUriForGcsPath(bucketPath)); |
| 556 | } finally { |
| 557 | FileUtil.deleteFile(localFile); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | /** |
| 562 | * Remove a file or directory from the bucket. |
| 563 | * |
| 564 | * @param pattern File, directory, or pattern to remove. |
| 565 | * @param force Whether to ignore failures and continue silently (will not throw) |
| 566 | */ |
| 567 | public CommandResult remove(String pattern, boolean force) throws IOException { |
| 568 | checkGSUtil(); |
| 569 | String path = getUriForGcsPath(Paths.get(pattern)); |
xingdai | 1432ffe | 2018-08-24 14:44:11 -0700 | [diff] [blame] | 570 | CLog.d("Removing file(s) %s", path); |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 571 | |
| 572 | List<String> command = new ArrayList<>(); |
| 573 | command.add(GSUTIL); |
| 574 | command.add(CMD_REMOVE); |
| 575 | |
| 576 | if (mRecursive) { |
| 577 | command.add(FLAG_RECURSIVE); |
| 578 | } |
| 579 | |
| 580 | if (force) { |
| 581 | command.add(FLAG_FORCE); |
| 582 | } |
| 583 | |
| 584 | command.add(path); |
| 585 | |
| 586 | CommandResult res = getRunUtil() |
| 587 | .runTimedCmdRetry(mTimeoutMs, mRetryInterval, mAttempts, |
| 588 | command.toArray(new String[0])); |
| 589 | |
| 590 | if (!force && !CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 591 | throw new IOException( |
| 592 | String.format( |
| 593 | "Failed to remove '%s' with %s\nstdout: %s\nstderr: %s", |
| 594 | pattern, |
| 595 | res.getStatus(), |
| 596 | res.getStdout(), |
| 597 | res.getStderr())); |
| 598 | } |
| 599 | return res; |
| 600 | } |
| 601 | |
| 602 | /** |
| 603 | * Remove a file or directory from the bucket. |
| 604 | * |
| 605 | * @param pattern File, directory, or pattern to remove. |
| 606 | */ |
| 607 | public CommandResult remove(String pattern) throws IOException { |
| 608 | return remove(pattern, false); |
| 609 | } |
| 610 | |
| 611 | /** |
| 612 | * Remove a file or directory from the bucket. |
| 613 | * |
| 614 | * @param path Path to remove |
| 615 | * @param force Whether to fail if the file does not exist |
| 616 | */ |
| 617 | public CommandResult remove(Path path, boolean force) throws IOException { |
| 618 | return remove(path.toString(), force); |
| 619 | } |
| 620 | |
| 621 | /** |
| 622 | * Remove a file or directory from the bucket. |
| 623 | * |
| 624 | * @param path Path to remove |
| 625 | */ |
| 626 | public CommandResult remove(Path path) throws IOException { |
| 627 | return remove(path.toString(), false); |
| 628 | } |
| 629 | |
| 630 | |
| 631 | /** |
| 632 | * Remove the GCS bucket |
| 633 | * |
| 634 | * @throws IOException |
| 635 | */ |
| 636 | public CommandResult removeBucket() throws IOException { |
| 637 | checkGSUtil(); |
xingdai | 1432ffe | 2018-08-24 14:44:11 -0700 | [diff] [blame] | 638 | CLog.d("Removing bucket %s", mBucketName); |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 639 | |
| 640 | String[] command = { |
| 641 | GSUTIL, |
| 642 | CMD_REMOVE_BUCKET, |
| 643 | getUriForGcsPath(Paths.get("/")) |
| 644 | }; |
| 645 | |
| 646 | CommandResult res = getRunUtil() |
| 647 | .runTimedCmdRetry(mTimeoutMs, mRetryInterval, mAttempts, command); |
| 648 | |
| 649 | if (!CommandStatus.SUCCESS.equals(res.getStatus())) { |
| 650 | throw new IOException( |
| 651 | String.format( |
| 652 | "Failed to remove bucket '%s' with %s\nstdout: %s\nstderr: %s", |
| 653 | mBucketName, |
| 654 | res.getStatus(), |
| 655 | res.getStdout(), |
| 656 | res.getStderr())); |
| 657 | } |
| 658 | |
| 659 | return res; |
| 660 | } |
| 661 | |
| 662 | public void setAttempts(int attempts) { |
| 663 | mAttempts = attempts; |
| 664 | } |
| 665 | |
| 666 | public void setBotoConfig(String botoConfig) { |
| 667 | mBotoConfig = botoConfig; |
| 668 | } |
| 669 | |
| 670 | public void setBotoPath(String botoPath) { |
| 671 | mBotoPath = botoPath; |
| 672 | } |
| 673 | |
| 674 | public void setBucketName(String bucketName) { |
| 675 | mBucketName = bucketName; |
| 676 | } |
| 677 | |
| 678 | public void setNoClobber(boolean noClobber) { |
| 679 | mNoClobber = noClobber; |
| 680 | } |
| 681 | |
| 682 | public void setParallel(boolean parallel) { |
| 683 | mParallel = parallel; |
| 684 | } |
| 685 | |
| 686 | public void setRecursive(boolean recursive) { |
| 687 | mRecursive = recursive; |
| 688 | } |
| 689 | |
| 690 | public void setRetryInterval(long retryInterval) { |
| 691 | mRetryInterval = retryInterval; |
| 692 | } |
| 693 | |
| 694 | public void setTimeoutMs(long timeout) { |
| 695 | mTimeoutMs = timeout; |
| 696 | } |
| 697 | |
| 698 | public void setTimeout(long timeout, TimeUnit unit) { |
| 699 | setTimeoutMs(unit.toMillis(timeout)); |
| 700 | } |
Zach Riggle | 508ce43 | 2018-03-13 03:53:10 -0500 | [diff] [blame] | 701 | } |