diff --git a/.kokoro/common.sh b/.kokoro/common.sh index 8f09de5d3..a8d0ea04d 100644 --- a/.kokoro/common.sh +++ b/.kokoro/common.sh @@ -52,3 +52,8 @@ function retry_with_backoff { return $exit_code } + +## Helper functionss +function now() { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n'; } +function msg() { println "$*" >&2; } +function println() { printf '%s\n' "$(now) $*"; } \ No newline at end of file diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh index 0aade871c..cf3bb4347 100755 --- a/.kokoro/dependencies.sh +++ b/.kokoro/dependencies.sh @@ -36,3 +36,51 @@ retry_with_backoff 3 10 \ -Dclirr.skip=true mvn -B dependency:analyze -DfailOnWarning=true + +echo "****************** DEPENDENCY LIST COMPLETENESS CHECK *******************" +## Run dependency list completeness check +function completenessCheck() { + # Output dep list with compile scope generated using the original pom + msg "Generating dependency list using original pom..." + mvn dependency:list -f pom.xml -Dsort=true | grep '\[INFO] .*:.*:.*:.*:.*' | grep -v ':test$' >.org-list.txt + + # Output dep list generated using the flattened pom (test scope deps are ommitted) + msg "Generating dependency list using flattened pom..." + mvn dependency:list -f .flattened-pom.xml -Dsort=true | grep '\[INFO] .*:.*:.*:.*:.*' >.new-list.txt + + # Compare two dependency lists + msg "Comparing dependency lists..." + diff .org-list.txt .new-list.txt >.diff.txt + if [[ $? == 0 ]] + then + msg "Success. No diff!" + else + msg "Diff found. See below: " + msg "You can also check .diff.txt file located in $1." + cat .diff.txt + return 1 + fi +} + +# Allow failures to continue running the script +set +e + +error_count=0 +for path in $(find -name ".flattened-pom.xml") +do + # Check flattened pom in each dir that contains it for completeness + dir=$(dirname "$path") + pushd "$dir" + completenessCheck "$dir" + error_count=$(($error_count + $?)) + popd +done + +if [[ $error_count == 0 ]] +then + msg "All checks passed." + exit 0 +else + msg "Errors found. See log statements above." + exit 1 +fi diff --git a/.kokoro/nightly/integration.cfg b/.kokoro/nightly/integration.cfg index ca0274800..40c4abb7b 100644 --- a/.kokoro/nightly/integration.cfg +++ b/.kokoro/nightly/integration.cfg @@ -10,20 +10,25 @@ env_vars: { key: "JOB_TYPE" value: "integration" } - +# TODO: remove this after we've migrated all tests and scripts env_vars: { key: "GCLOUD_PROJECT" value: "gcloud-devel" } +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "gcloud-devel" +} + env_vars: { key: "ENABLE_BUILD_COP" value: "true" } env_vars: { - key: "GOOGLE_APPLICATION_CREDENTIALS" - value: "keystore/73713_java_it_service_account" + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "keystore/73713_java_it_service_account" } before_action { diff --git a/.kokoro/nightly/samples.cfg b/.kokoro/nightly/samples.cfg index b4b051cd0..20aabd55d 100644 --- a/.kokoro/nightly/samples.cfg +++ b/.kokoro/nightly/samples.cfg @@ -11,9 +11,15 @@ env_vars: { value: "samples" } +# TODO: remove this after we've migrated all tests and scripts env_vars: { key: "GCLOUD_PROJECT" - value: "gcloud-devel" + value: "java-docs-samples-testing" +} + +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "java-docs-samples-testing" } env_vars: { diff --git a/.kokoro/presubmit/samples.cfg b/.kokoro/presubmit/samples.cfg index fa7b493d0..1171aead0 100644 --- a/.kokoro/presubmit/samples.cfg +++ b/.kokoro/presubmit/samples.cfg @@ -11,14 +11,20 @@ env_vars: { value: "samples" } +# TODO: remove this after we've migrated all tests and scripts env_vars: { - key: "GCLOUD_PROJECT" - value: "gcloud-devel" + key: "GCLOUD_PROJECT" + value: "java-docs-samples-testing" } env_vars: { - key: "GOOGLE_APPLICATION_CREDENTIALS" - value: "keystore/73713_java_it_service_account" + key: "GOOGLE_CLOUD_PROJECT" + value: "java-docs-samples-testing" +} + +env_vars: { + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "keystore/73713_java_it_service_account" } before_action { diff --git a/.readme-partials.yaml b/.readme-partials.yaml index 4b9e2ad49..3a56b30ca 100644 --- a/.readme-partials.yaml +++ b/.readme-partials.yaml @@ -118,4 +118,9 @@ custom_content: | - [`StorageExample`](https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/StorageExample.java) is a simple command line interface that provides some of Cloud Storage's functionality. Read more about using the application on the [`StorageExample` docs page](https://github.com/googleapis/google-cloud-java/blob/master/google-cloud-examples/README.md). - [`Bookshelf`](https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/bookshelf) - An App Engine application that manages a virtual bookshelf. - This app uses `google-cloud` to interface with Cloud Datastore and Cloud Storage. It also uses Cloud SQL, another Google Cloud Platform service. - - [`Flexible Environment/Storage example`](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/flexible/cloudstorage) - An app that uploads files to a public Cloud Storage bucket on the App Engine Flexible Environment runtime. \ No newline at end of file + - [`Flexible Environment/Storage example`](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/flexible/cloudstorage) - An app that uploads files to a public Cloud Storage bucket on the App Engine Flexible Environment runtime. + +versioning: | + This library follows [Semantic Versioning](http://semver.org/), but does update [Storage interface](src/main/java/com.google.cloud.storage/Storage.java) + to introduce new methods which can break your implementations if you implement this interface for testing purposes. + diff --git a/CHANGELOG.md b/CHANGELOG.md index c3a855760..2ccff73a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## [1.108.0](https://www.github.com/googleapis/java-storage/compare/v1.107.0...v1.108.0) (2020-04-30) + + +### Features + +* add mockito dependency ([#284](https://www.github.com/googleapis/java-storage/issues/284)) ([58692dd](https://www.github.com/googleapis/java-storage/commit/58692dd8eeb2d228d14c896e563184d723b25df1)) +* V4 POST policy ([#177](https://www.github.com/googleapis/java-storage/issues/177)) ([32d8ffa](https://www.github.com/googleapis/java-storage/commit/32d8fface1a994cb5ac928f08c0467edc3c9aab1)) + + +### Bug Fixes + +* Documentation for Blob.update() and Storage.update() methods is confusing/incorrect ([#261](https://www.github.com/googleapis/java-storage/issues/261)) ([876405f](https://www.github.com/googleapis/java-storage/commit/876405f81cf195f5619b353be8d1e8efcbf5e0b3)), closes [#252](https://www.github.com/googleapis/java-storage/issues/252) + + +### Dependencies + +* pin mockito version to work with java 7 ([#292](https://www.github.com/googleapis/java-storage/issues/292)) ([8eb2fff](https://www.github.com/googleapis/java-storage/commit/8eb2fff3f51c90af7f76f74d40ed1d6d6b4320b7)) +* update dependency com.google.api.grpc:grpc-google-cloud-kms-v1 to v0.85.1 ([#273](https://www.github.com/googleapis/java-storage/issues/273)) ([7b5e7d1](https://www.github.com/googleapis/java-storage/commit/7b5e7d173cdac6b2de802c568e3a60b915d39d1c)) +* update dependency com.google.api.grpc:proto-google-cloud-kms-v1 to v0.85.1 ([#274](https://www.github.com/googleapis/java-storage/issues/274)) ([0ab4304](https://www.github.com/googleapis/java-storage/commit/0ab4304ea4e5e5668c05c67d2c96c6056f8c19c2)) +* update dependency com.google.cloud:google-cloud-conformance-tests to v0.0.10 ([#281](https://www.github.com/googleapis/java-storage/issues/281)) ([f3dee7e](https://www.github.com/googleapis/java-storage/commit/f3dee7ea0d0e305f0bc0c980aa65e538f7bf890c)) +* update dependency com.google.http-client:google-http-client-bom to v1.35.0 ([#282](https://www.github.com/googleapis/java-storage/issues/282)) ([1c1c1be](https://www.github.com/googleapis/java-storage/commit/1c1c1bee0d6382e76e74f9a00dca8e527cc390c6)) +* update dependency io.grpc:grpc-bom to v1.28.1 ([#250](https://www.github.com/googleapis/java-storage/issues/250)) ([b35e81c](https://www.github.com/googleapis/java-storage/commit/b35e81ce19fa72672aefe8bd956959bfa954194c)) +* update dependency io.grpc:grpc-bom to v1.29.0 ([#275](https://www.github.com/googleapis/java-storage/issues/275)) ([9b241b4](https://www.github.com/googleapis/java-storage/commit/9b241b468d4f3a73b81c5bc67c085c6fe7c6ea1e)) +* update dependency org.threeten:threetenbp to v1.4.4 ([#278](https://www.github.com/googleapis/java-storage/issues/278)) ([7bae49f](https://www.github.com/googleapis/java-storage/commit/7bae49f16ba5de0eeac8301a6a11b85bd4406ed5)) + + +### Documentation + +* label legacy storage classes in documentation ([#267](https://www.github.com/googleapis/java-storage/issues/267)) ([50e5938](https://www.github.com/googleapis/java-storage/commit/50e5938147f7bb2594b9a142e8087c6e555f4979)), closes [#254](https://www.github.com/googleapis/java-storage/issues/254) + ## [1.107.0](https://www.github.com/googleapis/java-storage/compare/v1.106.0...v1.107.0) (2020-04-14) diff --git a/README.md b/README.md index 074a1de9e..193fa6606 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file com.google.cloud libraries-bom - 4.4.1 + 5.3.0 pom import @@ -38,7 +38,7 @@ If you are using Maven without BOM, add this to your dependencies: com.google.cloud google-cloud-storage - 1.106.0 + 1.107.0 ``` @@ -47,11 +47,11 @@ If you are using Maven without BOM, add this to your dependencies: If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-storage:1.107.0' +compile 'com.google.cloud:google-cloud-storage:1.108.0' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "1.107.0" +libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "1.108.0" ``` [//]: # ({x-version-update-end}) @@ -207,16 +207,15 @@ display on your webpage. + ## Troubleshooting To get help, follow the instructions in the [shared Troubleshooting document][troubleshooting]. - ## Java Versions Java 7 or above is required for using this client. - ## Versioning This library follows [Semantic Versioning](http://semver.org/), but does update [Storage interface](src/main/java/com.google.cloud.storage/Storage.java) diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml new file mode 100644 index 000000000..ae8d781db --- /dev/null +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -0,0 +1,24 @@ + + + + + com/google/cloud/storage/Storage + com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.PostPolicyV4$PostFieldsV4, com.google.cloud.storage.PostPolicyV4$PostConditionsV4, com.google.cloud.storage.Storage$PostPolicyV4Option[]) + 7012 + + + com/google/cloud/storage/Storage + com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.PostPolicyV4$PostFieldsV4, com.google.cloud.storage.Storage$PostPolicyV4Option[]) + 7012 + + + com/google/cloud/storage/Storage + com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.PostPolicyV4$PostConditionsV4, com.google.cloud.storage.Storage$PostPolicyV4Option[]) + 7012 + + + com/google/cloud/storage/Storage + com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.Storage$PostPolicyV4Option[]) + 7012 + + \ No newline at end of file diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 94cbf8a78..ce9a1145d 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 1.107.0 + 1.108.0 jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 1.107.0 + 1.108.0 google-cloud-storage @@ -40,6 +40,10 @@ com.google.apis google-api-services-storage + + com.google.code.gson + gson + com.google.cloud google-cloud-core @@ -126,13 +130,13 @@ com.google.api.grpc grpc-google-cloud-kms-v1 - 0.83.1 + 0.85.1 test com.google.api.grpc proto-google-cloud-kms-v1 - 0.83.1 + 0.85.1 test @@ -156,6 +160,11 @@ easymock test + + org.mockito + mockito-core + test + org.objenesis objenesis @@ -169,7 +178,16 @@ org.apache.httpcomponents httpclient - 4.5.12 + test + + + org.apache.httpcomponents + httpmime + test + + + org.apache.httpcomponents + httpcore test diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java index 3cb99e61d..743f3e7b4 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java @@ -546,45 +546,27 @@ public Blob reload(BlobSourceOption... options) { } /** - * Updates the blob's information. Bucket or blob's name cannot be changed by this method. If you - * want to rename the blob or move it to a different bucket use the {@link #copyTo} and {@link - * #delete} operations. A new {@code Blob} object is returned. By default no checks are made on - * the metadata generation of the current blob. If you want to update the information only if the - * current blob metadata are at their latest version use the {@code metagenerationMatch} option: - * {@code newBlob.update(BlobTargetOption.metagenerationMatch())}. + * Updates the blob properties. The {@code options} parameter contains the preconditions for + * applying the update. To update the properties call {@link #toBuilder()}, set the properties you + * want to change, build the new {@code Blob} instance, and then call {@link + * #update(BlobTargetOption...)}. * - *

Original metadata are merged with metadata in the provided {@code blobInfo}. If the original - * metadata already contains a key specified in the provided {@code blobInfo's} metadata map, it - * will be replaced by the new value. Removing metadata can be done by setting that metadata's - * value to {@code null}. + *

The property update details are described in {@link Storage#update(BlobInfo)}. {@link + * Storage#update(BlobInfo, BlobTargetOption...)} describes how to specify preconditions. * - *

Example of adding new metadata values or updating existing ones. + *

Example of updating the content type: * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
-   * Map newMetadata = new HashMap<>();
-   * newMetadata.put("keyToAddOrUpdate", "value");
-   * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName)
-   *     .setMetadata(newMetadata)
-   *     .build());
-   * }
- * - *

Example of removing metadata values. - * - *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
-   * Map newMetadata = new HashMap<>();
-   * newMetadata.put("keyToRemove", null);
-   * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName)
-   *     .setMetadata(newMetadata)
-   *     .build());
+   * BlobId blobId = BlobId.of(bucketName, blobName);
+   * Blob blob = storage.get(blobId);
+   * blob.toBuilder().setContentType("text/plain").build().update();
    * }
* - * @param options update options - * @return a {@code Blob} object with updated information + * @param options preconditions to apply the update + * @return the updated {@code Blob} * @throws StorageException upon failure + * @see https://cloud.google.com/storage/docs/json_api/v1/objects/update */ public Blob update(BlobTargetOption... options) { return storage.update(this, options); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java new file mode 100644 index 000000000..df2936dc9 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java @@ -0,0 +1,389 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Presigned V4 post policy. + * + * @see POST Object + */ +public final class PostPolicyV4 { + private String url; + private Map fields; + + private PostPolicyV4(String url, Map fields) { + this.url = url; + this.fields = fields; + } + + public static PostPolicyV4 of(String url, Map fields) { + return new PostPolicyV4(url, fields); + } + + public String getUrl() { + return url; + } + + public Map getFields() { + return fields; + } + + /** + * Class representing which fields to specify in a V4 POST request. + * + * @see POST + * Object Form fields + */ + public static final class PostFieldsV4 { + private Map fieldsMap; + + private PostFieldsV4(Builder builder) { + this.fieldsMap = builder.fieldsMap; + } + + private PostFieldsV4(Map fields) { + this.fieldsMap = fields; + } + + public static PostFieldsV4 of(Map fields) { + return new PostFieldsV4(fields); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public Map getFieldsMap() { + return fieldsMap; + } + + public static class Builder { + private Map fieldsMap; + + private Builder() { + fieldsMap = new HashMap<>(); + } + + public PostFieldsV4 build() { + return new PostFieldsV4(this); + } + + public Builder setAcl(String acl) { + fieldsMap.put("acl", acl); + return this; + } + + public Builder setCacheControl(String cacheControl) { + fieldsMap.put("cache-control", cacheControl); + return this; + } + + public Builder setContentDisposition(String contentDisposition) { + fieldsMap.put("content-disposition", contentDisposition); + return this; + } + + public Builder setContentEncoding(String contentEncoding) { + fieldsMap.put("content-encoding", contentEncoding); + return this; + } + + public Builder setContentLength(int contentLength) { + fieldsMap.put("content-length", "" + contentLength); + return this; + } + + public Builder setContentType(String contentType) { + fieldsMap.put("content-type", contentType); + return this; + } + + public Builder Expires(String expires) { + fieldsMap.put("expires", expires); + return this; + } + + public Builder setSuccessActionRedirect(String successActionRedirect) { + fieldsMap.put("success_action_redirect", successActionRedirect); + return this; + } + + public Builder setSuccessActionStatus(int successActionStatus) { + fieldsMap.put("success_action_status", "" + successActionStatus); + return this; + } + + public Builder AddCustomMetadataField(String field, String value) { + fieldsMap.put("x-goog-meta-" + field, value); + return this; + } + } + } + + /** + * Class for specifying conditions in a V4 POST Policy document. + * + * @see + * Policy document + */ + public static final class PostConditionsV4 { + private Set conditions; + + private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + public PostConditionsV4(Builder builder) { + this.conditions = builder.conditions; + } + + public Builder toBuilder() { + return new Builder(conditions); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public Set getConditions() { + return conditions; + } + + public static class Builder { + Set conditions; + + private Builder() { + this.conditions = new LinkedHashSet<>(); + } + + private Builder(Set conditions) { + this.conditions = conditions; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public PostConditionsV4 build() { + return new PostConditionsV4(this); + } + + public Builder addAclCondition(ConditionV4Type type, String acl) { + conditions.add(new ConditionV4(type, "acl", acl)); + return this; + } + + public Builder addBucketCondition(ConditionV4Type type, String bucket) { + conditions.add(new ConditionV4(type, "bucket", bucket)); + return this; + } + + public Builder addCacheControlCondition(ConditionV4Type type, String cacheControl) { + conditions.add(new ConditionV4(type, "cache-control", cacheControl)); + return this; + } + + public Builder addContentDispositionCondition( + ConditionV4Type type, String contentDisposition) { + conditions.add(new ConditionV4(type, "content-disposition", contentDisposition)); + return this; + } + + public Builder addContentEncodingCondition(ConditionV4Type type, String contentEncoding) { + conditions.add(new ConditionV4(type, "content-encoding", contentEncoding)); + return this; + } + + public Builder addContentLengthCondition(ConditionV4Type type, int contentLength) { + conditions.add(new ConditionV4(type, "content-length", "" + contentLength)); + return this; + } + + public Builder addContentTypeCondition(ConditionV4Type type, String contentType) { + conditions.add(new ConditionV4(type, "content-type", contentType)); + return this; + } + + public Builder addExpiresCondition(ConditionV4Type type, long expires) { + conditions.add(new ConditionV4(type, "expires", dateFormat.format(expires))); + return this; + } + + public Builder addExpiresCondition(ConditionV4Type type, String expires) { + conditions.add(new ConditionV4(type, "expires", expires)); + return this; + } + + public Builder addKeyCondition(ConditionV4Type type, String key) { + conditions.add(new ConditionV4(type, "key", key)); + return this; + } + + public Builder addSuccessActionRedirectUrlCondition( + ConditionV4Type type, String successActionRedirectUrl) { + conditions.add(new ConditionV4(type, "success_action_redirect", successActionRedirectUrl)); + return this; + } + + public Builder addSuccessActionStatusCondition(ConditionV4Type type, int status) { + conditions.add(new ConditionV4(type, "success_action_status", "" + status)); + return this; + } + + public Builder addContentLengthRangeCondition(int min, int max) { + conditions.add(new ConditionV4(ConditionV4Type.CONTENT_LENGTH_RANGE, "" + min, "" + max)); + return this; + } + + Builder addCustomCondition(ConditionV4Type type, String field, String value) { + conditions.add(new ConditionV4(type, field, value)); + return this; + } + } + } + + /** + * Class for a V4 POST Policy document. + * + * @see + * Policy document + */ + public static final class PostPolicyV4Document { + private String expiration; + private PostConditionsV4 conditions; + + private PostPolicyV4Document(String expiration, PostConditionsV4 conditions) { + this.expiration = expiration; + this.conditions = conditions; + } + + public static PostPolicyV4Document of(String expiration, PostConditionsV4 conditions) { + return new PostPolicyV4Document(expiration, conditions); + } + + public String toJson() { + JsonObject object = new JsonObject(); + JsonArray conditions = new JsonArray(); + for (ConditionV4 condition : this.conditions.conditions) { + switch (condition.type) { + case MATCHES: + JsonObject match = new JsonObject(); + match.addProperty(condition.operand1, condition.operand2); + conditions.add(match); + break; + case STARTS_WITH: + JsonArray startsWith = new JsonArray(); + startsWith.add("starts-with"); + startsWith.add("$" + condition.operand1); + startsWith.add(condition.operand2); + conditions.add(startsWith); + break; + case CONTENT_LENGTH_RANGE: + JsonArray contentLengthRange = new JsonArray(); + contentLengthRange.add("content-length-range"); + contentLengthRange.add(Integer.parseInt(condition.operand1)); + contentLengthRange.add(Integer.parseInt(condition.operand2)); + conditions.add(contentLengthRange); + break; + } + } + object.add("conditions", conditions); + object.addProperty("expiration", expiration); + + String json = object.toString(); + StringBuilder escapedJson = new StringBuilder(); + + // Certain characters in a policy must be escaped + for (char c : json.toCharArray()) { + if (c >= 128) { // is a unicode character + escapedJson.append(String.format("\\u%04x", (int) c)); + } else { + switch (c) { + case '\\': + escapedJson.append("\\\\"); + break; + case '\b': + escapedJson.append("\\b"); + break; + case '\f': + escapedJson.append("\\f"); + break; + case '\n': + escapedJson.append("\\n"); + break; + case '\r': + escapedJson.append("\\r"); + break; + case '\t': + escapedJson.append("\\t"); + break; + case '\u000b': + escapedJson.append("\\v"); + break; + default: + escapedJson.append(c); + } + } + } + return escapedJson.toString(); + } + } + + public enum ConditionV4Type { + MATCHES, + STARTS_WITH, + CONTENT_LENGTH_RANGE + } + + /** + * Class for a specific POST policy document condition. + * + * @see + * Policy document + */ + static final class ConditionV4 { + ConditionV4Type type; + String operand1; + String operand2; + + private ConditionV4(ConditionV4Type type, String operand1, String operand2) { + this.type = type; + this.operand1 = operand1; + this.operand2 = operand2; + } + + @Override + public boolean equals(Object other) { + ConditionV4 condition = (ConditionV4) other; + return this.type == condition.type + && this.operand1.equals(condition.operand1) + && this.operand2.equals(condition.operand2); + } + + @Override + public int hashCode() { + return Objects.hash(type, operand1, operand2); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index a9bb589a2..38794cd4b 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -32,6 +32,8 @@ import com.google.cloud.WriteChannel; import com.google.cloud.storage.Acl.Entity; import com.google.cloud.storage.HmacKey.HmacKeyMetadata; +import com.google.cloud.storage.PostPolicyV4.PostConditionsV4; +import com.google.cloud.storage.PostPolicyV4.PostFieldsV4; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -1041,7 +1043,7 @@ public static BlobListOption userProject(String userProject) { /** * If set to {@code true}, lists all versions of a blob. The default is {@code false}. * - * @see Object Versioning + * @see Object Versioning */ public static BlobListOption versions(boolean versions) { return new BlobListOption(StorageRpc.Option.VERSIONS, versions); @@ -1060,6 +1062,107 @@ public static BlobListOption fields(BlobField... fields) { } } + /** Class for specifying Post Policy V4 options. * */ + class PostPolicyV4Option implements Serializable { + private static final long serialVersionUID = 8150867146534084543L; + private final PostPolicyV4Option.Option option; + private final Object value; + + enum Option { + PATH_STYLE, + VIRTUAL_HOSTED_STYLE, + BUCKET_BOUND_HOST_NAME, + SERVICE_ACCOUNT_CRED + } + + private PostPolicyV4Option(Option option, Object value) { + this.option = option; + this.value = value; + } + + PostPolicyV4Option.Option getOption() { + return option; + } + + Object getValue() { + return value; + } + + /** + * Provides a service account signer to sign the policy. If not provided an attempt is made to + * get it from the environment. + * + * @see Service + * Accounts + */ + public static PostPolicyV4Option signWith(ServiceAccountSigner signer) { + return new PostPolicyV4Option(PostPolicyV4Option.Option.SERVICE_ACCOUNT_CRED, signer); + } + + /** + * Use a virtual hosted-style hostname, which adds the bucket into the host portion of the URI + * rather than the path, e.g. 'https://mybucket.storage.googleapis.com/...'. The bucket name is + * obtained from the resource passed in. + * + * @see Request Endpoints + */ + public static PostPolicyV4Option withVirtualHostedStyle() { + return new PostPolicyV4Option(PostPolicyV4Option.Option.VIRTUAL_HOSTED_STYLE, ""); + } + + /** + * Generates a path-style URL, which places the bucket name in the path portion of the URL + * instead of in the hostname, e.g 'https://storage.googleapis.com/mybucket/...'. Note that this + * cannot be used alongside {@code withVirtualHostedStyle()}. Virtual hosted-style URLs, which + * can be used via the {@code withVirtualHostedStyle()} method, should generally be preferred + * instead of path-style URLs. + * + * @see Request Endpoints + */ + public static PostPolicyV4Option withPathStyle() { + return new PostPolicyV4Option(PostPolicyV4Option.Option.PATH_STYLE, ""); + } + + /** + * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of + * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load + * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this + * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This + * method signature uses HTTP for the URI scheme, and is equivalent to calling {@code + * withBucketBoundHostname("...", UriScheme.HTTP).} + * + * @see CNAME + * Redirects + * @see + * GCLB Redirects + */ + public static PostPolicyV4Option withBucketBoundHostname(String bucketBoundHostname) { + return withBucketBoundHostname(bucketBoundHostname, Storage.UriScheme.HTTP); + } + + /** + * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of + * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load + * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this + * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. The + * bucket name itself should not include the URI scheme (http or https), so it is specified via + * a local enum. + * + * @see CNAME + * Redirects + * @see + * GCLB Redirects + */ + public static PostPolicyV4Option withBucketBoundHostname( + String bucketBoundHostname, Storage.UriScheme uriScheme) { + return new PostPolicyV4Option( + PostPolicyV4Option.Option.BUCKET_BOUND_HOST_NAME, + uriScheme.getScheme() + "://" + bucketBoundHostname); + } + } + /** Class for specifying signed URL options. */ class SignUrlOption implements Serializable { @@ -1154,8 +1257,8 @@ public static SignUrlOption withV4Signature() { } /** - * Provides a service account signer to sign the URL. If not provided an attempt will be made to - * get it from the environment. + * Provides a service account signer to sign the URL. If not provided an attempt is made to get + * it from the environment. * * @see Service * Accounts @@ -1167,7 +1270,7 @@ public static SignUrlOption signWith(ServiceAccountSigner signer) { /** * Use a different host name than the default host name 'storage.googleapis.com'. This option is * particularly useful for developers to point requests to an alternate endpoint (e.g. a staging - * environment or sending requests through VPC). Note that if using this with the {@code + * environment or sending requests through VPC). If using this with the {@code * withVirtualHostedStyle()} method, you should omit the bucket name from the hostname, as it * automatically gets prepended to the hostname for virtual hosted-style URLs. */ @@ -1177,10 +1280,10 @@ public static SignUrlOption withHostName(String hostName) { /** * Use a virtual hosted-style hostname, which adds the bucket into the host portion of the URI - * rather than the path, e.g. 'https://mybucket.storage.googleapis.com/...'. The bucket name - * will be obtained from the resource passed in. For V4 signing, this also sets the "host" - * header in the canonicalized extension headers to the virtual hosted-style host, unless that - * header is supplied via the {@code withExtHeaders()} method. + * rather than the path, e.g. 'https://mybucket.storage.googleapis.com/...'. The bucket name is + * obtained from the resource passed in. For V4 signing, this also sets the "host" header in the + * canonicalized extension headers to the virtual hosted-style host, unless that header is + * supplied via the {@code withExtHeaders()} method. * * @see Request Endpoints */ @@ -1189,11 +1292,11 @@ public static SignUrlOption withVirtualHostedStyle() { } /** - * Generate a path-style URL, which places the bucket name in the path portion of the URL - * instead of in the hostname, e.g 'https://storage.googleapis.com/mybucket/...'. Note that this - * cannot be used alongside {@code withVirtualHostedStyle()}. Virtual hosted-style URLs, which - * can be used via the {@code withVirtualHostedStyle()} method, should generally be preferred - * instead of path-style URLs. + * Generates a path-style URL, which places the bucket name in the path portion of the URL + * instead of in the hostname, e.g 'https://storage.googleapis.com/mybucket/...'. This cannot be + * used alongside {@code withVirtualHostedStyle()}. Virtual hosted-style URLs, which can be used + * via the {@code withVirtualHostedStyle()} method, should generally be preferred instead of + * path-style URLs. * * @see Request Endpoints */ @@ -1204,9 +1307,9 @@ public static SignUrlOption withPathStyle() { /** * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load - * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this - * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This - * method signature uses HTTP for the URI scheme, and is equivalent to calling {@code + * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. This cannot be + * used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This method + * signature uses HTTP for the URI scheme, and is equivalent to calling {@code * withBucketBoundHostname("...", UriScheme.HTTP).} * * @see CNAME @@ -1896,6 +1999,7 @@ Blob create( * only if supplied Decrpytion Key decrypts the blob successfully, otherwise a {@link * StorageException} is thrown. For more information review * + * @throws StorageException upon failure * @see Encrypted * Elements @@ -1989,60 +2093,73 @@ Blob create( Bucket update(BucketInfo bucketInfo, BucketTargetOption... options); /** - * Updates blob information. Original metadata are merged with metadata in the provided {@code - * blobInfo}. To replace metadata instead you first have to unset them. Unsetting metadata can be - * done by setting the provided {@code blobInfo}'s metadata to {@code null}. Accepts an optional - * userProject {@link BlobTargetOption} option which defines the project id to assign operational - * costs. + * Updates the blob properties if the preconditions specified by {@code options} are met. The + * property update works as described in {@link #update(BlobInfo)}. + * + *

{@code options} parameter can contain the preconditions for applying the update. E.g. update + * of the blob properties might be required only if the properties have not been updated + * externally. {@code StorageException} with the code {@code 412} is thrown if preconditions fail. * - *

Example of udating a blob, only if the blob's metageneration matches a value, otherwise a - * {@link StorageException} is thrown. + *

Example of updating the content type only if the properties are not updated externally: * *

{@code
-   * String bucketName = "my-unique-bucket";
-   * String blobName = "my-blob-name";
-   * Blob blob = storage.get(bucketName, blobName);
-   * BlobInfo updatedInfo = blob.toBuilder().setContentType("text/plain").build();
-   * storage.update(updatedInfo, BlobTargetOption.metagenerationMatch());
+   * BlobId blobId = BlobId.of(bucketName, blobName);
+   * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
+   * Blob blob = storage.create(blobInfo);
+   *
+   * doSomething();
+   *
+   * BlobInfo update = blob.toBuilder().setContentType("multipart/form-data").build();
+   * Storage.BlobTargetOption option = Storage.BlobTargetOption.metagenerationMatch();
+   * try {
+   *   storage.update(update, option);
+   * } catch (StorageException e) {
+   *   if (e.getCode() == 412) {
+   *     // the properties were updated externally
+   *   } else {
+   *     throw e;
+   *   }
+   * }
    * }
* + * @param blobInfo information to update + * @param options preconditions to apply the update * @return the updated blob * @throws StorageException upon failure + * @see https://cloud.google.com/storage/docs/json_api/v1/objects/update */ Blob update(BlobInfo blobInfo, BlobTargetOption... options); /** - * Updates blob information. Original metadata are merged with metadata in the provided {@code - * blobInfo}. If the original metadata already contains a key specified in the provided {@code - * blobInfo's} metadata map, it will be replaced by the new value. Removing metadata can be done - * by setting that metadata's value to {@code null}. + * Updates the properties of the blob. This method issues an RPC request to merge the current blob + * properties with the properties in the provided {@code blobInfo}. Properties not defined in + * {@code blobInfo} will not be updated. To unset a blob property this property in {@code + * blobInfo} should be explicitly set to {@code null}. * - *

Example of adding new metadata values or updating existing ones. + *

Bucket or blob's name cannot be changed by this method. If you want to rename the blob or + * move it to a different bucket use the {@link Blob#copyTo} and {@link #delete} operations. * - *

{@code
-   * String bucketName = "my-unique-bucket";
-   * String blobName = "my-blob-name";
-   * Map newMetadata = new HashMap<>();
-   * newMetadata.put("keyToAddOrUpdate", "value");
-   * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName)
-   *     .setMetadata(newMetadata)
-   *     .build());
-   * }
+ *

Property update alters the blob metadata generation and doesn't alter the blob generation. * - *

Example of removing metadata values. + *

Example of how to update blob's user provided metadata and unset the content type: * *

{@code
-   * String bucketName = "my-unique-bucket";
-   * String blobName = "my-blob-name";
-   * Map newMetadata = new HashMap<>();
-   * newMetadata.put("keyToRemove", null);
-   * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName)
-   *     .setMetadata(newMetadata)
-   *     .build());
+   * Map metadataUpdate = new HashMap<>();
+   * metadataUpdate.put("keyToAdd", "new value");
+   * metadataUpdate.put("keyToRemove", null);
+   * BlobInfo blobUpdate = BlobInfo.newBuilder(bucketName, blobName)
+   *     .setMetadata(metadataUpdate)
+   *     .setContentType(null)
+   *     .build();
+   * Blob blob = storage.update(blobUpdate);
    * }
* + * @param blobInfo information to update * @return the updated blob * @throws StorageException upon failure + * @see https://cloud.google.com/storage/docs/json_api/v1/objects/update */ Blob update(BlobInfo blobInfo); @@ -2534,6 +2651,96 @@ Blob create( */ URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options); + /** + * Generates a URL and a map of fields that can be specified in an HTML form to submit a POST + * request. The returned map includes a signature which must be provided with the request. + * Generating a presigned POST policy requires a service account signer. If an instance of {@link + * com.google.auth.ServiceAccountSigner} was passed to {@link StorageOptions}' builder via {@code + * setCredentials(Credentials)} or the default credentials are being used and the environment + * variable {@code GOOGLE_APPLICATION_CREDENTIALS} is set, generatPresignedPostPolicyV4 will use + * that credentials to sign the URL. If the credentials passed to {@link StorageOptions} do not + * implement {@link ServiceAccountSigner} (this is the case, for instance, for Google Cloud SDK + * credentials) then {@code signUrl} will throw an {@link IllegalStateException} unless an + * implementation of {@link ServiceAccountSigner} is passed using the {@link + * PostPolicyV4Option#signWith(ServiceAccountSigner)} option. + * + *

Example of generating a presigned post policy which has the condition that only jpeg images + * can be uploaded, and applies the public read acl to each image uploaded, and making the POST + * request: + * + *

{@code
+   * PostFieldsV4 fields = PostFieldsV4.newBuilder().setAcl("public-read").build();
+   * PostConditionsV4 conditions = PostConditionsV4.newBuilder().addContentTypeCondition(ConditionV4Type.MATCHES, "image/jpeg").build();
+   *
+   * PostPolicyV4 policy = storage.generateSignedPostPolicyV4(
+   *     BlobInfo.newBuilder("my-bucket", "my-object").build(),
+   *     7, TimeUnit.DAYS, fields, conditions);
+   *
+   * HttpClient client = HttpClientBuilder.create().build();
+   * HttpPost request = new HttpPost(policy.getUrl());
+   * MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+   *
+   * for (Map.Entry entry : policy.getFields().entrySet()) {
+   *     builder.addTextBody(entry.getKey(), entry.getValue());
+   * }
+   * File file = new File("path/to/your/file/to/upload");
+   * builder.addBinaryBody("file", new FileInputStream(file), ContentType.APPLICATION_OCTET_STREAM, file.getName());
+   * request.setEntity(builder.build());
+   * client.execute(request);
+   * }
+ * + * @param blobInfo the blob uploaded in the form + * @param fields the fields specified in the form + * @param conditions which conditions every upload must satisfy + * @param duration how long until the form expires, in milliseconds + * @param options optional post policy options + * @see POST + * Object + */ + PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostFieldsV4 fields, + PostConditionsV4 conditions, + PostPolicyV4Option... options); + + /** + * Generates a presigned post policy without any conditions. Automatically creates required + * conditions. See full documentation for generateSignedPostPolicyV4( BlobInfo blobInfo, long + * duration, TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions, + * PostPolicyV4Option... options) above. + */ + PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostFieldsV4 fields, + PostPolicyV4Option... options); + + /** + * Generates a presigned post policy without any fields. Automatically creates required fields. + * See full documentation for generateSignedPostPolicyV4( BlobInfo blobInfo, long duration, + * TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions, PostPolicyV4Option... options) + * above. + */ + PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostConditionsV4 conditions, + PostPolicyV4Option... options); + + /** + * Generates a presigned post policy without any fields or conditions. Automatically creates + * required fields and conditions. See full documentation for generateSignedPostPolicyV4( BlobInfo + * blobInfo, long duration, TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions, + * PostPolicyV4Option... options) above. + */ + PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options); + /** * Gets the requested blobs. A batch request is used to perform this call. * @@ -2578,10 +2785,10 @@ Blob create( List get(Iterable blobIds); /** - * Updates the requested blobs. A batch request is used to perform this call. Original metadata - * are merged with metadata in the provided {@code BlobInfo} objects. To replace metadata instead - * you first have to unset them. Unsetting metadata can be done by setting the provided {@code - * BlobInfo} objects metadata to {@code null}. See {@link #update(BlobInfo)} for a code example. + * Updates the requested blobs. A batch request is used to perform this call. The original + * properties are merged with the properties in the provided {@code BlobInfo} objects. Unsetting a + * property can be done by setting the property of the provided {@code BlobInfo} objects to {@code + * null}. See {@link #update(BlobInfo)} for a code example. * *

Example of updating information on several blobs using a single batch request. * @@ -2604,10 +2811,10 @@ Blob create( List update(BlobInfo... blobInfos); /** - * Updates the requested blobs. A batch request is used to perform this call. Original metadata - * are merged with metadata in the provided {@code BlobInfo} objects. To replace metadata instead - * you first have to unset them. Unsetting metadata can be done by setting the provided {@code - * BlobInfo} objects metadata to {@code null}. See {@link #update(BlobInfo)} for a code example. + * Updates the requested blobs. A batch request is used to perform this call. The original + * properties are merged with the properties in the provided {@code BlobInfo} objects. Unsetting a + * property can be done by setting the property of the provided {@code BlobInfo} objects to {@code + * null}. See {@link #update(BlobInfo)} for a code example. * *

Example of updating information on several blobs using a single batch request. * @@ -3148,6 +3355,7 @@ HmacKeyMetadata updateHmacKeyState( final HmacKeyMetadata hmacKeyMetadata, final HmacKey.HmacKeyState state, UpdateHmacKeyOption... options); + /** * Gets the IAM policy for the provided bucket. * diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java index a345a31fc..5cf979878 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java @@ -20,8 +20,9 @@ import com.google.cloud.StringEnumValue; /** - * Enums for the storage classes. See https://cloud.google.com/storage/docs/storage-classes for - * details. + * Enums for the storage classes. See https://cloud.google.com/storage/docs/storage-classes + * for details. */ public final class StorageClass extends StringEnumValue { private static final long serialVersionUID = -6938125060419556331L; @@ -42,42 +43,61 @@ public StorageClass apply(String constant) { new StringEnumType(StorageClass.class, CONSTRUCTOR); /** - * Standard storage class. See: https://cloud.google.com/storage/docs/storage-classes for details + * Standard storage class. + * + * @see https://cloud.google.com/storage/docs/storage-classes#standard */ public static final StorageClass STANDARD = type.createAndRegister("STANDARD"); /** - * Nearline storage class. See: https://cloud.google.com/storage/docs/storage-classes for details + * Nearline storage class. + * + * @see https://cloud.google.com/storage/docs/storage-classes#nearline */ public static final StorageClass NEARLINE = type.createAndRegister("NEARLINE"); /** - * Coldline storage class. See: https://cloud.google.com/storage/docs/storage-classes for details + * Coldline storage class. + * + * @see https://cloud.google.com/storage/docs/storage-classes#coldline */ public static final StorageClass COLDLINE = type.createAndRegister("COLDLINE"); /** - * Archive storage class. See: https://cloud.google.com/storage/docs/storage-classes for details + * Archive storage class. + * + * @see https://cloud.google.com/storage/docs/storage-classes#archive */ public static final StorageClass ARCHIVE = type.createAndRegister("ARCHIVE"); /** - * Regional storage class. This is supported as a legacy storage class and will be deprecated in - * the future. See: https://cloud.google.com/storage/docs/storage-classes for details + * Legacy Regional storage class, use {@link #STANDARD} instead. This class will be deprecated in + * the future. + * + * @see https://cloud.google.com/storage/docs/storage-classes#legacy */ public static final StorageClass REGIONAL = type.createAndRegister("REGIONAL"); /** - * Multi-regional storage class. This is supported as a legacy storage class and will be - * deprecated in the future. See: https://cloud.google.com/storage/docs/storage-classes for - * details + * Legacy Multi-regional storage class, use {@link #STANDARD} instead. This class will be + * deprecated in the future. + * + * @see https://cloud.google.com/storage/docs/storage-classes#legacy */ public static final StorageClass MULTI_REGIONAL = type.createAndRegister("MULTI_REGIONAL"); /** - * Durable Reduced Availability storage class. This is supported as a legacy storage class and - * will be deprecated in the future. See: https://cloud.google.com/storage/docs/storage-classes - * for details + * Legacy Durable Reduced Availability storage class, use {@link #STANDARD} instead. This class + * will be deprecated in the future. + * + * @see https://cloud.google.com/storage/docs/storage-classes#legacy */ public static final StorageClass DURABLE_REDUCED_AVAILABILITY = type.createAndRegister("DURABLE_REDUCED_AVAILABILITY"); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 0f38e3827..0e24521eb 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -50,6 +50,10 @@ import com.google.cloud.Tuple; import com.google.cloud.storage.Acl.Entity; import com.google.cloud.storage.HmacKey.HmacKeyMetadata; +import com.google.cloud.storage.PostPolicyV4.ConditionV4Type; +import com.google.cloud.storage.PostPolicyV4.PostConditionsV4; +import com.google.cloud.storage.PostPolicyV4.PostFieldsV4; +import com.google.cloud.storage.PostPolicyV4.PostPolicyV4Document; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.RewriteResponse; import com.google.common.base.CharMatcher; @@ -72,12 +76,15 @@ import java.net.URI; import java.net.URL; import java.net.URLEncoder; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -733,6 +740,139 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio } } + @Override + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostFieldsV4 fields, + PostConditionsV4 conditions, + PostPolicyV4Option... options) { + EnumMap optionMap = Maps.newEnumMap(SignUrlOption.Option.class); + // Convert to a map of SignUrlOptions so we can re-use some utility methods + for (PostPolicyV4Option option : options) { + optionMap.put(SignUrlOption.Option.valueOf(option.getOption().name()), option.getValue()); + } + + optionMap.put(SignUrlOption.Option.SIGNATURE_VERSION, SignUrlOption.SignatureVersion.V4); + + ServiceAccountSigner credentials = + (ServiceAccountSigner) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED); + if (credentials == null) { + checkState( + this.getOptions().getCredentials() instanceof ServiceAccountSigner, + "Signing key was not provided and could not be derived"); + credentials = (ServiceAccountSigner) this.getOptions().getCredentials(); + } + + checkArgument( + !(optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) + && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE) + && optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)), + "Only one of VIRTUAL_HOSTED_STYLE, PATH_STYLE, or BUCKET_BOUND_HOST_NAME SignUrlOptions can be" + + " specified."); + + String bucketName = slashlessBucketNameFromBlobInfo(blobInfo); + + boolean usePathStyle = shouldUsePathStyleForSignedUrl(optionMap); + + String url; + + if (usePathStyle) { + url = STORAGE_XML_URI_SCHEME + "://" + STORAGE_XML_URI_HOST_NAME + "/" + bucketName + "/"; + } else { + url = STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + STORAGE_XML_URI_HOST_NAME + "/"; + } + + if (optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) { + url = optionMap.get(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME) + "/"; + } + + SimpleDateFormat googDateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + SimpleDateFormat yearMonthDayFormat = new SimpleDateFormat("yyyyMMdd"); + SimpleDateFormat expirationFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + googDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + yearMonthDayFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + expirationFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + long timestamp = getOptions().getClock().millisTime(); + String date = googDateFormat.format(timestamp); + String signingCredential = + credentials.getAccount() + + "/" + + yearMonthDayFormat.format(timestamp) + + "/auto/storage/goog4_request"; + + Map policyFields = new HashMap<>(); + + PostConditionsV4.Builder conditionsBuilder = conditions.toBuilder(); + + for (Map.Entry entry : fields.getFieldsMap().entrySet()) { + // Every field needs a corresponding policy condition, so add them if they're missing + conditionsBuilder.addCustomCondition( + ConditionV4Type.MATCHES, entry.getKey(), entry.getValue()); + + policyFields.put(entry.getKey(), entry.getValue()); + } + + PostConditionsV4 v4Conditions = + conditionsBuilder + .addBucketCondition(ConditionV4Type.MATCHES, blobInfo.getBucket()) + .addKeyCondition(ConditionV4Type.MATCHES, blobInfo.getName()) + .addCustomCondition(ConditionV4Type.MATCHES, "x-goog-date", date) + .addCustomCondition(ConditionV4Type.MATCHES, "x-goog-credential", signingCredential) + .addCustomCondition(ConditionV4Type.MATCHES, "x-goog-algorithm", "GOOG4-RSA-SHA256") + .build(); + PostPolicyV4Document document = + PostPolicyV4Document.of( + expirationFormat.format(timestamp + unit.toMillis(duration)), v4Conditions); + String policy = BaseEncoding.base64().encode(document.toJson().getBytes()); + String signature = + BaseEncoding.base16().encode(credentials.sign(policy.getBytes())).toLowerCase(); + + for (PostPolicyV4.ConditionV4 condition : v4Conditions.getConditions()) { + if (condition.type == ConditionV4Type.MATCHES) { + policyFields.put(condition.operand1, condition.operand2); + } + } + policyFields.put("key", blobInfo.getName()); + policyFields.put("x-goog-credential", signingCredential); + policyFields.put("x-goog-algorithm", "GOOG4-RSA-SHA256"); + policyFields.put("x-goog-date", date); + policyFields.put("x-goog-signature", signature); + policyFields.put("policy", policy); + + policyFields.remove("bucket"); + + return PostPolicyV4.of(url, policyFields); + } + + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostFieldsV4 fields, + PostPolicyV4Option... options) { + return generateSignedPostPolicyV4( + blobInfo, duration, unit, fields, PostConditionsV4.newBuilder().build(), options); + } + + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostConditionsV4 conditions, + PostPolicyV4Option... options) { + return generateSignedPostPolicyV4( + blobInfo, duration, unit, PostFieldsV4.newBuilder().build(), conditions, options); + } + + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options) { + return generateSignedPostPolicyV4( + blobInfo, duration, unit, PostFieldsV4.newBuilder().build(), options); + } + private String constructResourceUriPath( String slashlessBucketName, String escapedBlobName, diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index c677cbdb6..e211c0277 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -109,8 +109,6 @@ public HttpStorageRpc(StorageOptions options) { censusHttpModule = new CensusHttpModule(tracer, true); initializer = censusHttpModule.getHttpRequestInitializer(initializer); batchRequestInitializer = censusHttpModule.getHttpRequestInitializer(null); - HttpStorageRpcSpans.registerAllSpanNamesForCollection(); - storage = new Storage.Builder(transport, new JacksonFactory(), initializer) .setRootUrl(options.getHost()) @@ -205,7 +203,7 @@ public void submit() { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } } @@ -272,7 +270,7 @@ public Bucket create(Bucket bucket, Map options) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -310,7 +308,7 @@ public StorageObject create( throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -336,7 +334,7 @@ public Tuple> list(Map options) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -370,7 +368,7 @@ public Tuple> list(final String bucket, Map options) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -443,7 +441,7 @@ public StorageObject get(StorageObject object, Map options) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -481,7 +479,7 @@ public Bucket patch(Bucket bucket, Map options) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -510,7 +508,7 @@ public StorageObject patch(StorageObject storageObject, Map options) throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -536,7 +534,7 @@ public boolean delete(Bucket bucket, Map options) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -569,7 +567,7 @@ public boolean delete(StorageObject blob, Map options) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -606,7 +604,7 @@ public StorageObject compose( throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -634,13 +632,20 @@ public byte[] load(StorageObject from, Map options) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @Override public RpcBatch createBatch() { - return new DefaultRpcBatch(storage); + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BATCH); + Scope scope = tracer.withSpan(span); + try { + return new DefaultRpcBatch(storage); + } finally { + scope.close(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); + } } private Get createReadRequest(StorageObject from, Map options) throws IOException { @@ -679,7 +684,7 @@ public long read( throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -708,7 +713,7 @@ public Tuple read( throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -776,7 +781,7 @@ public void write( throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -832,7 +837,7 @@ public String open(StorageObject object, Map options) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -865,7 +870,7 @@ public String open(String signedURL) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -877,7 +882,7 @@ public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { return rewrite(rewriteRequest, null); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -889,7 +894,7 @@ public RewriteResponse continueRewrite(RewriteResponse previousResponse) { return rewrite(previousResponse.rewriteRequest, previousResponse.rewriteToken); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -967,7 +972,7 @@ public BucketAccessControl getAcl(String bucket, String entity, Map o throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -991,7 +996,7 @@ public boolean deleteAcl(String bucket, String entity, Map options) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1010,7 +1015,7 @@ public BucketAccessControl createAcl(BucketAccessControl acl, Map opt throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1029,7 +1034,7 @@ public BucketAccessControl patchAcl(BucketAccessControl acl, Map opti throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1049,7 +1054,7 @@ public List listAcls(String bucket, Map options) throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1068,7 +1073,7 @@ public ObjectAccessControl getDefaultAcl(String bucket, String entity) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1088,7 +1093,7 @@ public boolean deleteDefaultAcl(String bucket, String entity) { throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1103,7 +1108,7 @@ public ObjectAccessControl createDefaultAcl(ObjectAccessControl acl) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1121,7 +1126,7 @@ public ObjectAccessControl patchDefaultAcl(ObjectAccessControl acl) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1136,7 +1141,7 @@ public List listDefaultAcls(String bucket) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1159,7 +1164,7 @@ public ObjectAccessControl getAcl(String bucket, String object, Long generation, throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1183,7 +1188,7 @@ public boolean deleteAcl(String bucket, String object, Long generation, String e throw serviceException; } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1202,7 +1207,7 @@ public ObjectAccessControl createAcl(ObjectAccessControl acl) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1221,7 +1226,7 @@ public ObjectAccessControl patchAcl(ObjectAccessControl acl) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1241,7 +1246,7 @@ public List listAcls(String bucket, String object, Long gen throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1265,7 +1270,7 @@ public HmacKey createHmacKey(String serviceAccountEmail, Map options) throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1295,7 +1300,7 @@ public Tuple> listHmacKeys(Map opti throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1319,7 +1324,7 @@ public HmacKeyMetadata getHmacKey(String accessId, Map options) { throw translate(ex); } finally { scope.close(); - span.end(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } @@ -1343,7 +1348,7 @@ public HmacKeyMetadata updateHmacKey(HmacKeyMetadata hmacKeyMetadata, Map

The results of this method will then be run by JUnit's Parameterized test runner + */ + @Parameters(name = "{2}") + public static Collection testCases() throws IOException { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + InputStream credentialsStream = cl.getResourceAsStream(SERVICE_ACCOUNT_JSON_RESOURCE); + assertNotNull( + String.format("Unable to load service account json: %s", SERVICE_ACCOUNT_JSON_RESOURCE), + credentialsStream); + + InputStream dataJson = cl.getResourceAsStream(TEST_DATA_JSON_RESOURCE); + assertNotNull( + String.format("Unable to load test definition: %s", TEST_DATA_JSON_RESOURCE), dataJson); + + ServiceAccountCredentials serviceAccountCredentials = + ServiceAccountCredentials.fromStream(credentialsStream); + + InputStreamReader reader = new InputStreamReader(dataJson, Charsets.UTF_8); + TestFile.Builder testBuilder = TestFile.newBuilder(); + JsonFormat.parser().merge(reader, testBuilder); + TestFile testFile = testBuilder.build(); + + List tests = testFile.getPostPolicyV4TestsList(); + ArrayList data = new ArrayList<>(tests.size()); + for (PostPolicyV4Test test : tests) { + data.add(new Object[] {test, serviceAccountCredentials, test.getDescription()}); + } + return data; + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index 52a6acd7e..e1fb52603 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -67,6 +67,8 @@ import com.google.cloud.storage.CopyWriter; import com.google.cloud.storage.HmacKey; import com.google.cloud.storage.HttpMethod; +import com.google.cloud.storage.PostPolicyV4; +import com.google.cloud.storage.PostPolicyV4.PostFieldsV4; import com.google.cloud.storage.ServiceAccount; import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobField; @@ -98,11 +100,14 @@ import io.grpc.stub.MetadataUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.nio.ByteBuffer; +import java.nio.file.Files; import java.security.Key; import java.util.ArrayList; import java.util.Arrays; @@ -120,6 +125,11 @@ import java.util.logging.Logger; import java.util.zip.GZIPInputStream; import javax.crypto.spec.SecretKeySpec; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.junit.AfterClass; @@ -3208,4 +3218,29 @@ public void testBucketLogging() throws ExecutionException, InterruptedException RemoteStorageHelper.forceDelete(storage, loggingBucket, 5, TimeUnit.SECONDS); } } + + @Test + public void testSignedPostPolicyV4() throws Exception { + PostFieldsV4 fields = PostFieldsV4.newBuilder().setAcl("public-read").build(); + + PostPolicyV4 policy = + storage.generateSignedPostPolicyV4( + BlobInfo.newBuilder(BUCKET, "my-object").build(), 7, TimeUnit.DAYS, fields); + + HttpClient client = HttpClientBuilder.create().build(); + HttpPost request = new HttpPost(policy.getUrl()); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + for (Map.Entry entry : policy.getFields().entrySet()) { + builder.addTextBody(entry.getKey(), entry.getValue()); + } + File file = File.createTempFile("temp", "file"); + Files.write(file.toPath(), "hello world".getBytes()); + builder.addBinaryBody( + "file", new FileInputStream(file), ContentType.APPLICATION_OCTET_STREAM, file.getName()); + request.setEntity(builder.build()); + client.execute(request); + + assertEquals("hello world", new String(storage.get(BUCKET, "my-object").getContent())); + } } diff --git a/pom.xml b/pom.xml index 8ad94856f..f82ab9c4b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 1.107.0 + 1.108.0 Storage Parent https://github.com/googleapis/java-storage @@ -66,7 +66,7 @@ 1.93.4 1.9.0 4.13 - 1.4.3 + 1.4.4 1.3.2 1.18 0.24.0 @@ -82,10 +82,15 @@ com.google.http-client google-http-client-bom - 1.34.2 + 1.35.0 pom import + + com.google.code.gson + gson + 2.8.6 + com.google.cloud google-cloud-core-bom @@ -103,7 +108,7 @@ io.grpc grpc-bom - 1.27.0 + 1.29.0 pom import @@ -190,6 +195,12 @@ 3.6 test + + org.mockito + mockito-core + 2.28.2 + test + org.objenesis objenesis @@ -212,7 +223,25 @@ com.google.cloud google-cloud-conformance-tests - 0.0.9 + 0.0.10 + test + + + org.apache.httpcomponents + httpclient + 4.5.12 + test + + + org.apache.httpcomponents + httpmime + 4.5.12 + test + + + org.apache.httpcomponents + httpcore + 4.4.13 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 597e05998..723f245ac 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -29,7 +29,7 @@ com.google.cloud google-cloud-storage - 1.106.0 + 1.107.0 diff --git a/samples/pom.xml b/samples/pom.xml index 351cabdd9..04e223773 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -18,7 +18,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index b45a8cc11..edabb4eec 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -28,7 +28,7 @@ com.google.cloud google-cloud-storage - 1.106.1-SNAPSHOT + 1.107.0 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index bd7d378c7..785b6dfcd 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -30,7 +30,7 @@ com.google.cloud libraries-bom - 5.1.0 + 5.3.0 pom import diff --git a/synth.metadata b/synth.metadata index 49839559e..12985f3b7 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,15 +3,15 @@ { "git": { "name": ".", - "remote": "git@github.com:googleapis/java-storage.git", - "sha": "b5441df8d27ea8c77621da575decbf127f281897" + "remote": "https://github.com/googleapis/java-storage.git", + "sha": "389b759ae875d8c20e21a030fb02bfdf4d937a7e" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "8eff3790f88b50706a0c4b6a20b385f24e9ac4e7" + "sha": "f8a9933e5e98202b04ef427f28d1d79221190fa4" } } ] diff --git a/versions.txt b/versions.txt index b440d332e..f2fb46fb2 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-storage:1.107.0:1.107.0 \ No newline at end of file +google-cloud-storage:1.108.0:1.108.0 \ No newline at end of file