Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow duplicate property assignment and support existing resources #42416

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public abstract partial class Resource : System.ClientModel.Primitives.IPersista
{
protected Resource(Azure.Provisioning.IConstruct scope, Azure.Provisioning.Resource? parent, string resourceName, Azure.Core.ResourceType resourceType, string version, System.Func<string, object> createProperties) { }
public Azure.Core.ResourceIdentifier Id { get { throw null; } }
public bool IsExisting { get { throw null; } }
public string Name { get { throw null; } }
public Azure.Provisioning.Resource? Parent { get { throw null; } }
public Azure.Provisioning.IConstruct Scope { get { throw null; } }
Expand Down Expand Up @@ -199,6 +200,7 @@ public partial class KeyVault : Azure.Provisioning.Resource<Azure.ResourceManage
public KeyVault(Azure.Provisioning.IConstruct scope, Azure.Provisioning.ResourceManager.ResourceGroup? parent = null, string name = "kv", string version = "2023-02-01", Azure.Core.AzureLocation? location = default(Azure.Core.AzureLocation?)) : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultData>)) { }
public void AddAccessPolicy(Azure.Provisioning.Output output) { }
protected override Azure.Provisioning.Resource? FindParentInScope(Azure.Provisioning.IConstruct scope) { throw null; }
public static Azure.Provisioning.KeyVaults.KeyVault FromExisting(Azure.Provisioning.IConstruct scope, string name, Azure.Provisioning.ResourceManager.ResourceGroup? parent = null) { throw null; }
protected override string GetAzureName(Azure.Provisioning.IConstruct scope, string resourceName) { throw null; }
}
public static partial class KeyVaultExtensions
Expand All @@ -208,8 +210,8 @@ public static partial class KeyVaultExtensions
}
public partial class KeyVaultSecret : Azure.Provisioning.Resource<Azure.ResourceManager.KeyVault.KeyVaultSecretData>
{
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, string name, Azure.Provisioning.ConnectionString connectionString, string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, string name = "kvs", string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, Azure.Provisioning.KeyVaults.KeyVault? parent = null, string name = "kvs", string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, string name, Azure.Provisioning.ConnectionString connectionString, Azure.Provisioning.KeyVaults.KeyVault? parent = null, string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
protected override Azure.Provisioning.Resource? FindParentInScope(Azure.Provisioning.IConstruct scope) { throw null; }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public abstract partial class Resource : System.ClientModel.Primitives.IPersista
{
protected Resource(Azure.Provisioning.IConstruct scope, Azure.Provisioning.Resource? parent, string resourceName, Azure.Core.ResourceType resourceType, string version, System.Func<string, object> createProperties) { }
public Azure.Core.ResourceIdentifier Id { get { throw null; } }
public bool IsExisting { get { throw null; } }
public string Name { get { throw null; } }
public Azure.Provisioning.Resource? Parent { get { throw null; } }
public Azure.Provisioning.IConstruct Scope { get { throw null; } }
Expand Down Expand Up @@ -199,6 +200,7 @@ public partial class KeyVault : Azure.Provisioning.Resource<Azure.ResourceManage
public KeyVault(Azure.Provisioning.IConstruct scope, Azure.Provisioning.ResourceManager.ResourceGroup? parent = null, string name = "kv", string version = "2023-02-01", Azure.Core.AzureLocation? location = default(Azure.Core.AzureLocation?)) : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultData>)) { }
public void AddAccessPolicy(Azure.Provisioning.Output output) { }
protected override Azure.Provisioning.Resource? FindParentInScope(Azure.Provisioning.IConstruct scope) { throw null; }
public static Azure.Provisioning.KeyVaults.KeyVault FromExisting(Azure.Provisioning.IConstruct scope, string name, Azure.Provisioning.ResourceManager.ResourceGroup? parent = null) { throw null; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets have a follow up to refine this api so we don't need to take in scope.

protected override string GetAzureName(Azure.Provisioning.IConstruct scope, string resourceName) { throw null; }
}
public static partial class KeyVaultExtensions
Expand All @@ -208,8 +210,8 @@ public static partial class KeyVaultExtensions
}
public partial class KeyVaultSecret : Azure.Provisioning.Resource<Azure.ResourceManager.KeyVault.KeyVaultSecretData>
{
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, string name, Azure.Provisioning.ConnectionString connectionString, string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, string name = "kvs", string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, Azure.Provisioning.KeyVaults.KeyVault? parent = null, string name = "kvs", string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
public KeyVaultSecret(Azure.Provisioning.IConstruct scope, string name, Azure.Provisioning.ConnectionString connectionString, Azure.Provisioning.KeyVaults.KeyVault? parent = null, string version = "2023-02-01") : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, Azure.ResourceManager.KeyVault.KeyVaultSecretData>)) { }
protected override Azure.Provisioning.Resource? FindParentInScope(Azure.Provisioning.IConstruct scope) { throw null; }
}
}
Expand Down
2 changes: 1 addition & 1 deletion sdk/provisioning/Azure.Provisioning/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "net",
"TagPrefix": "net/provisioning/Azure.Provisioning",
"Tag": "net/provisioning/Azure.Provisioning_be6662616d"
"Tag": "net/provisioning/Azure.Provisioning_15c6f021fe"
}
10 changes: 7 additions & 3 deletions sdk/provisioning/Azure.Provisioning/src/ModuleConstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,15 @@ public BinaryData SerializeModule()

foreach (var resource in GetResources(false))
{
if (resource.IsExisting)
{
continue;
}
if (resource is Tenant)
{
continue;
}
if (resource is ResourceGroup && resource.Id.Name == "resourceGroup()")
if (resource is ResourceGroup && resource.Id.Name == ResourceGroup.ResourceGroupFunction)
{
continue;
}
Expand All @@ -139,11 +143,11 @@ public BinaryData SerializeModule()

private void WriteExistingResources(MemoryStream stream)
{
foreach (var resource in GetExistingResources(false))
foreach (var resource in GetResources(false).Where(r => r.IsExisting))
{
stream.WriteLine();
stream.WriteLine($"resource {resource.Name} '{resource.Id.ResourceType}@{resource.Version}' existing = {{");
stream.WriteLine($" name: '{resource.Name}'");
stream.WriteLine($" name: '{resource.Id.Name}'");
stream.WriteLine($"}}");
}
}
Expand Down
26 changes: 15 additions & 11 deletions sdk/provisioning/Azure.Provisioning/src/ModuleInfrastructure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,25 +112,29 @@ private void BuildModuleConstructs(Resource resource, Dictionary<Resource, List<

if (parentScope != null)
{
foreach (var parameter in resource.Parameters)
foreach (var parameter in resource.Scope.GetParameters(false))
{
parentScope.AddParameter(parameter);
}

foreach (var typeDictPair in resource.ParameterOverrides)
foreach (var typeDictPair in resource.PropertyOverrides)
{
// ToList to avoid modifying the collection while iterating
foreach (var propertyParameterPair in typeDictPair.Value.ToList())
{
var parameterToCopy = propertyParameterPair.Value;
resource.ParameterOverrides[typeDictPair.Key][propertyParameterPair.Key] = new Parameter(
parameterToCopy.Name,
parameterToCopy.Description,
parameterToCopy.DefaultValue,
parameterToCopy.IsSecure,
var parameterToCopy = propertyParameterPair.Value.Parameter;
if (parameterToCopy == null)
{
continue;
}
resource.PropertyOverrides[typeDictPair.Key][propertyParameterPair.Key] = new PropertyOverride(parameter: new Parameter(
parameterToCopy.Value.Name,
parameterToCopy.Value.Description,
parameterToCopy.Value.DefaultValue,
parameterToCopy.Value.IsSecure,
parentScope,
parameterToCopy.Value,
parameterToCopy.Output);
parameterToCopy.Value.Value,
parameterToCopy.Value.Output));
}
}
}
Expand Down Expand Up @@ -164,7 +168,7 @@ private bool NeedsModuleConstruct(Resource resource, Dictionary<Resource, List<R
}
}

if (resource is ResourceGroup)
if (resource is ResourceGroup && !resource.IsExisting)
{
// TODO add policy support
return resourceTree[resource].Count > 0;
Expand Down
16 changes: 16 additions & 0 deletions sdk/provisioning/Azure.Provisioning/src/PropertyOverride.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Provisioning
{
internal struct PropertyOverride
{
public string? PropertyValue { get; }
public Parameter? Parameter { get; }
internal PropertyOverride(string? propertyValue = default, Parameter? parameter = default)
{
PropertyValue = propertyValue;
Parameter = parameter;
}
}
}
42 changes: 21 additions & 21 deletions sdk/provisioning/Azure.Provisioning/src/Resource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ namespace Azure.Provisioning
public abstract class Resource : IPersistableModel<Resource>
#pragma warning restore AZC0012 // Avoid single word type names
{
internal Dictionary<object, Dictionary<string, Parameter>> ParameterOverrides { get; }

private Dictionary<object, Dictionary<string, string>> PropertyOverrides { get; }
internal Dictionary<object, Dictionary<string, PropertyOverride>> PropertyOverrides { get; }

private IList<Resource> Dependencies { get; }

Expand Down Expand Up @@ -74,20 +72,26 @@ internal void AddDependency(Resource resource)
/// <param name="createProperties">Lambda to create the ARM properties.</param>
/// <exception cref="ArgumentNullException">If <paramref name="scope"/> is null.</exception>
protected Resource(IConstruct scope, Resource? parent, string resourceName, ResourceType resourceType, string version, Func<string, object> createProperties)
: this(scope, parent, resourceName, resourceType, version, createProperties, false)
{
}

internal Resource(IConstruct scope, Resource? parent, string resourceName, ResourceType resourceType, string version, Func<string, object> createProperties, bool isExisting)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer isExisting not be on the parameter list and is just set in the ctor with init

Copy link
Member Author

@JoshLove-msft JoshLove-msft Mar 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isExisting needs to be available to the constructor to make it easier to bypass calling GetAzureName for existing resources.

{
if (scope is null) throw new ArgumentNullException(nameof(scope));

Scope = scope;
Parameters = new List<Parameter>();
Parent = parent ?? FindParentInScope(scope);
var azureName = GetAzureName(scope, resourceName);

var azureName = isExisting ? resourceName : GetAzureName(scope, resourceName);
Scope.AddResource(this);
ResourceData = createProperties(azureName);
Version = version;
ParameterOverrides = new Dictionary<object, Dictionary<string, Parameter>>();
PropertyOverrides = new Dictionary<object, Dictionary<string, string>>();
PropertyOverrides = new Dictionary<object, Dictionary<string, PropertyOverride>>();
Dependencies = new List<Resource>();
ResourceType = resourceType;
IsExisting = isExisting;
Id = Parent is null
? ResourceIdentifier.Root
: Parent is ResourceGroup
Expand All @@ -96,6 +100,11 @@ protected Resource(IConstruct scope, Resource? parent, string resourceName, Reso
Name = GetHash();
}

/// <summary>
/// Whether or not the resource already exists.
/// </summary>
public bool IsExisting { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add protected init


/// <summary>
/// Validate and sanitize the resource name.
/// </summary>
Expand Down Expand Up @@ -161,13 +170,13 @@ protected string GetGloballyUniqueName(string resourceName)
/// <param name="parameter">The <see cref="Parameter"/> to assign.</param>
private protected void AssignProperty(object instance, string propertyName, Parameter parameter)
{
if (ParameterOverrides.TryGetValue(instance, out var overrides))
if (PropertyOverrides.TryGetValue(instance, out var overrides))
{
overrides[propertyName] = parameter;
overrides[propertyName] = new PropertyOverride(parameter: parameter);
}
else
{
ParameterOverrides.Add(instance, new Dictionary<string, Parameter> { { propertyName, parameter } });
PropertyOverrides.Add(instance, new Dictionary<string, PropertyOverride> { { propertyName, new PropertyOverride(parameter: parameter) } });
}
Scope.AddParameter(parameter);
//TODO: We should not need this instead a parameter should have a reference to the resource it is associated with but belong to the construct only.
Expand All @@ -179,11 +188,11 @@ private protected void AssignProperty(object instance, string propertyName, stri
{
if (PropertyOverrides.TryGetValue(instance, out var overrides))
{
overrides[propertyName] = propertyValue;
overrides[propertyName] = new PropertyOverride(propertyValue: propertyValue);
}
else
{
PropertyOverrides.Add(instance, new Dictionary<string, string> { { propertyName, propertyValue } });
PropertyOverrides.Add(instance, new Dictionary<string, PropertyOverride> { { propertyName, new PropertyOverride(propertyValue: propertyValue) } });
}
}

Expand Down Expand Up @@ -262,15 +271,6 @@ private BinaryData SerializeModule(ModelReaderWriterOptions options)
}

var bicepOptions = new BicepModelReaderWriterOptions();
foreach (var parameter in ParameterOverrides)
{
var dict = new Dictionary<string, string>();
foreach (var kvp in parameter.Value)
{
dict.Add(kvp.Key, kvp.Value.GetParameterString(ModuleScope!));
}
bicepOptions.ParameterOverrides.Add(parameter.Key, dict);
}
foreach (var propertyOverride in PropertyOverrides)
{
if (!bicepOptions.ParameterOverrides.TryGetValue(propertyOverride.Key, out var dict))
Expand All @@ -280,7 +280,7 @@ private BinaryData SerializeModule(ModelReaderWriterOptions options)
}
foreach (var kvp in propertyOverride.Value)
{
dict.Add(kvp.Key, kvp.Value);
dict[kvp.Key] = kvp.Value.Parameter?.GetParameterString(ModuleScope!) ?? kvp.Value.PropertyValue;
}
}
var data = ModelReaderWriter.Write(ResourceData, bicepOptions).ToMemory();
Expand Down
33 changes: 29 additions & 4 deletions sdk/provisioning/Azure.Provisioning/src/ResourceOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,20 @@ public abstract class Resource<T> : Resource
/// <summary>
/// Gets the properties of the resource.
/// </summary>
public T Properties { get; }
public T Properties
{
get
JoshLove-msft marked this conversation as resolved.
Show resolved Hide resolved
{
if (IsExisting)
{
throw new InvalidOperationException("Properties are not available for existing resources");
JoshLove-msft marked this conversation as resolved.
Show resolved Hide resolved
}

return _properties;
}
}

private readonly T _properties;

/// <summary>
/// Initializes a new instance of the <see cref="Resource{T}"/>.
Expand All @@ -42,15 +55,27 @@ protected Resource(
ResourceType resourceType,
string version,
Func<string, T> createProperties)
: base(scope, parent, resourceName, resourceType, version, name => createProperties(name))
: this(scope, parent, resourceName, resourceType, version, name => createProperties(name), false)
{
}

internal Resource(
IConstruct scope,
Resource? parent,
string resourceName,
ResourceType resourceType,
string version,
Func<string, T> createProperties,
bool isExisting)
: base(scope, parent, resourceName, resourceType, version, name => createProperties(name), isExisting)
{
Properties = (T)ResourceData;
_properties = (T)ResourceData;

if (scope.Configuration?.UseInteractiveMode == true)
{
// We can't use the lambda overload because not all of the T's will inherit from TrackedResourceData
// TODO we may need to add a protected LocationSelector property in the future if there are exceptions to the rule
AssignProperty(Properties, "Location", new Parameter("location", null, defaultValue: $"{ResourceGroup.ResourceGroupFunction}.location", isExpression: true));
AssignProperty(_properties, "Location", new Parameter("location", null, defaultValue: $"{ResourceGroup.ResourceGroupFunction}.location", isExpression: true));
}
}

Expand Down
Loading