Skip to content

Commit

Permalink
AZD-CM integration (Azure#46106)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
KrzysztofCwalina authored Sep 26, 2024
1 parent 2899e0b commit fe6df81
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35309.182
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Provisioning.CloudMachine", "src\Azure.Provisioning.CloudMachine.csproj", "{AF572EE6-69D4-4C6D-87C0-763281AB7AED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Provisioning.CloudMachine.Tests", "tests\Azure.Provisioning.CloudMachine.Tests.csproj", "{46DCEF27-4157-4FB6-A283-B8484EC45665}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.TestFramework", "..\..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{67BF7830-BE03-4F13-B062-5D747A553542}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF572EE6-69D4-4C6D-87C0-763281AB7AED}.Release|Any CPU.Build.0 = Release|Any CPU
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46DCEF27-4157-4FB6-A283-B8484EC45665}.Release|Any CPU.Build.0 = Release|Any CPU
{67BF7830-BE03-4F13-B062-5D747A553542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67BF7830-BE03-4F13-B062-5D747A553542}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67BF7830-BE03-4F13-B062-5D747A553542}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67BF7830-BE03-4F13-B062-5D747A553542}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {66A0BAC4-5774-4C62-941C-D9E8F9D2A3E9}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -1,11 +1,54 @@
namespace Azure.CloudMachine
{
public partial class ClientCache
{
public ClientCache() { }
public T Get<T>(string id, System.Func<T> value) where T : class { throw null; }
}
public partial class CloudMachineClient
{
protected CloudMachineClient() { }
public CloudMachineClient(Azure.Identity.DefaultAzureCredential? credential = null, Microsoft.Extensions.Configuration.IConfiguration? configuration = null) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public Azure.CloudMachine.ClientCache ClientCache { get { throw null; } }
public Azure.Core.TokenCredential Credential { get { throw null; } }
public string Id { get { throw null; } }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public Azure.CloudMachine.CloudMachineClient.CloudMachineProperties Properties { get { throw null; } }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object? obj) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override string ToString() { throw null; }
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public partial struct CloudMachineProperties
{
private object _dummy;
private int _dummyPrimitive;
public System.Uri BlobServiceUri { get { throw null; } }
public System.Uri DefaultContainerUri { get { throw null; } }
public System.Uri KeyVaultUri { get { throw null; } }
public string ServiceBusNamespace { get { throw null; } }
}
}
public static partial class MessagingServices
{
public static void Send(this Azure.CloudMachine.CloudMachineClient cm, object serializable) { }
}
public static partial class StorageServices
{
public static System.BinaryData Download(this Azure.CloudMachine.CloudMachineClient cm, string name) { throw null; }
public static string Upload(this Azure.CloudMachine.CloudMachineClient cm, object json, string? name = null) { throw null; }
}
}
namespace Azure.Provisioning.CloudMachine
{
public partial class CloudMachineInfrastructure : Azure.Provisioning.Infrastructure
{
public CloudMachineInfrastructure(string name = "cm") : base (default(string)) { }
public CloudMachineInfrastructure(string cloudMachineId) : base (default(string)) { }
public Azure.Provisioning.BicepParameter PrincipalIdParameter { get { throw null; } }
public Azure.Provisioning.BicepParameter PrincipalNameParameter { get { throw null; } }
public Azure.Provisioning.BicepParameter PrincipalTypeParameter { get { throw null; } }
public override Azure.Provisioning.ProvisioningPlan Build(Azure.Provisioning.ProvisioningContext? context = null) { throw null; }
public static bool Configure(string[] args, System.Action<Azure.Provisioning.CloudMachine.CloudMachineInfrastructure>? configure = null) { throw null; }
}
}
142 changes: 142 additions & 0 deletions sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azd.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.IO;
using Azure.Provisioning;
using Azure.Provisioning.CloudMachine;
using Azure.Provisioning.Primitives;
using Azure.Provisioning.Resources;
using Azure.Provisioning.Expressions;
using System.Text.Json.Nodes;
using System.Text.Json;
using System;

namespace Azure.CloudMachine;

internal static class Azd
{
private const string MainBicepName = "main";
private const string ResourceGroupVersion = "2024-03-01";

internal static void Init(string infraDirectory, CloudMachineInfrastructure cmi)
{
Directory.CreateDirectory(infraDirectory);

cmi.Build().Save(infraDirectory);
var cmid = ReadOrCreateCmid();

// main.bicep
var location = new BicepParameter("location", typeof(string));
var principalId = new BicepParameter("principalId", typeof(string));

ResourceGroup rg = new(nameof(rg), ResourceGroupVersion)
{
Name = cmid,
Location = location
};

Infrastructure mainBicep = new("main")
{
TargetScope = "subscription"
};
ModuleImport import = new("cm", $"cm.bicep")
{
Name = "cm",
Scope = new IdentifierExpression(rg.ResourceName)
};
import.Parameters.Add(nameof(location), location);
import.Parameters.Add(nameof(principalId), principalId);

mainBicep.Add(rg);
mainBicep.Add(import);
mainBicep.Add(location);
mainBicep.Add(principalId);
mainBicep.Build().Save(infraDirectory);

WriteMainParametersFile(infraDirectory);
}

private static void WriteMainParametersFile(string infraDirectory)
{
File.WriteAllText(Path.Combine(infraDirectory, $"{MainBicepName}.parameters.json"),
"""
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": {
"value": "${AZURE_ENV_NAME}"
},
"location" : {
"value" : "${AZURE_LOCATION}"
},
"principalId": {
"value": "${AZURE_PRINCIPAL_ID}"
}
}
}
""");
}

internal static string ReadOrCreateCmid()
{
string appsettings = Path.Combine(".", "appsettings.json");

string? cmid;
if (!File.Exists(appsettings))
{
cmid = GenerateCloudMachineId();

using FileStream file = File.OpenWrite(appsettings);
Utf8JsonWriter writer = new Utf8JsonWriter(file);
writer.WriteStartObject();
writer.WritePropertyName("CloudMachine"u8);
writer.WriteStartObject();
writer.WriteString("ID"u8, cmid);
writer.WriteEndObject();
writer.WriteEndObject();
writer.Flush();
return cmid;
}

using FileStream json = File.OpenRead(appsettings);
using JsonDocument jd = JsonDocument.Parse(json);
JsonElement je = jd.RootElement;
// attempt to read CM configuration from existing configuration file
if (je.TryGetProperty("CloudMachine"u8, out JsonElement cm))
{
if (!cm.TryGetProperty("ID"u8, out JsonElement id))
{
throw new NotImplementedException();
}
cmid = id.GetString();
if (cmid == null)
throw new NotImplementedException();
return cmid;
}
else
{ // add CM configuration to existing file
JsonNode? root = JsonValue.Parse(appsettings);
if (root is null)
throw new NotImplementedException();

if (root is not JsonObject obj)
throw new NotImplementedException();

var cmProperties = new JsonObject();
cmid = GenerateCloudMachineId();
cmProperties.Add("ID", cmid);
obj.Add("CloudMachine", cmProperties);
}

return cmid;

static string GenerateCloudMachineId()
{
var guid = Guid.NewGuid();
var guidString = guid.ToString("N");
var cnId = "cm" + guidString.Substring(0, 15); // we can increase it to 20, but the template name cannot be that long
return cnId;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Azure.Provisioning.CloudMachine simplifies declarative resource provisioning in .NET.</Description>
Expand All @@ -11,9 +11,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Messaging.ServiceBus"/>
<PackageReference Include="Azure.Provisioning.Storage" VersionOverride="1.0.0-alpha.20240918.3" />
<PackageReference Include="Azure.Provisioning.ServiceBus" VersionOverride="1.0.0-alpha.20240918.3" />
<PackageReference Include="Azure.Provisioning.EventGrid" VersionOverride="1.0.0-alpha.20240918.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" VersionOverride="8.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;

namespace Azure.CloudMachine;

// TODO: this is a very demo implementation. We need to do better
public class ClientCache
{
private readonly Dictionary<string, object> _clients = new Dictionary<string, object>();

// TODO: consider uisng ICLientCreator instead of Func
public T Get<T>(string id, Func<T> value) where T: class
{
lock (_clients)
{
if (_clients.TryGetValue(id, out object cached))
{
T client = (T)cached;
return client;
}

if (_clients.Count > 100)
{
GC();
}
T created = value();
_clients.Add(id, created);
return created;
}

void GC()
{
foreach (object value in _clients.Values)
{
if (value is IDisposable disposable) disposable.Dispose();
}
_clients.Clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.Configuration;

namespace Azure.CloudMachine;

public partial class CloudMachineClient
{
public string Id { get; }

public TokenCredential Credential { get; } = new ChainedTokenCredential(
new AzureDeveloperCliCredential()
);

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "<Pending>")]
public CloudMachineClient(DefaultAzureCredential? credential = default, IConfiguration? configuration = default)
{
if (credential != default)
{
Credential = credential;
}

string? cmid;
if (configuration == default)
{
cmid = Azd.ReadOrCreateCmid();
}
else
{
cmid = configuration["CloudMachine:ID"];
if (cmid == null)
throw new Exception("CloudMachine:ID configuration value missing");
}

Id = cmid!;
}

protected CloudMachineClient()
{
Id = "CM";
}

[EditorBrowsable(EditorBrowsableState.Never)]
public ClientCache ClientCache { get; } = new ClientCache();

[EditorBrowsable(EditorBrowsableState.Never)]
public CloudMachineProperties Properties => new CloudMachineProperties(this);

public struct CloudMachineProperties
{
private readonly CloudMachineClient _cm;

internal CloudMachineProperties(CloudMachineClient cm) => _cm = cm;
public Uri DefaultContainerUri => new Uri($"https://{_cm.Id}.blob.core.windows.net/default");
public Uri BlobServiceUri => new Uri($"https://{_cm.Id}.blob.core.windows.net/");
public Uri KeyVaultUri => new Uri($"https://{_cm.Id}.vault.azure.net/");

public string ServiceBusNamespace => $"{_cm.Id}.servicebus.windows.net";
}

[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();

[EditorBrowsable(EditorBrowsableState.Never)]
public override string ToString() => Id;
}
Loading

0 comments on commit fe6df81

Please sign in to comment.