Repository: googleapis/java-storage
Pull request #1247 (2022-02-12 01:01:54)

fix: cherry pick PR#1160
Cherry Pick PR #1160 into 2.1.x



Patch:
diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml
index 85c4887d..1fe6a791 100644
--- a/google-cloud-storage/clirr-ignored-differences.xml
+++ b/google-cloud-storage/clirr-ignored-differences.xml
@@ -36,4 +36,9 @@
         <method>com.google.api.services.storage.model.StorageObject queryCompletedResumableUpload(java.lang.String, long)</method>
         <differenceType>7012</differenceType>
     </difference>
+    <difference>
+        <className>com/google/cloud/storage/BucketInfo$LifecycleRule$LifecycleAction</className>
+        <method>BucketInfo$LifecycleRule$LifecycleAction()</method>
+        <differenceType>7004</differenceType>
+    </difference>
 </differences>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index 83a836f7..2404a7c1 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -548,8 +548,13 @@ public class BucketInfo implements Serializable {
                   StorageClass.valueOf(action.getStorageClass()));
           break;
         default:
-          throw new UnsupportedOperationException(
-              "The specified lifecycle action " + action.getType() + " is not currently supported");
+          log.warning(
+              "The lifecycle action "
+                  + action.getType()
+                  + " is not supported by this version of the library. "
+                  + "Attempting to update with this rule may cause errors. Please "
+                  + "update to the latest version of google-cloud-storage.");
+          lifecycleAction = LifecycleAction.newLifecycleAction("Unknown action");
       }
 
       Rule.Condition condition = rule.getCondition();
@@ -799,13 +804,21 @@ public class BucketInfo implements Serializable {
     }
 
     /**
-     * Base class for the Action to take when a Lifecycle Condition is met. Specific Actions are
+     * Base class for the Action to take when a Lifecycle Condition is met. Supported Actions are
      * expressed as subclasses of this class, accessed by static factory methods.
      */
-    public abstract static class LifecycleAction implements Serializable {
+    public static class LifecycleAction implements Serializable {
       private static final long serialVersionUID = 5801228724709173284L;
 
-      public abstract String getActionType();
+      private final String actionType;
+
+      public LifecycleAction(String actionType) {
+        this.actionType = actionType;
+      }
+
+      public String getActionType() {
+        return actionType;
+      }
 
       @Override
       public String toString() {
@@ -830,17 +843,24 @@ public class BucketInfo implements Serializable {
           StorageClass storageClass) {
         return new SetStorageClassLifecycleAction(storageClass);
       }
+
+      /**
+       * Creates a new {@code LifecycleAction , with no specific supported action associated with it. This
+       * is only intended as a "backup" for when the library doesn't recognize the type, and should
+       * generally not be used, instead use the supported actions, and upgrade the library if necessary
+       * to get new supported actions.
+       */
+      public static LifecycleAction newLifecycleAction(String actionType) {
+        return new LifecycleAction(actionType);
+      }
     }
 
     public static class DeleteLifecycleAction extends LifecycleAction {
       public static final String TYPE = "Delete";
       private static final long serialVersionUID = -2050986302222644873L;
 
-      private DeleteLifecycleAction() {}
-
-      @Override
-      public String getActionType() {
-        return TYPE;
+      private DeleteLifecycleAction() {
+        super(TYPE);
       }
     }
 
@@ -851,14 +871,10 @@ public class BucketInfo implements Serializable {
       private final StorageClass storageClass;
 
       private SetStorageClassLifecycleAction(StorageClass storageClass) {
+        super(TYPE);
         this.storageClass = storageClass;
       }
 
-      @Override
-      public String getActionType() {
-        return TYPE;
-      }
-
       @Override
       public String toString() {
         return MoreObjects.toStringHelper(this)
@@ -1007,7 +1023,11 @@ public class BucketInfo implements Serializable {
 
     @Override
     void populateCondition(Rule.Condition condition) {
-      throw new UnsupportedOperationException();
+      log.warning(
+          "The lifecycle condition "
+              + condition
+              + " is not currently supported. Please update to the latest version of google-cloud-java."
+              + " Also, use LifecycleRule rather than the deprecated DeleteRule.");
     }
 
     private void writeObject(ObjectOutputStream out) throws IOException {
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
index e198d853..ca3e8bc3 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java
@@ -34,8 +34,8 @@ class PolicyHelper {
   static Policy convertFromApiPolicy(com.google.api.services.storage.model.Policy apiPolicy) {
     Policy.Builder policyBuilder = Policy.newBuilder();
     List<Bindings> bindings = apiPolicy.getBindings();
+    ImmutableList.Builder<Binding> coreBindings = ImmutableList.builder();
     if (null != bindings && !bindings.isEmpty()) {
-      ImmutableList.Builder<Binding> coreBindings = ImmutableList.builder();
       for (Bindings binding : bindings) {
         Binding.Builder bindingBuilder = Binding.newBuilder();
         bindingBuilder.setRole(binding.getRole());
@@ -49,10 +49,8 @@ class PolicyHelper {
         }
         coreBindings.add(bindingBuilder.build());
       }
-      policyBuilder.setBindings(coreBindings.build());
-    } else {
-      throw new IllegalStateException("Missing required bindings.");
     }
+    policyBuilder.setBindings(coreBindings.build());
     return policyBuilder.setEtag(apiPolicy.getEtag()).setVersion(apiPolicy.getVersion()).build();
   }
 
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 02bdb8dd..21185994 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
@@ -3540,6 +3540,11 @@ public interface Storage extends Service<StorageOptions> {
   /**
    * Gets the IAM policy for the provided bucket.
    *
+   * <p>It's possible for bindings to be empty and instead have permissions inherited through
+   * Project or Organization IAM Policies. To prevent corrupting policies when you update an IAM
+   * policy with {@code Storage.setIamPolicy}, the ETAG value is used to perform optimistic
+   * concurrency.
+   *
    * <p>Example of getting the IAM policy for a bucket.
    *
    * <pre>{@code
@@ -3556,6 +3561,9 @@ public interface Storage extends Service<StorageOptions> {
   /**
    * Updates the IAM policy on the specified bucket.
    *
+   * <p>To prevent corrupting policies when you update an IAM policy with {@code
+   * Storage.setIamPolicy}, the ETAG value is used to perform optimistic concurrency.
+   *
    * <p>Example of updating the IAM policy on a bucket.
    *
    * <pre>{@code


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
index c74625a1..2041de55 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
@@ -314,6 +314,10 @@ public class BucketInfoTest {
     for (DeleteRule delRule : rules) {
       assertEquals(delRule, DeleteRule.fromPb(delRule.toPb()));
     }
+    Rule unsupportedRule =
+        new Rule().setAction(new Rule.Action().setType("This action doesn't exist"));
+    DeleteRule.fromPb(
+        unsupportedRule); // if this doesn't throw an exception, unsupported rules work
   }
 
   @Test
@@ -363,6 +367,17 @@ public class BucketInfoTest {
     assertEquals(StorageClass.COLDLINE.toString(), lifecycleRule.getAction().getStorageClass());
     assertEquals(30, lifecycleRule.getCondition().getDaysSinceCustomTime().intValue());
     assertNotNull(lifecycleRule.getCondition().getCustomTimeBefore());
+
+    Rule unsupportedRule =
+        new LifecycleRule(
+                LifecycleAction.newLifecycleAction("This action type doesn't exist"),
+                LifecycleCondition.newBuilder().setAge(10).build())
+            .toPb();
+    unsupportedRule.setAction(
+        unsupportedRule.getAction().setType("This action type also doesn't exist"));
+
+    LifecycleRule.fromPb(
+        unsupportedRule); // If this doesn't throw an exception, unsupported rules are working
   }
 
   @Test
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java
index dd5c0f78..a34538c1 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java
@@ -65,11 +65,15 @@ public class PolicyHelperTest {
     assertTrue(new ApiPolicyMatcher(apiPolicy).matches(actualApiPolicy));
   }
 
-  @Test(expected = IllegalStateException.class)
+  @Test
   public void testApiPolicyWithoutBinding() {
     List<Bindings> bindings = null;
     com.google.api.services.storage.model.Policy apiPolicy =
-        new com.google.api.services.storage.model.Policy().setBindings(bindings).setEtag(ETAG);
-    PolicyHelper.convertFromApiPolicy(apiPolicy);
+        new com.google.api.services.storage.model.Policy()
+            .setBindings(bindings)
+            .setEtag(ETAG)
+            .setVersion(1);
+    Policy policy = PolicyHelper.convertFromApiPolicy(apiPolicy);
+    assertEquals(policy.getBindings().size(), 0);
   }
 }
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 73ef7e9b..befdf6a3 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
@@ -147,6 +147,7 @@ import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -2423,6 +2424,7 @@ public class ITStorageTest {
     }
   }
 
+  @Ignore // TODO(#1240): Remove once HMAC Key IT are fixed
   @Test
   public void testHmacKey() {
     ServiceAccount serviceAccount = ServiceAccount.of(System.getenv("IT_SERVICE_ACCOUNT_EMAIL"));