Skip to content

Commit fe6df81

Browse files
AZD-CM integration (#46106)
* added solution, and a placeholder test file * added basic azd integration * made principalId an azd parameter * removed dependency on concrete config system * removed accidentaly added file * refactored * started experimenting with storage APIs * changed resource names to be == to CMID * small PR feedback * fixed build break * merged * removed unfinished work * added cache and messaging * use cache * PR feedback * cached SB Client * updated api file
1 parent 2899e0b commit fe6df81

11 files changed

+531
-31
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.12.35309.182
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Provisioning.CloudMachine", "src\Azure.Provisioning.CloudMachine.csproj", "{AF572EE6-69D4-4C6D-87C0-763281AB7AED}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Provisioning.CloudMachine.Tests", "tests\Azure.Provisioning.CloudMachine.Tests.csproj", "{46DCEF27-4157-4FB6-A283-B8484EC45665}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.TestFramework", "..\..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{67BF7830-BE03-4F13-B062-5D747A553542}"
11+
EndProject
12+
Global
13+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
14+
Debug|Any CPU = Debug|Any CPU
15+
Release|Any CPU = Release|Any CPU
16+
EndGlobalSection
17+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
18+
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19+
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Debug|Any CPU.Build.0 = Debug|Any CPU
20+
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Release|Any CPU.ActiveCfg = Release|Any CPU
21+
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Release|Any CPU.Build.0 = Release|Any CPU
22+
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Debug|Any CPU.Build.0 = Debug|Any CPU
24+
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Release|Any CPU.ActiveCfg = Release|Any CPU
25+
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{67BF7830-BE03-4F13-B062-5D747A553542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{67BF7830-BE03-4F13-B062-5D747A553542}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{67BF7830-BE03-4F13-B062-5D747A553542}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{67BF7830-BE03-4F13-B062-5D747A553542}.Release|Any CPU.Build.0 = Release|Any CPU
30+
EndGlobalSection
31+
GlobalSection(SolutionProperties) = preSolution
32+
HideSolutionNode = FALSE
33+
EndGlobalSection
34+
GlobalSection(ExtensibilityGlobals) = postSolution
35+
SolutionGuid = {66A0BAC4-5774-4C62-941C-D9E8F9D2A3E9}
36+
EndGlobalSection
37+
EndGlobal
Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,54 @@
1+
namespace Azure.CloudMachine
2+
{
3+
public partial class ClientCache
4+
{
5+
public ClientCache() { }
6+
public T Get<T>(string id, System.Func<T> value) where T : class { throw null; }
7+
}
8+
public partial class CloudMachineClient
9+
{
10+
protected CloudMachineClient() { }
11+
public CloudMachineClient(Azure.Identity.DefaultAzureCredential? credential = null, Microsoft.Extensions.Configuration.IConfiguration? configuration = null) { }
12+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
13+
public Azure.CloudMachine.ClientCache ClientCache { get { throw null; } }
14+
public Azure.Core.TokenCredential Credential { get { throw null; } }
15+
public string Id { get { throw null; } }
16+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
17+
public Azure.CloudMachine.CloudMachineClient.CloudMachineProperties Properties { get { throw null; } }
18+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
19+
public override bool Equals(object? obj) { throw null; }
20+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
21+
public override int GetHashCode() { throw null; }
22+
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
23+
public override string ToString() { throw null; }
24+
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
25+
public partial struct CloudMachineProperties
26+
{
27+
private object _dummy;
28+
private int _dummyPrimitive;
29+
public System.Uri BlobServiceUri { get { throw null; } }
30+
public System.Uri DefaultContainerUri { get { throw null; } }
31+
public System.Uri KeyVaultUri { get { throw null; } }
32+
public string ServiceBusNamespace { get { throw null; } }
33+
}
34+
}
35+
public static partial class MessagingServices
36+
{
37+
public static void Send(this Azure.CloudMachine.CloudMachineClient cm, object serializable) { }
38+
}
39+
public static partial class StorageServices
40+
{
41+
public static System.BinaryData Download(this Azure.CloudMachine.CloudMachineClient cm, string name) { throw null; }
42+
public static string Upload(this Azure.CloudMachine.CloudMachineClient cm, object json, string? name = null) { throw null; }
43+
}
44+
}
145
namespace Azure.Provisioning.CloudMachine
246
{
347
public partial class CloudMachineInfrastructure : Azure.Provisioning.Infrastructure
448
{
5-
public CloudMachineInfrastructure(string name = "cm") : base (default(string)) { }
49+
public CloudMachineInfrastructure(string cloudMachineId) : base (default(string)) { }
650
public Azure.Provisioning.BicepParameter PrincipalIdParameter { get { throw null; } }
7-
public Azure.Provisioning.BicepParameter PrincipalNameParameter { get { throw null; } }
8-
public Azure.Provisioning.BicepParameter PrincipalTypeParameter { get { throw null; } }
951
public override Azure.Provisioning.ProvisioningPlan Build(Azure.Provisioning.ProvisioningContext? context = null) { throw null; }
52+
public static bool Configure(string[] args, System.Action<Azure.Provisioning.CloudMachine.CloudMachineInfrastructure>? configure = null) { throw null; }
1053
}
1154
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.IO;
5+
using Azure.Provisioning;
6+
using Azure.Provisioning.CloudMachine;
7+
using Azure.Provisioning.Primitives;
8+
using Azure.Provisioning.Resources;
9+
using Azure.Provisioning.Expressions;
10+
using System.Text.Json.Nodes;
11+
using System.Text.Json;
12+
using System;
13+
14+
namespace Azure.CloudMachine;
15+
16+
internal static class Azd
17+
{
18+
private const string MainBicepName = "main";
19+
private const string ResourceGroupVersion = "2024-03-01";
20+
21+
internal static void Init(string infraDirectory, CloudMachineInfrastructure cmi)
22+
{
23+
Directory.CreateDirectory(infraDirectory);
24+
25+
cmi.Build().Save(infraDirectory);
26+
var cmid = ReadOrCreateCmid();
27+
28+
// main.bicep
29+
var location = new BicepParameter("location", typeof(string));
30+
var principalId = new BicepParameter("principalId", typeof(string));
31+
32+
ResourceGroup rg = new(nameof(rg), ResourceGroupVersion)
33+
{
34+
Name = cmid,
35+
Location = location
36+
};
37+
38+
Infrastructure mainBicep = new("main")
39+
{
40+
TargetScope = "subscription"
41+
};
42+
ModuleImport import = new("cm", $"cm.bicep")
43+
{
44+
Name = "cm",
45+
Scope = new IdentifierExpression(rg.ResourceName)
46+
};
47+
import.Parameters.Add(nameof(location), location);
48+
import.Parameters.Add(nameof(principalId), principalId);
49+
50+
mainBicep.Add(rg);
51+
mainBicep.Add(import);
52+
mainBicep.Add(location);
53+
mainBicep.Add(principalId);
54+
mainBicep.Build().Save(infraDirectory);
55+
56+
WriteMainParametersFile(infraDirectory);
57+
}
58+
59+
private static void WriteMainParametersFile(string infraDirectory)
60+
{
61+
File.WriteAllText(Path.Combine(infraDirectory, $"{MainBicepName}.parameters.json"),
62+
"""
63+
{
64+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
65+
"contentVersion": "1.0.0.0",
66+
"parameters": {
67+
"environmentName": {
68+
"value": "${AZURE_ENV_NAME}"
69+
},
70+
"location" : {
71+
"value" : "${AZURE_LOCATION}"
72+
},
73+
"principalId": {
74+
"value": "${AZURE_PRINCIPAL_ID}"
75+
}
76+
}
77+
}
78+
""");
79+
}
80+
81+
internal static string ReadOrCreateCmid()
82+
{
83+
string appsettings = Path.Combine(".", "appsettings.json");
84+
85+
string? cmid;
86+
if (!File.Exists(appsettings))
87+
{
88+
cmid = GenerateCloudMachineId();
89+
90+
using FileStream file = File.OpenWrite(appsettings);
91+
Utf8JsonWriter writer = new Utf8JsonWriter(file);
92+
writer.WriteStartObject();
93+
writer.WritePropertyName("CloudMachine"u8);
94+
writer.WriteStartObject();
95+
writer.WriteString("ID"u8, cmid);
96+
writer.WriteEndObject();
97+
writer.WriteEndObject();
98+
writer.Flush();
99+
return cmid;
100+
}
101+
102+
using FileStream json = File.OpenRead(appsettings);
103+
using JsonDocument jd = JsonDocument.Parse(json);
104+
JsonElement je = jd.RootElement;
105+
// attempt to read CM configuration from existing configuration file
106+
if (je.TryGetProperty("CloudMachine"u8, out JsonElement cm))
107+
{
108+
if (!cm.TryGetProperty("ID"u8, out JsonElement id))
109+
{
110+
throw new NotImplementedException();
111+
}
112+
cmid = id.GetString();
113+
if (cmid == null)
114+
throw new NotImplementedException();
115+
return cmid;
116+
}
117+
else
118+
{ // add CM configuration to existing file
119+
JsonNode? root = JsonValue.Parse(appsettings);
120+
if (root is null)
121+
throw new NotImplementedException();
122+
123+
if (root is not JsonObject obj)
124+
throw new NotImplementedException();
125+
126+
var cmProperties = new JsonObject();
127+
cmid = GenerateCloudMachineId();
128+
cmProperties.Add("ID", cmid);
129+
obj.Add("CloudMachine", cmProperties);
130+
}
131+
132+
return cmid;
133+
134+
static string GenerateCloudMachineId()
135+
{
136+
var guid = Guid.NewGuid();
137+
var guidString = guid.ToString("N");
138+
var cnId = "cm" + guidString.Substring(0, 15); // we can increase it to 20, but the template name cannot be that long
139+
return cnId;
140+
}
141+
}
142+
}

sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>Azure.Provisioning.CloudMachine simplifies declarative resource provisioning in .NET.</Description>
@@ -11,9 +11,11 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<PackageReference Include="Azure.Messaging.ServiceBus"/>
1415
<PackageReference Include="Azure.Provisioning.Storage" VersionOverride="1.0.0-alpha.20240918.3" />
1516
<PackageReference Include="Azure.Provisioning.ServiceBus" VersionOverride="1.0.0-alpha.20240918.3" />
1617
<PackageReference Include="Azure.Provisioning.EventGrid" VersionOverride="1.0.0-alpha.20240918.1" />
18+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" VersionOverride="8.0.0" />
1719
</ItemGroup>
1820

1921
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Azure.CloudMachine;
8+
9+
// TODO: this is a very demo implementation. We need to do better
10+
public class ClientCache
11+
{
12+
private readonly Dictionary<string, object> _clients = new Dictionary<string, object>();
13+
14+
// TODO: consider uisng ICLientCreator instead of Func
15+
public T Get<T>(string id, Func<T> value) where T: class
16+
{
17+
lock (_clients)
18+
{
19+
if (_clients.TryGetValue(id, out object cached))
20+
{
21+
T client = (T)cached;
22+
return client;
23+
}
24+
25+
if (_clients.Count > 100)
26+
{
27+
GC();
28+
}
29+
T created = value();
30+
_clients.Add(id, created);
31+
return created;
32+
}
33+
34+
void GC()
35+
{
36+
foreach (object value in _clients.Values)
37+
{
38+
if (value is IDisposable disposable) disposable.Dispose();
39+
}
40+
_clients.Clear();
41+
}
42+
}
43+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.ComponentModel;
7+
using Azure.Core;
8+
using Azure.Identity;
9+
using Microsoft.Extensions.Configuration;
10+
11+
namespace Azure.CloudMachine;
12+
13+
public partial class CloudMachineClient
14+
{
15+
public string Id { get; }
16+
17+
public TokenCredential Credential { get; } = new ChainedTokenCredential(
18+
new AzureDeveloperCliCredential()
19+
);
20+
21+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "<Pending>")]
22+
public CloudMachineClient(DefaultAzureCredential? credential = default, IConfiguration? configuration = default)
23+
{
24+
if (credential != default)
25+
{
26+
Credential = credential;
27+
}
28+
29+
string? cmid;
30+
if (configuration == default)
31+
{
32+
cmid = Azd.ReadOrCreateCmid();
33+
}
34+
else
35+
{
36+
cmid = configuration["CloudMachine:ID"];
37+
if (cmid == null)
38+
throw new Exception("CloudMachine:ID configuration value missing");
39+
}
40+
41+
Id = cmid!;
42+
}
43+
44+
protected CloudMachineClient()
45+
{
46+
Id = "CM";
47+
}
48+
49+
[EditorBrowsable(EditorBrowsableState.Never)]
50+
public ClientCache ClientCache { get; } = new ClientCache();
51+
52+
[EditorBrowsable(EditorBrowsableState.Never)]
53+
public CloudMachineProperties Properties => new CloudMachineProperties(this);
54+
55+
public struct CloudMachineProperties
56+
{
57+
private readonly CloudMachineClient _cm;
58+
59+
internal CloudMachineProperties(CloudMachineClient cm) => _cm = cm;
60+
public Uri DefaultContainerUri => new Uri($"https://{_cm.Id}.blob.core.windows.net/default");
61+
public Uri BlobServiceUri => new Uri($"https://{_cm.Id}.blob.core.windows.net/");
62+
public Uri KeyVaultUri => new Uri($"https://{_cm.Id}.vault.azure.net/");
63+
64+
public string ServiceBusNamespace => $"{_cm.Id}.servicebus.windows.net";
65+
}
66+
67+
[EditorBrowsable(EditorBrowsableState.Never)]
68+
public override bool Equals(object? obj) => base.Equals(obj);
69+
[EditorBrowsable(EditorBrowsableState.Never)]
70+
public override int GetHashCode() => base.GetHashCode();
71+
72+
[EditorBrowsable(EditorBrowsableState.Never)]
73+
public override string ToString() => Id;
74+
}

0 commit comments

Comments
 (0)