Skip to content

Commit

Permalink
mitigate bug related to dot and flatten from azure-client-runtime (#1380
Browse files Browse the repository at this point in the history
)

* test case for TagsPatchResource serialization, which fails

* fluent API for tags update
  • Loading branch information
weidongxu-microsoft authored Mar 29, 2021
1 parent 044a7fe commit 90f932d
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.azure.management.resources;

import com.microsoft.azure.management.resources.fluentcore.arm.models.HasManager;
import com.microsoft.azure.management.resources.fluentcore.arm.models.Resource;
import com.microsoft.azure.management.resources.implementation.ResourceManager;
import rx.Observable;

import java.util.Map;

/**
* Entry point to tag management API.
*/
public interface TagOperations extends HasManager<ResourceManager> {

/**
* Updates the tags of the Azure resource.
*
* @param resource the Azure resource to have its tags updated
* @param tags the tags
* @return the resource with updated tags
*/
TagResource updateTags(Resource resource, Map<String, String> tags);

/**
* Updates the tags of the Azure resource.
*
* @param resourceId the ID of the Azure resource to have its tags updated
* @param tags the tags
* @return the resource with updated tags
*/
TagResource updateTags(String resourceId, Map<String, String> tags);

/**
* Updates the tags of the Azure resource.
*
* @param resource the Azure resource to have its tags updated
* @param tags the tags
* @return the resource with updated tags
*/
Observable<TagResource> updateTagsAsync(Resource resource, Map<String, String> tags);

/**
* Updates the tags of the Azure resource.
*
* @param resourceId the ID of the Azure resource to have its tags updated
* @param tags the tags
* @return the resource with updated tags
*/
Observable<TagResource> updateTagsAsync(String resourceId, Map<String, String> tags);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.azure.management.resources;


import com.microsoft.azure.management.resources.fluentcore.arm.models.HasId;
import com.microsoft.azure.management.resources.fluentcore.arm.models.HasName;
import com.microsoft.azure.management.resources.fluentcore.model.HasInner;
import com.microsoft.azure.management.resources.implementation.TagsResourceInner;

import java.util.Map;

/**
* An immutable client-side representation of an Azure resource with tags.
*/
public interface TagResource extends
HasId, HasName, HasInner<TagsResourceInner> {

/**
* @return the type of the resource
*/
String type();

/**
* @return the tags for the resource
*/
Map<String, String> tags();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.microsoft.azure.management.resources.Providers;
import com.microsoft.azure.management.resources.ResourceGroups;
import com.microsoft.azure.management.resources.Subscriptions;
import com.microsoft.azure.management.resources.TagOperations;
import com.microsoft.azure.management.resources.Tenants;
import com.microsoft.azure.management.resources.fluentcore.arm.AzureConfigurable;
import com.microsoft.azure.management.resources.fluentcore.arm.implementation.AzureConfigurableImpl;
Expand Down Expand Up @@ -46,6 +47,7 @@ public final class ResourceManager extends ManagerBase implements HasInner<Resou
private PolicyAssignments policyAssignments;
private Subscriptions subscriptions;
private Tenants tenants;
private TagOperations tagOperations;

/**
* Creates an instance of ResourceManager that exposes resource management API entry points.
Expand Down Expand Up @@ -265,6 +267,16 @@ public PolicyAssignments policyAssignments() {
return policyAssignments;
}

/**
* @return the tag management API entry point
*/
public TagOperations tagOperations() {
if (tagOperations == null) {
tagOperations = new TagOperationsImpl(this);
}
return tagOperations;
}

@Override
public ResourceManagementClientImpl inner() {
return this.resourceManagementClient;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.azure.management.resources.implementation;

import com.microsoft.azure.management.resources.TagOperations;
import com.microsoft.azure.management.resources.TagResource;
import com.microsoft.azure.management.resources.Tags;
import com.microsoft.azure.management.resources.TagsPatchOperation;
import com.microsoft.azure.management.resources.TagsPatchResource;
import com.microsoft.azure.management.resources.fluentcore.arm.models.Resource;
import rx.Observable;
import rx.functions.Func1;

import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

class TagOperationsImpl implements TagOperations {

private final ResourceManager myManager;

TagOperationsImpl(ResourceManager resourceManager) {
this.myManager = resourceManager;
}

@Override
public TagResource updateTags(Resource resource, Map<String, String> tags) {
return this.updateTagsAsync(resource, tags).toBlocking().last();
}

@Override
public TagResource updateTags(String resourceId, Map<String, String> tags) {
return this.updateTagsAsync(resourceId, tags).toBlocking().last();
}

@Override
public Observable<TagResource> updateTagsAsync(Resource resource, Map<String, String> tags) {
return this.updateTagsAsync(Objects.requireNonNull(resource).id(), tags);
}

@Override
public Observable<TagResource> updateTagsAsync(String resourceId, Map<String, String> tags) {
TagsPatchResource parameters = new TagsPatchResource()
.withOperation(TagsPatchOperation.REPLACE)
.withProperties(new Tags().withTags(new TreeMap<>(tags)));
return this.manager().inner().tagOperations()
.updateAtScopeAsync(resourceId, parameters)
.map(new Func1<TagsResourceInner, TagResource>() {
@Override
public TagResource call(TagsResourceInner inner) {
return new TagResourceImpl(inner);
}
});
}

@Override
public ResourceManager manager() {
return this.myManager;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.azure.management.resources.implementation;

import com.microsoft.azure.management.resources.TagResource;

import java.util.Collections;
import java.util.Map;

final class TagResourceImpl implements TagResource {

private final TagsResourceInner innerObject;

TagResourceImpl(TagsResourceInner inner) {
this.innerObject = inner;
}

@Override
public TagsResourceInner inner() {
return this.innerObject;
}

@Override
public String id() {
return this.inner().id();
}

@Override
public String name() {
return this.inner().name();
}

@Override
public String type() {
return this.inner().type();
}

@Override
public Map<String, String> tags() {
return this.inner().properties() == null
? Collections.<String, String>emptyMap()
: Collections.unmodifiableMap(this.inner().properties().tags());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.microsoft.azure.management.resources;

import com.microsoft.azure.management.resources.implementation.TypeSerializationTests;
import org.junit.Assert;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class TagsTests extends ResourceManagerTestBase {

@Test
public void canUpdateTag() throws Exception {
// assume there is a resource
GenericResource resource = resourceClient.genericResources().list().iterator().next();

Map<String, String> originalTags = resource.tags();
if (originalTags == null) {
originalTags = new HashMap<>();
}

TagResource updatedTags = resourceClient.tagOperations().updateTags(resource, new TypeSerializationTests.Map1<>("tag.1", "value.1"));
Assert.assertEquals(1, updatedTags.tags().size());
Assert.assertTrue(updatedTags.tags().containsKey("tag.1"));
Assert.assertEquals("value.1", updatedTags.tags().get("tag.1"));

updatedTags = resourceClient.tagOperations().updateTags(resource, new HashMap<String, String>());
Assert.assertEquals(0, updatedTags.tags().size());

updatedTags = resourceClient.tagOperations().updateTags(resource, originalTags);
Assert.assertEquals(originalTags, updatedTags.tags());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,85 @@
package com.microsoft.azure.management.resources.implementation;

import com.microsoft.azure.management.resources.DeploymentProperties;
import com.microsoft.azure.management.resources.Tags;
import com.microsoft.azure.management.resources.TagsPatchOperation;
import com.microsoft.azure.management.resources.TagsPatchResource;
import com.microsoft.azure.serializer.AzureJacksonAdapter;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class TypeSerializationTests {

public static final class Map1<K,V> extends AbstractMap<K,V> {
private final K k0;
private final V v0;

public Map1(K k0, V v0) {
this.k0 = Objects.requireNonNull(k0);
this.v0 = Objects.requireNonNull(v0);
}

@Override
public Set<Entry<K,V>> entrySet() {
Entry<K,V> entry = new AbstractMap.SimpleEntry<>(k0, v0);
return new HashSet<>(Collections.singletonList(entry));
}

@Override
public V get(Object o) {
return o.equals(k0) ? v0 : null;
}

@Override
public boolean containsKey(Object o) {
return o.equals(k0);
}

@Override
public boolean containsValue(Object o) {
return o.equals(v0); // implicit nullcheck of o
}

@Override
public int size() {
return 1;
}

@Override
public boolean isEmpty() {
return false;
}

@Override
public int hashCode() {
return k0.hashCode() ^ v0.hashCode();
}
}

@Test
@Ignore("fails if Map is final")
public void testTagsPatchResourceSerialization() throws Exception {
Map<String, String> tags = new Map1<>("tag.1", "value.1");

TagsPatchResource tagsPatchResource = new TagsPatchResource()
.withOperation(TagsPatchOperation.REPLACE)
.withProperties(new Tags().withTags(tags));

AzureJacksonAdapter serializerAdapter = new AzureJacksonAdapter();
String tagsPatchResourceJson = serializerAdapter.serialize(tagsPatchResource);
Assert.assertTrue(tagsPatchResourceJson.contains("tag.1"));
}

@Test
@Ignore("To fix later as swagger changes on DeploymentExtendedInner")
public void testDeploymentSerialization() throws Exception {
final String templateJson = "{ \"/subscriptions/<redacted>/resourceGroups/<redacted>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<redacted>\": {} }";

Expand All @@ -27,11 +97,15 @@ public void testDeploymentSerialization() throws Exception {
Assert.assertTrue(deploymentJson.contains("Microsoft.ManagedIdentity"));
}

private static DeploymentInner createRequestFromInner(DeploymentImpl deployment) {
private static DeploymentInner createRequestFromInner(DeploymentImpl deployment) throws NoSuchFieldException, IllegalAccessException {
Field field = DeploymentImpl.class.getDeclaredField("deploymentCreateUpdateParameters");
field.setAccessible(true);
DeploymentInner implInner = (DeploymentInner) field.get(deployment);

DeploymentInner inner = new DeploymentInner()
.withProperties(new DeploymentProperties());
inner.properties().withMode(deployment.mode());
inner.properties().withTemplate("{\"$schema\":\"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\"contentVersion\":\"1.0.0.0\",\"parameters\":{\"vnetName\":{\"type\":\"string\",\"defaultValue\":\"VNet2\",\"metadata\":{\"description\":\"VNet name\"}},\"vnetAddressPrefix\":{\"type\":\"string\",\"defaultValue\":\"10.0.0.0/16\",\"metadata\":{\"description\":\"Address prefix\"}},\"subnet1Prefix\":{\"type\":\"string\",\"defaultValue\":\"10.0.0.0/24\",\"metadata\":{\"description\":\"Subnet 1 Prefix\"}},\"subnet1Name\":{\"type\":\"string\",\"defaultValue\":\"Subnet1\",\"metadata\":{\"description\":\"Subnet 1 Name\"}},\"subnet2Prefix\":{\"type\":\"string\",\"defaultValue\":\"10.0.1.0/24\",\"metadata\":{\"description\":\"Subnet 2 Prefix\"}},\"subnet2Name\":{\"type\":\"string\",\"defaultValue\":\"Subnet222\",\"metadata\":{\"description\":\"Subnet 2 Name\"}}},\"variables\":{\"apiVersion\":\"2015-06-15\"},\"resources\":[{\"apiVersion\":\"[variables('apiVersion')]\",\"type\":\"Microsoft.Network/virtualNetworks\",\"name\":\"[parameters('vnetName')]\",\"location\":\"[resourceGroup().location]\",\"properties\":{\"addressSpace\":{\"addressPrefixes\":[\"[parameters('vnetAddressPrefix')]\"]},\"subnets\":[{\"name\":\"[parameters('subnet1Name')]\",\"properties\":{\"addressPrefix\":\"[parameters('subnet1Prefix')]\"}},{\"name\":\"[parameters('subnet2Name')]\",\"properties\":{\"addressPrefix\":\"[parameters('subnet2Prefix')]\"}}]}}]}");
inner.properties().withTemplate(implInner.properties().template());
inner.properties().withTemplateLink(deployment.templateLink());
inner.properties().withParameters(deployment.parameters());
inner.properties().withParametersLink(deployment.parametersLink());
Expand Down
Loading

0 comments on commit 90f932d

Please sign in to comment.