nacx commented on this pull request.

Many thanks @jmspring!

This looks quite good! I've added some general comments. Apart from those, 
could you paste the summary of the live tests results?

> @@ -53,6 +53,33 @@ Verify service principal
 az login -u <Application-id> -p <password> --service-principal --tenant 
<Tenant-id>
 ```
 
+## KeyVault Live Tests
+
+In order to run the KeyVault live tests, an additional parameter is needed.  
This is the `objectId` of
+the service principal being used for the tests.  One can retrieve the service 
principal `objectId` by
+doing the following using the Azure 2.0 CLI:

Is this property actually needed? Or are now the tests using the existing 
service principal supplier to automatically get the `objectId`?

> @@ -252,6 +253,15 @@ NetworkSecurityRuleApi 
> getNetworkSecurityRuleApi(@PathParam("resourcegroup") Str
    @Delegate
    GraphRBACApi getGraphRBACApi();
    
+   /**
+    * Managing your key vaults as well as the keys, secrets, and certificates 
within your key vaults can be 
+    * accomplished through a REST interface.+

[minor] Remove the trailing `+`.

> +import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class VaultProperties {
+
+   @AutoValue
+   public abstract static class Permissions {
+
+      @Nullable public abstract List<String> certificates();
+      @Nullable public abstract List<String> keys();
+      @Nullable public abstract List<String> secrets();
+      @Nullable public abstract List<String> storage();

These lists are never null since you enforce immutability and presence. Let's 
remove the `@Nullable` annotation and apply the same pattern for all other 
collections where you enforce presence.

> +   @Fallback(NullOnNotFoundOr404.class)
+   @OAuthResource("https://vault.azure.net";)
+   DeletedKeyBundle deleteKey(@EndpointParam URI vaultBaseUrl, 
@PathParam("keyName") String keyName);
+
+   @Named("key:get_versions")
+   @GET
+   @SelectJson("value")
+   @Path("/keys/{keyName}/versions")
+   @Fallback(EmptyListOnNotFoundOr404.class)
+   @OAuthResource("https://vault.azure.net";)
+   List<Key> getKeyVersions(@EndpointParam URI vaultBaseUrl, 
@PathParam("keyName") String keyName);
+
+   @Named("key:update")
+   @PATCH
+   @MapBinder(BindToJsonPayload.class)
+   @Path("/keys/{keyName}{keyVersion}")

Shouldn't this be `{keyName}/{keyVersion}`?

> + * 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 org.jclouds.azurecompute.arm.functions;
+import com.google.common.base.Function;
+import org.jclouds.http.HttpResponse;
+
+import javax.inject.Singleton;
+
+import static org.jclouds.http.HttpUtils.releasePayload;
+/**
+ * Parses an http response code from http responser
+ */
+@Singleton
+public class TrueOn20X implements Function<HttpResponse, Boolean> {

Is this class actually used?

> +import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+
+import javax.inject.Inject;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
+
+@Test(groups = "live", testName = "VaultApiLiveTest")
+public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
+   @Inject private Supplier<ServicePrincipal> svcPrincipal;

Injection does not work in tests. Remove this and get it from the injector in 
the base test class if needed.

> +   private static String cryptoText = "R29sZCUyNTIxJTJCR29sZCUyNTIxJTJCR2" +
+           "9sZCUyQmZyb20lMkJ0aGUlMkJBbWVyaWNhbiUyQlJpdmVyJTI1MjE";
+   private static String cryptoAlgorithm = "RSA-OAEP";
+   private static String hashToSign = 
"FvabKT6qGwpml59iHUJ72DZ4XyJcJ8bgpgFA4_8JFmM";
+   private static String signatureAlgorithm = "RS256";
+   private static String contentEncryptionKey = 
"YxzoHR65aFwD2_IOiZ5rD08jMSALA1y7b_yYW0G3hyI";
+
+   @BeforeClass
+   @Override
+   public void setup() {
+      super.setup();
+      createTestResourceGroup();
+      vaultName = String.format("kv%s", 
this.getClass().getSimpleName().toLowerCase());
+   }
+
+   @AfterClass

`alwaysRun = true` to cleanup stuff even if tests fail.

> +            return;
+         }
+      }
+
+      DeletedVault deletedVault = api().getDeletedVault(LOCATION, vaultName);
+      if (deletedVault != null) {
+         api().purgeVault(LOCATION, vaultName);
+         waitForOperation("vault", "purge", vaultName);
+      }
+   }
+
+   // Note:  Operations on a KeyVault that supports soft delete can take time
+   // to settle.  Thus for tests where appropriate, a wait operation with a
+   // one minute timeout will be used to make sure things are in the desired
+   // state before proceeding to the next test.
+   private boolean waitForOperation(String itemType, String operation, String 
entityName) {

jclouds already provides clean means to actively wait for things, and allows to 
configure the timeouts, etc, via properties. Please use the `Predicates2.retry` 
helper instead of manually doing this.
This logic might be useful for users too, so I'd strongly recommend to copy how 
[we configure active waits for Azure 
ARM](https://github.com/jclouds/jclouds-labs/blob/master/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java#L145-L385)
 and apply the same pattern here:

1. Refactor each conditional in this method into its own `Predicate` class.
2. Put all those predicates in an `AzurePredicatesModule` (or similar), and 
consider moving the ones that already exist (linked above) to that class.
3. For each defined predicate class, add a `@Provides` method that exposes the 
predicate encapsulated with the `retry` helper. Consider using the standard 
jclouds timeout properties or just create Azure-specific properties to 
configure them if that makes sense, but follow the same pattern.
4. [Initialize the predicates in the base test 
class](https://github.com/jclouds/jclouds-labs/blob/master/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/internal/BaseAzureComputeApiLiveTest.java#L106-L112)
 so you can easily use them in the tests.

> +            if (i == 0) {
+               break;
+            }
+            try {
+               Thread.sleep(6000);
+            } catch (java.lang.InterruptedException ie) {
+               break;
+            }
+         }
+      }
+
+      return done;
+   }
+
+   @Test
+   @Inject

Remove all `@Inject` annotations from tests.

> +                              ),
+                              ImmutableList.<String>of()
+                      ))))
+              .build(),
+              null);
+      vaultUri = vault.properties().vaultUri();
+      assertTrue(!vault.name().isEmpty());
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testGet() {
+      Vault vaultFound = api().getVault(vaultName);
+      assertTrue(!vaultFound.name().isEmpty());
+   }
+
+   @Test(dependsOnMethods = "testGet")

Better depend on the `testCreate` to maximize the chances of this test being 
run.

> +                                      "Backup",
+                                      "Restore",
+                                      "Purge"
+                              ),
+                              ImmutableList.<String>of()
+                      ))))
+              .build(),
+              null);
+      vaultUri = vault.properties().vaultUri();
+      assertTrue(!vault.name().isEmpty());
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testGet() {
+      Vault vaultFound = api().getVault(vaultName);
+      assertTrue(!vaultFound.name().isEmpty());

Better `assertNotNull(vaultFound)` to avoid an unexpected NPE?

> +      vaultUri = vault.properties().vaultUri();
+      assertTrue(!vault.name().isEmpty());
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testGet() {
+      Vault vaultFound = api().getVault(vaultName);
+      assertTrue(!vaultFound.name().isEmpty());
+   }
+
+   @Test(dependsOnMethods = "testGet")
+   public void testList() {
+      for (Vault vault : api().listVaults()) {
+         assertNotNull(vault.name());
+         VaultProperties props = vault.properties();
+         assertNotNull(props);

If properties are not nullable there is no need to check this. Deserialization 
will already fail.

> +
+      Map<String, String> tags = new HashMap<String, String>();
+      tags.put("purpose", "testing again");
+      KeyBundle updatedKey = api().updateKey(vaultUri, KEY_NAME, "/" + 
version, null, null, tags);
+      assertNotNull(updatedKey);
+
+      keys = api().getKeyVersions(vaultUri, KEY_NAME);
+      assertNotNull(keys);
+      boolean found = false;
+      for (Key k : keys) {
+         if (k.kid().contains(version)) {
+            key = k;
+            found = true;
+            break;
+         }
+      }

style minor:
```java
assertTrue(Iterables.anyMatch(keys, new Predicate<Key>() {
   @Override boolean apply(Key input) {
      return input.kid().contains(version);
   }
}));
```

> +      assertEquals(certBundle.tags().size(), 1);
+   }
+
+   @Test(dependsOnMethods = "testUpdateCertificate")
+   public void testUpdateCertificateVersion() {
+      // create a new version of the certificate
+      /*
+       * XXX -- update using version complains about needing policy (required 
input), yet
+       * passing in the same policy results in the error:
+       *
+       * Policy cannot be updated with a specific version of a certificate
+       *
+       * Will uncomment/fix once this issue is resolved.
+       *
+       */
+      assertTrue(true);

Better throw a SkipException to see the reason why this is not executed in the 
test output. this will also serve as a reminder for us that this needs to be 
implemented.

> +           
> "A6RII-HF2BkBcVa9KcAX3_di4KQE70PXgHf-dlz_RgLOJILeG50wzFeBFCLsjEEPp3itmoai" +
+           
"E6vfDidCRm5At8Vjka0G-N_afwkIijfQZLT0VaXvL39cIJE2QN3HJPZM8YPUlkFlYnY4GIRy" +
+           
"RWSBpK_KYuVufzUGtDi6Sh8pUa67ppa7DHVZlixlmnVqI3Oeg6XUvMqbFFqVSrcNbRQDwVGL" +
+           "3cUtK-KB1PfKg";
+   private static String keySignedData = 
"uO0r4P1cB-fKsDZ8cj5ahiNw8Tdsudt5zLCeEKOt29" +
+           
"LAlPDpeGx9Q1SOFNaR7JlRYVelxsohdzvydwX8ao6MLnqlpdEj0Xt5Aadp-kN84AXW238gab" +
+           
"S1AUyiWILCmdsBFeRU4wTRSxz2qGS_0ztHkaNln32P_9GJC72ZRlgZoVA4C_fowZolUoCWGj" +
+           
"4V7fAzcSoiNYipWP0HkFe3xmuz-cSQg3CCAs-MclHHfMeSagLJZZQ9bpl5LIr-Ik89bNtqEq" +
+           
"yP7Jb_fCgHajAx2lUFcRZhSIKuCfrLPMl6wzejQ2rQXX-ixEkDa73dYaPIrVW4IL3iC0Ufxn" +
+           "fxYffHJ7QCRw";
+   private static String keyWrappedData = 
"1jcTlu3KJNDBYydhaH9POWOo0tAPGkpsZVizCkHpC" +
+           
"3g_9Kg91Q3HKK-rfZynn5W5nVPM-SVFHA3JTankcXX8gx8GycwUh4pMoyil_DV35m2QjyuiT" +
+           
"ln83OJXw-nMvRXyKdVfF7nyRcs256kW7gthAOsYUVBrfFS7DFFxsXqLNREsA8j85IqIXIm8p" +
+           
"AB3C9uvl1I7SQhLvrwZZXXqjeCWMfseVJwWgsQFyyqH2P0f3-xnngV7cvik2k3Elrk3G_2Cu" +
+           
"JCozIIrANg9zG9Z8DrwSNNm9YooxWkSu0ZeDLOJ0bMdhcPGGm5OvKz3oZqX-39yv5klNlCRb" +
+           "r0q7gqmI0x25w";

Consider moving all these huge strings and the ones in the live test to files 
in `src/test/resources` for better readability.

> +           
> "4V7fAzcSoiNYipWP0HkFe3xmuz-cSQg3CCAs-MclHHfMeSagLJZZQ9bpl5LIr-Ik89bNtqEq" +
+           
"yP7Jb_fCgHajAx2lUFcRZhSIKuCfrLPMl6wzejQ2rQXX-ixEkDa73dYaPIrVW4IL3iC0Ufxn" +
+           "fxYffHJ7QCRw";
+   private static String keyWrappedData = 
"1jcTlu3KJNDBYydhaH9POWOo0tAPGkpsZVizCkHpC" +
+           
"3g_9Kg91Q3HKK-rfZynn5W5nVPM-SVFHA3JTankcXX8gx8GycwUh4pMoyil_DV35m2QjyuiT" +
+           
"ln83OJXw-nMvRXyKdVfF7nyRcs256kW7gthAOsYUVBrfFS7DFFxsXqLNREsA8j85IqIXIm8p" +
+           
"AB3C9uvl1I7SQhLvrwZZXXqjeCWMfseVJwWgsQFyyqH2P0f3-xnngV7cvik2k3Elrk3G_2Cu" +
+           
"JCozIIrANg9zG9Z8DrwSNNm9YooxWkSu0ZeDLOJ0bMdhcPGGm5OvKz3oZqX-39yv5klNlCRb" +
+           "r0q7gqmI0x25w";
+
+   @BeforeMethod
+   public void start() throws IOException {
+      super.start();
+      try {
+         vaultUri = server.getUrl("").toURI();
+      } catch (URISyntaxException use) {

Better add the exception to the method signature instead of silently catching 
it? This way if it is thrown tests will be skipped instead of run with a `null` 
URI.

> +                                      "Delete",
+                                      "Recover",
+                                      "Backup",
+                                      "Restore",
+                                      "Purge"
+                              ),
+                              ImmutableList.<String>of()
+                      ))))
+              .build(),
+              null);
+
+      String path = String.format(
+              
"/subscriptions/%s/resourcegroups/%s/providers/Microsoft.KeyVault/vaults/%s?%s",
+              subscriptionId, resourceGroup, vaultName, apiVersion
+      );
+      assertSent(server, "PUT", path);

I know this can be kind of a pain, but we need to assert the sent payload too 
for all requests that have a body. This *really* helps future maintenance of 
the different APIs.

> +                      ))))
+              .build(),
+              null);
+
+      String path = String.format(
+              
"/subscriptions/%s/resourcegroups/%s/providers/Microsoft.KeyVault/vaults/%s?%s",
+              subscriptionId, resourceGroup, vaultName, apiVersion
+      );
+      assertSent(server, "PUT", path);
+
+      assertNotNull(vault);
+      assertNotNull(vault.properties().vaultUri());
+      assertTrue(!vault.name().isEmpty());
+   }
+
+   public void createVaultReturns404() throws InterruptedException {

There is no need to test the 404 behavior if there is no custom `@Fallback` 
defined, so you can remove all the 404 tests for those methods that do not have 
fallbacks. The default behavior (throwing the exception) is already tested in 
jclouds-core.

> +              
> "/subscriptions/%s/providers/Microsoft.KeyVault/locations/%s/deletedVaults/%s?%s",
+              subscriptionId, location, vaultName, apiVersion
+      );
+      assertSent(server, "GET", path);
+
+      assertNull(vault);
+   }
+
+
+   // Key mock tests
+   public void listKeys() throws InterruptedException {
+      server.enqueue(jsonResponse("/vaultlistkeys.json").setResponseCode(200));
+      final VaultApi vaultApi = api.getVaultApi(resourceGroup);
+      List<Key> keys = vaultApi.listKeys(vaultUri);
+
+      String path = String.format("/keys?%s", apiVersion);

Same comment about skipping the test.

> @@ -99,6 +100,7 @@ public void setup() {
       vaultResourceGroup = 
System.getProperty("test.azurecompute-arm.vault.resource.group");
       vaultName = System.getProperty("test.azurecompute-arm.vault.name");
       vaultCertificateUrl = 
System.getProperty("test.azurecompute-arm.vault.certificate.url");
+      tenantId = System.getProperty("test.azurecompute-arm.tenantId");

Change to: `tenantId = injector.getInstance(Key.get(String.class, 
Tenant.class));`

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/jclouds/jclouds-labs/pull/416#pullrequestreview-80448092

Reply via email to