diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/CHANGELOG.md b/sdk/provisioning/Azure.Provisioning.CloudMachine/CHANGELOG.md index 13dd08af78ab..547c37ba3b96 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/CHANGELOG.md +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/CHANGELOG.md @@ -2,6 +2,8 @@ ## 1.0.0-beta.1 (Unreleased) +## 1.0.0-beta.2 (Unreleased) + ### Features Added ### Breaking Changes diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs index 5afe8aa21795..fcc2bf4f4892 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs @@ -1,45 +1,75 @@ namespace Azure.CloudMachine { - public partial class ClientCache + public partial class CloudMachineClient : Azure.CloudMachine.CloudMachineWorkspace { - public ClientCache() { } - public T Get(string id, System.Func value) where T : class { throw null; } + public CloudMachineClient(Azure.Core.TokenCredential? credential = null, Microsoft.Extensions.Configuration.IConfiguration? configuration = null) : base (default(Azure.Core.TokenCredential), default(Microsoft.Extensions.Configuration.IConfiguration)) { } + public Azure.CloudMachine.MessagingServices Messaging { get { throw null; } } + public Azure.CloudMachine.StorageServices Storage { get { throw null; } } } - public partial class CloudMachineClient + public partial class CloudMachineWorkspace : Azure.Core.WorkspaceClient { - protected CloudMachineClient() { } - public CloudMachineClient(Azure.Identity.DefaultAzureCredential? credential = null, Microsoft.Extensions.Configuration.IConfiguration? configuration = null) { } + public CloudMachineWorkspace(Azure.Core.TokenCredential? 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; } } + public override Azure.Core.TokenCredential Credential { get { throw null; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public Azure.CloudMachine.CloudMachineClient.CloudMachineProperties Properties { get { throw null; } } + public string Id { 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 Azure.Core.ClientConfiguration? GetConfiguration(string clientId, string? instanceId = null) { 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 + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct MessagingServices { - public static void Send(this Azure.CloudMachine.CloudMachineClient cm, object serializable) { } + private readonly object _dummy; + private readonly int _dummyPrimitive; + public void SendMessage(object serializable) { } + public void WhenMessageReceived(System.Action received) { } } - public static partial class StorageServices + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct 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; } + private readonly object _dummy; + private readonly int _dummyPrimitive; + public System.BinaryData DownloadBlob(string name) { throw null; } + public string UploadBlob(object json, string? name = null) { throw null; } + public void WhenBlobCreated(System.Func function) { } + public void WhenBlobUploaded(System.Action function) { } + } +} +namespace Azure.Core +{ + public partial class ClientCache + { + public ClientCache() { } + public T Get(string id, System.Func value) where T : class { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct ClientConfiguration + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public ClientConfiguration(string endpoint, string? apiKey = null) { throw null; } + public string? ApiKey { get { throw null; } } + public Azure.Core.CredentialType CredentialType { get { throw null; } } + public string Endpoint { get { throw null; } } + } + public enum CredentialType + { + EntraId = 0, + ApiKey = 1, + } + public abstract partial class WorkspaceClient + { + protected WorkspaceClient() { } + public abstract Azure.Core.TokenCredential Credential { get; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public Azure.Core.ClientCache Subclients { get { throw null; } } + public abstract Azure.Core.ClientConfiguration? GetConfiguration(string clientId, string? instanceId = null); } } namespace Azure.Provisioning.CloudMachine @@ -47,6 +77,7 @@ namespace Azure.Provisioning.CloudMachine public abstract partial class CloudMachineFeature { protected CloudMachineFeature() { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public abstract void AddTo(Azure.Provisioning.CloudMachine.CloudMachineInfrastructure cm); } public partial class CloudMachineInfrastructure @@ -65,24 +96,26 @@ namespace Azure.Provisioning.CloudMachine.KeyVault { public static partial class KeyVaultExtensions { - public static Azure.Security.KeyVault.Secrets.SecretClient GetKeyVaultSecretClient(this Azure.CloudMachine.CloudMachineClient client) { throw null; } + public static Azure.Security.KeyVault.Secrets.SecretClient GetKeyVaultSecretsClient(this Azure.Core.WorkspaceClient workspace) { throw null; } } public partial class KeyVaultFeature : Azure.Provisioning.CloudMachine.CloudMachineFeature { - public KeyVaultFeature() { } + public KeyVaultFeature(Azure.Provisioning.KeyVault.KeyVaultSku? sku = null) { } public Azure.Provisioning.KeyVault.KeyVaultSku Sku { get { throw null; } set { } } - public override void AddTo(Azure.Provisioning.CloudMachine.CloudMachineInfrastructure cm) { } + public override void AddTo(Azure.Provisioning.CloudMachine.CloudMachineInfrastructure infrastructure) { } } } namespace Azure.Provisioning.CloudMachine.OpenAI { public partial class OpenAIFeature : Azure.Provisioning.CloudMachine.CloudMachineFeature { - public OpenAIFeature() { } - public override void AddTo(Azure.Provisioning.CloudMachine.CloudMachineInfrastructure cm) { } + public OpenAIFeature(string model, string modelVersion) { } + public string Model { get { throw null; } } + public string ModelVersion { get { throw null; } } + public override void AddTo(Azure.Provisioning.CloudMachine.CloudMachineInfrastructure cloudMachine) { } } public static partial class OpenAIFeatureExtensions { - public static Azure.Security.KeyVault.Secrets.SecretClient GetOpenAIClient(this Azure.CloudMachine.CloudMachineClient client) { throw null; } + public static OpenAI.Chat.ChatClient GetOpenAIChatClient(this Azure.Core.WorkspaceClient workspace) { throw null; } } } diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj index 58d9db9b883c..b15868c0262a 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj @@ -2,7 +2,7 @@ Azure.Provisioning.CloudMachine simplifies declarative resource provisioning in .NET. - 1.0.0-beta.1 + 1.0.0-beta.2 $(RequiredTargetFrameworks) 12 @@ -11,6 +11,7 @@ + diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/KeyVaultFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/KeyVaultFeature.cs new file mode 100644 index 000000000000..bba2556237ea --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/KeyVaultFeature.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core; +using Azure.Provisioning.Authorization; +using Azure.Provisioning.Expressions; +using Azure.Provisioning.KeyVault; +using Azure.Security.KeyVault.Secrets; + +namespace Azure.Provisioning.CloudMachine.KeyVault; + +public class KeyVaultFeature : CloudMachineFeature +{ + public KeyVaultSku Sku { get; set; } + + public KeyVaultFeature(KeyVaultSku? sku = default) + { + if (sku == null) + { + sku = new KeyVaultSku { Name = KeyVaultSkuName.Standard, Family = KeyVaultSkuFamily.A, }; + } + Sku = sku; + } + public override void AddTo(CloudMachineInfrastructure infrastructure) + { + // Add a KeyVault to the CloudMachine infrastructure. + KeyVaultService keyVaultResource = new("cm_kv") + { + Name = infrastructure.Id, + Properties = + new KeyVaultProperties + { + Sku = this.Sku, + TenantId = BicepFunction.GetSubscription().TenantId, + EnabledForDeployment = true, + AccessPolicies = [ + new KeyVaultAccessPolicy() { + ObjectId = infrastructure.PrincipalIdParameter, + Permissions = new IdentityAccessPermissions() { + Secrets = [IdentityAccessSecretPermission.Get, IdentityAccessSecretPermission.Set] + }, + TenantId = infrastructure.Identity.TenantId + } + ] + }, + }; + + infrastructure.AddResource(keyVaultResource); + + RoleAssignment ra = keyVaultResource.CreateRoleAssignment(KeyVaultBuiltInRole.KeyVaultAdministrator, RoleManagementPrincipalType.User, infrastructure.PrincipalIdParameter); + infrastructure.AddResource(ra); + + // necessary until ResourceName is settable via AssignRole. + RoleAssignment kvMiRoleAssignment = new RoleAssignment(keyVaultResource.IdentifierName + "_" + infrastructure.Identity.IdentifierName + "_" + KeyVaultBuiltInRole.GetBuiltInRoleName(KeyVaultBuiltInRole.KeyVaultAdministrator)); + kvMiRoleAssignment.Name = BicepFunction.CreateGuid(keyVaultResource.Id, infrastructure.Identity.Id, BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", KeyVaultBuiltInRole.KeyVaultAdministrator.ToString())); + kvMiRoleAssignment.Scope = new IdentifierExpression(keyVaultResource.IdentifierName); + kvMiRoleAssignment.PrincipalType = RoleManagementPrincipalType.ServicePrincipal; + kvMiRoleAssignment.RoleDefinitionId = BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", KeyVaultBuiltInRole.KeyVaultAdministrator.ToString()); + kvMiRoleAssignment.PrincipalId = infrastructure.Identity.PrincipalId; + infrastructure.AddResource(kvMiRoleAssignment); + } +} + +public static class KeyVaultExtensions +{ + public static SecretClient GetKeyVaultSecretsClient(this WorkspaceClient workspace) + { + ClientConfiguration? connectionMaybe = workspace.GetConfiguration(typeof(SecretClient).FullName); + if (connectionMaybe == null) + { + throw new Exception("Connection not found"); + } + + ClientConfiguration connection = connectionMaybe.Value; + if (connection.CredentialType == CredentialType.EntraId) + { + return new(new Uri(connection.Endpoint), workspace.Credential); + } + throw new Exception("ApiKey not supported"); + } +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs new file mode 100644 index 000000000000..e2f4d65be3a8 --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Diagnostics.Contracts; +using Azure.AI.OpenAI; +using Azure.CloudMachine; +using Azure.Core; +using Azure.Provisioning.Authorization; +using Azure.Provisioning.CognitiveServices; +using OpenAI.Chat; + +namespace Azure.Provisioning.CloudMachine.OpenAI; + +public class OpenAIFeature : CloudMachineFeature +{ + public string Model { get; } + public string ModelVersion { get; } + + public OpenAIFeature(string model, string modelVersion) { Model = model; ModelVersion = modelVersion; } + + public override void AddTo(CloudMachineInfrastructure cloudMachine) + { + CognitiveServicesAccount cognitiveServices = new("openai") + { + Name = cloudMachine.Id, + Kind = "OpenAI", + Sku = new CognitiveServicesSku { Name = "S0" }, + Properties = new CognitiveServicesAccountProperties() + { + PublicNetworkAccess = ServiceAccountPublicNetworkAccess.Enabled, + CustomSubDomainName = cloudMachine.Id + }, + }; + + cloudMachine.AddResource(cognitiveServices.CreateRoleAssignment( + CognitiveServicesBuiltInRole.CognitiveServicesOpenAIContributor, + RoleManagementPrincipalType.User, + cloudMachine.PrincipalIdParameter) + ); + + // TODO: if we every support more than one deployment, they need to be chained using DependsOn. + // The reason is that deployments need to be deployed/created serially. + CognitiveServicesAccountDeployment deployment = new("openai_deployment", "2023-05-01") + { + Parent = cognitiveServices, + Name = cloudMachine.Id, + Properties = new CognitiveServicesAccountDeploymentProperties() + { + Model = new CognitiveServicesAccountDeploymentModel() { + Name = this.Model, + Format = "OpenAI", + Version = this.ModelVersion + } + }, + }; + + cloudMachine.AddResource(cognitiveServices); + cloudMachine.AddResource(deployment); + } +} + +public static class OpenAIFeatureExtensions +{ + public static ChatClient GetOpenAIChatClient(this WorkspaceClient workspace) + { + string chatClientId = typeof(ChatClient).FullName; + + ChatClient client = workspace.Subclients.Get(chatClientId, () => + { + string azureOpenAIClientId = typeof(AzureOpenAIClient).FullName; + + AzureOpenAIClient aoia = workspace.Subclients.Get(azureOpenAIClientId, () => + { + ClientConfiguration? connectionMaybe = workspace.GetConfiguration(typeof(AzureOpenAIClient).FullName); + if (connectionMaybe == null) throw new Exception("Connection not found"); + + ClientConfiguration connection = connectionMaybe.Value; + Uri endpoint = new(connection.Endpoint); + var clientOptions = new AzureOpenAIClientOptions(); + if (connection.CredentialType == CredentialType.EntraId) + { + AzureOpenAIClient aoai = new(endpoint, workspace.Credential, clientOptions); + return aoai; + } + else + { + AzureOpenAIClient aoai = new(endpoint, new ApiKeyCredential(connection.ApiKey!), clientOptions); + return aoai; + } + }); + + string azureOpenAIChatClientId = typeof(ChatClient).FullName; + ClientConfiguration? connectionMaybe = workspace.GetConfiguration(azureOpenAIChatClientId); + if (connectionMaybe == null) throw new Exception("Connection not found"); + var connection = connectionMaybe.Value; + ChatClient chat = aoia.GetChatClient(connection.Endpoint); + return chat; + }); + + return client; + } +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azd.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/Azd.cs similarity index 100% rename from sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azd.cs rename to sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/Azd.cs diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CloudMachineFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineFeature.cs similarity index 75% rename from sdk/provisioning/Azure.Provisioning.CloudMachine/src/CloudMachineFeature.cs rename to sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineFeature.cs index 0f96ce3ea71f..4489185758ff 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CloudMachineFeature.cs +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineFeature.cs @@ -1,9 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.ComponentModel; + namespace Azure.Provisioning.CloudMachine; public abstract class CloudMachineFeature { + [EditorBrowsable(EditorBrowsableState.Never)] public abstract void AddTo(CloudMachineInfrastructure cm); } diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CloudMachineInfrastructure.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs similarity index 91% rename from sdk/provisioning/Azure.Provisioning.CloudMachine/src/CloudMachineInfrastructure.cs rename to sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs index f134dd71790e..64c677bc0e43 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CloudMachineInfrastructure.cs +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs @@ -13,6 +13,7 @@ using Azure.Provisioning.Storage; using Azure.Provisioning.Primitives; using System.Collections.Generic; +using System.Security.Principal; namespace Azure.Provisioning.CloudMachine; @@ -97,7 +98,7 @@ public CloudMachineInfrastructure(string cmId) }, Name = _cmid, }; - _serviceBusNamespaceAuthorizationRule = new($"cm_servicebus_auth_rule", "2021-11-01") + _serviceBusNamespaceAuthorizationRule = new("cm_servicebus_auth_rule", "2021-11-01") { Parent = _serviceBusNamespace, Rights = [ServiceBusAccessRight.Listen, ServiceBusAccessRight.Send, ServiceBusAccessRight.Manage] @@ -115,6 +116,7 @@ public CloudMachineInfrastructure(string cmId) }; _serviceBusSubscription_private = new("cm_servicebus_subscription_private", "2021-11-01") { + Name = "cm_servicebus_subscription_private", Parent = _serviceBusTopic_private, IsClientAffine = false, LockDuration = new StringLiteral("PT30S"), @@ -160,6 +162,7 @@ public CloudMachineInfrastructure(string cmId) }; _eventGridSubscription_blobs = new("cm_eventgrid_subscription_blob", "2022-06-15") { + Name = "cm-eventgrid-subscription-blob", Parent = _eventGridTopic_blobs, DeliveryWithResourceIdentity = new DeliveryWithResourceIdentity { @@ -230,12 +233,19 @@ public ProvisioningPlan Build(ProvisioningContext? context = null) _infrastructure.Add(_serviceBusSubscription_private); _infrastructure.Add(_serviceBusSubscription_default); - RoleAssignment roleAssignment = _serviceBusNamespace.CreateRoleAssignment(ServiceBusBuiltInRole.AzureServiceBusDataSender, RoleManagementPrincipalType.User, PrincipalIdParameter); + // This is necessary until SystemTopic adds an AssignRole method. + var role = ServiceBusBuiltInRole.AzureServiceBusDataSender; + RoleAssignment roleAssignment = new RoleAssignment("cm_servicebus_role"); + roleAssignment.Name = BicepFunction.CreateGuid(_serviceBusNamespace.Id, Identity.Id, BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", role.ToString())); + roleAssignment.Scope = new IdentifierExpression(_serviceBusNamespace.IdentifierName); + roleAssignment.PrincipalType = RoleManagementPrincipalType.ServicePrincipal; + roleAssignment.RoleDefinitionId = BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", role.ToString()); + roleAssignment.PrincipalId = Identity.PrincipalId; _infrastructure.Add(roleAssignment); // the role assignment must exist before the system topic event subscription is created. _eventGridSubscription_blobs.DependsOn.Add(roleAssignment); - _infrastructure.Add(_eventGridSubscription_blobs); + _infrastructure.Add(_eventGridTopic_blobs); // Placeholders for now. diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/CloudMachine.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/CloudMachine.cs deleted file mode 100644 index 87a3d1a54393..000000000000 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/CloudMachine.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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 = "")] - 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; -} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/MessagingServices.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/MessagingServices.cs deleted file mode 100644 index d7702dbf254c..000000000000 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/MessagingServices.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Azure.Messaging.ServiceBus; - -namespace Azure.CloudMachine; - -public static class MessagingServices -{ - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "AZC0107:DO NOT call public asynchronous method in synchronous scope.", Justification = "")] - public static void Send(this CloudMachineClient cm, object serializable) - { - ServiceBusSender sender = cm.ClientCache.Get("cm_default_topic_sender", () => - { - ServiceBusClient sb = cm.ClientCache.Get(cm.Properties.ServiceBusNamespace, () => - { - ServiceBusClient sb = new(cm.Properties.ServiceBusNamespace, cm.Credential); - return sb; - }); - - ServiceBusSender sender = sb.CreateSender("cm_default_topic_sender"); - return sender; - }); - - BinaryData serialized = BinaryData.FromObjectAsJson(serializable); - ServiceBusMessage message = new(serialized); -#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). - sender.SendMessageAsync(message).GetAwaiter().GetResult(); -#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). - } -} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/StorageServices.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/StorageServices.cs deleted file mode 100644 index 0899450718d2..000000000000 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/StorageServices.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; - -namespace Azure.CloudMachine; - -public static class StorageServices -{ - public static string Upload(this CloudMachineClient cm, object json, string? name = default) - { - BlobContainerClient container = cm.ClientCache.Get(cm.Properties.DefaultContainerUri.AbsoluteUri, () => - { - BlobContainerClient container = new(cm.Properties.DefaultContainerUri, cm.Credential); - return container; - }); - - if (name == default) name = $"b{Guid.NewGuid()}"; - - container.UploadBlob(name, BinaryData.FromObjectAsJson(json)); - - return name; - } - - public static BinaryData Download(this CloudMachineClient cm, string name) - { - BlobContainerClient container = cm.ClientCache.Get(cm.Properties.DefaultContainerUri.AbsoluteUri, () => - { - BlobContainerClient container = new(cm.Properties.DefaultContainerUri, cm.Credential); - return container; - }); - - BlobClient blob = container.GetBlobClient(name); - BlobDownloadResult result = blob.DownloadContent(); - return result.Content; - } -} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/ClientCache.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/ClientCache.cs similarity index 97% rename from sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/ClientCache.cs rename to sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/ClientCache.cs index 11735b94c514..0cef9c35b12d 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Client/ClientCache.cs +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/ClientCache.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Azure.CloudMachine; +namespace Azure.Core; // TODO: this is a very demo implementation. We need to do better public class ClientCache diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/LoggingPolicy.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/LoggingPolicy.cs new file mode 100644 index 000000000000..fd05bf879bda --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/LoggingPolicy.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace System.ClientModel; + +internal class LoggingPolicy : PipelinePolicy +{ + public LoggingPolicy() {} + + public List AllowedHeaders { get; } = new List(["Content-Type", "Accept", "User-Agent", "x-ms-client-request-id"]); + public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + LogRequest(message); + if (currentIndex < pipeline.Count - 1) + { + pipeline[currentIndex + 1].Process(message, pipeline, currentIndex + 1); + } + } + + public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + LogRequest(message); + if (currentIndex < pipeline.Count - 1) + { + await pipeline[currentIndex + 1].ProcessAsync(message, pipeline, currentIndex + 1).ConfigureAwait(false); + } + LogResponse(message); + } + + protected virtual void LogRequest(PipelineMessage message) + { + string logMessage = FormatRequestLog(message); + Console.WriteLine(logMessage); + } + protected virtual void LogResponse(PipelineMessage message) + { + string logMessage = FormatResponseLog(message); + Console.WriteLine(logMessage); + } + + protected virtual string FormatRequestLog(PipelineMessage message) { + StringBuilder logMessage = new StringBuilder(); + FormatRequestLine(message, logMessage); + FormatHeaders(message, logMessage); + FormatContent(message, logMessage); + return logMessage.ToString(); + } + protected virtual string FormatResponseLog(PipelineMessage message) + { + StringBuilder logMessage = new StringBuilder(); + PipelineResponse response = message.Response!; + logMessage.Append(response.Status.ToString()); + logMessage.Append(' '); + logMessage.AppendLine(response.ReasonPhrase); + FormatHeaders(message, logMessage); + FormatContent(message, logMessage); + return logMessage.ToString(); + } + + protected virtual void FormatRequestLine(PipelineMessage message, StringBuilder logMessage) + { + PipelineRequest request = message.Request; + logMessage.Append(request.Method); + logMessage.Append(' '); + logMessage.AppendLine(request.Uri!.AbsoluteUri); + } + protected virtual void FormatHeaders(PipelineMessage message, StringBuilder logMessage) + { + foreach (var header in message.Request.Headers) + { + if (AllowedHeaders.Contains(header.Key)) + { + logMessage.AppendLine($"{header.Key} : {header.Value}"); + } + else + { + logMessage.AppendLine($"{header.Key} : [REDACTED ...{header.Value.Length} characters]"); + } + } + } + protected virtual void FormatContent(PipelineMessage message, StringBuilder logMessage) + { + var stream = new MemoryStream(); + message.Request.Content!.WriteTo(stream); + stream.Position = 0; + var content = BinaryData.FromStream(stream); + logMessage.AppendLine(content.ToString()); + } +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/WorkspaceClient.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/WorkspaceClient.cs new file mode 100644 index 000000000000..77277b61b5ab --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Core/WorkspaceClient.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ComponentModel; + +namespace Azure.Core; + +public abstract class WorkspaceClient +{ + public abstract TokenCredential Credential { get; } + + public abstract ClientConfiguration? GetConfiguration(string clientId, string? instanceId = default); + + [EditorBrowsable(EditorBrowsableState.Never)] + public ClientCache Subclients { get; } = new ClientCache(); +} + +public readonly struct ClientConfiguration +{ + public ClientConfiguration(string endpoint, string? apiKey = default) + { + Endpoint = endpoint; + ApiKey = apiKey; + CredentialType = apiKey == default ? CredentialType.EntraId : CredentialType.ApiKey; + } + public string Endpoint { get; } + public string? ApiKey { get; } + public CredentialType CredentialType { get; } +} + +public enum CredentialType +{ + EntraId, + ApiKey, +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/GlobalSuppressions.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/GlobalSuppressions.cs new file mode 100644 index 000000000000..4416e7cc32ab --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Usage", "AZC0005:DO provide protected parameterless constructor for mocking.", Justification = "", Scope = "type", Target = "~T:Azure.CloudMachine.CloudMachineClient")] +[assembly: SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "", Scope = "member", Target = "~M:Azure.CloudMachine.CloudMachineClient.#ctor(Azure.Identity.DefaultAzureCredential,Microsoft.Extensions.Configuration.IConfiguration)")] +[assembly: SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "", Scope = "member", Target = "~M:Azure.CloudMachine.CloudMachineClient.#ctor(Azure.Core.TokenCredential,Microsoft.Extensions.Configuration.IConfiguration)")] diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/KeyVaultFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/KeyVaultFeature.cs deleted file mode 100644 index 0f79f8947999..000000000000 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/KeyVaultFeature.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.CloudMachine; -using Azure.Provisioning.Authorization; -using Azure.Provisioning.Expressions; -using Azure.Provisioning.KeyVault; -using Azure.Security.KeyVault.Secrets; - -namespace Azure.Provisioning.CloudMachine.KeyVault; - -public class KeyVaultFeature : CloudMachineFeature -{ - public KeyVaultSku Sku { get; set; } = new KeyVaultSku { Name = KeyVaultSkuName.Standard, Family = KeyVaultSkuFamily.A, }; - - public override void AddTo(CloudMachineInfrastructure cm) - { - // Add a KeyVault to the CloudMachine infrastructure. - KeyVaultService _keyVault = new("cm_kv") - { - Name = cm.Id, - Properties = - new KeyVaultProperties - { - Sku = this.Sku, - TenantId = BicepFunction.GetSubscription().TenantId, - EnabledForDeployment = true, - AccessPolicies = [ - new KeyVaultAccessPolicy() { - ObjectId = cm.PrincipalIdParameter, - Permissions = new IdentityAccessPermissions() { - Secrets = [IdentityAccessSecretPermission.Get, IdentityAccessSecretPermission.Set] - }, - TenantId = cm.Identity.TenantId - } - ] - }, - }; - - cm.AddResource(_keyVault); - - RoleAssignment ra = _keyVault.CreateRoleAssignment(KeyVaultBuiltInRole.KeyVaultAdministrator, RoleManagementPrincipalType.User, cm.PrincipalIdParameter); - cm.AddResource(ra); - - // necessary until ResourceName is settable via AssignRole. - RoleAssignment kvMiRoleAssignment = new RoleAssignment(_keyVault.IdentifierName + "_" + cm.Identity.IdentifierName + "_" + KeyVaultBuiltInRole.GetBuiltInRoleName(KeyVaultBuiltInRole.KeyVaultAdministrator)); - kvMiRoleAssignment.Name = BicepFunction.CreateGuid(_keyVault.Id, cm.Identity.Id, BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", KeyVaultBuiltInRole.KeyVaultAdministrator.ToString())); - kvMiRoleAssignment.Scope = new IdentifierExpression(_keyVault.IdentifierName); - kvMiRoleAssignment.PrincipalType = RoleManagementPrincipalType.ServicePrincipal; - kvMiRoleAssignment.RoleDefinitionId = BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", KeyVaultBuiltInRole.KeyVaultAdministrator.ToString()); - kvMiRoleAssignment.PrincipalId = cm.Identity.PrincipalId; - cm.AddResource(kvMiRoleAssignment); - } -} - -public static class KeyVaultExtensions -{ - public static SecretClient GetKeyVaultSecretClient(this CloudMachineClient client) - { - return new(new($"https://{client.Id}.vault.azure.net/"), client.Credential); - } -} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/CloudMachineClient.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/CloudMachineClient.cs new file mode 100644 index 000000000000..a943c6cb82d6 --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/CloudMachineClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; +using Microsoft.Extensions.Configuration; + +namespace Azure.CloudMachine; + +public partial class CloudMachineClient : CloudMachineWorkspace +{ + public CloudMachineClient(TokenCredential? credential = default, IConfiguration? configuration = default) + : base(credential, configuration) + { + } + + public MessagingServices Messaging => new(this); + public StorageServices Storage => new(this); +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/CloudMachineWorkspace.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/CloudMachineWorkspace.cs new file mode 100644 index 000000000000..f0bafdd3997c --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/CloudMachineWorkspace.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Azure.Core; +using Azure.Identity; +using Microsoft.Extensions.Configuration; + +namespace Azure.CloudMachine; + +public class CloudMachineWorkspace : WorkspaceClient +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public string Id { get; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override TokenCredential Credential { get; } = new ChainedTokenCredential( + new AzureCliCredential(), + new AzureDeveloperCliCredential() + ); + + [SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "")] + public CloudMachineWorkspace(TokenCredential? 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!; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override ClientConfiguration? GetConfiguration(string clientId, string? instanceId = default) + { + switch (clientId) + { + case "Azure.Security.KeyVault.Secrets.SecretClient": + return new ClientConfiguration($"https://{this.Id}.vault.azure.net/"); + case "Azure.Messaging.ServiceBus.ServiceBusClient": + return new ClientConfiguration($"{this.Id}.servicebus.windows.net"); + case "Azure.Messaging.ServiceBus.ServiceBusSender": + if (instanceId == default) instanceId = "cm_default_topic_sender"; + return new ClientConfiguration(instanceId); + case "Azure.Storage.Blobs.BlobContainerClient": + if (instanceId == default) instanceId = "default"; + return new ClientConfiguration($"https://{this.Id}.blob.core.windows.net/{instanceId}"); + case "Azure.AI.OpenAI.AzureOpenAIClient": + string endpoint = $"https://{this.Id}.openai.azure.com"; + string? key = null; // Environment.GetEnvironmentVariable("openai_cm_key"); + if (key != null) + return new ClientConfiguration(endpoint, key); + else + return new ClientConfiguration(endpoint); + case "OpenAI.Chat.ChatClient": + return new ClientConfiguration(this.Id); + default: + throw new Exception($"unknown client {clientId}"); + } + } + + [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; +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/MessagingServices.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/MessagingServices.cs new file mode 100644 index 000000000000..4e7dcbcd9615 --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/MessagingServices.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Messaging.ServiceBus; + +namespace Azure.CloudMachine; + +public readonly struct MessagingServices +{ + private readonly CloudMachineClient _cm; + internal MessagingServices(CloudMachineClient cm) => _cm = cm; + + public void SendMessage(object serializable) + { + ServiceBusSender sender = GetSender(); + + BinaryData serialized = BinaryData.FromObjectAsJson(serializable); + ServiceBusMessage message = new(serialized); +#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). + sender.SendMessageAsync(message).GetAwaiter().GetResult(); +#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). + } + + private ServiceBusSender GetSender() + { + string senderClientId = typeof(ServiceBusSender).FullName; + CloudMachineClient cm = _cm; + ServiceBusSender sender = _cm.Subclients.Get(senderClientId, () => + { + string serviceBusClientId = typeof(ServiceBusClient).FullName; + ServiceBusClient sb = cm.Subclients.Get(serviceBusClientId, () => + { + string sbNamespace = cm.GetConfiguration(serviceBusClientId)!.Value.Endpoint; + ServiceBusClient sb = new(sbNamespace, cm.Credential); + return sb; + }); + + string ServiceBusSenderId = typeof(ServiceBusClient).FullName; + string defaultTopic = cm.GetConfiguration(ServiceBusSenderId)!.Value.Endpoint; + ServiceBusSender sender = sb.CreateSender(defaultTopic); + return sender; + }); + return sender; + } + + public void WhenMessageReceived(Action received) + { + throw new NotImplementedException(); + } +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/StorageServices.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/StorageServices.cs new file mode 100644 index 000000000000..e5541501255a --- /dev/null +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OFX/StorageServices.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; + +namespace Azure.CloudMachine; + +public readonly struct StorageServices +{ + private readonly CloudMachineClient _cm; + internal StorageServices(CloudMachineClient cm) => _cm = cm; + + private BlobContainerClient GetDefaultContainer() + { + string blobContainerClientId = typeof(BlobContainerClient).FullName; + CloudMachineClient cm = _cm; + BlobContainerClient container = cm.Subclients.Get(blobContainerClientId, () => + { + string endpoint = cm.GetConfiguration(blobContainerClientId)!.Value.Endpoint; + BlobContainerClient container = new(new Uri(endpoint), cm.Credential); + return container; + }); + return container; + } + public string UploadBlob(object json, string? name = default) + { + BlobContainerClient container = GetDefaultContainer(); + + if (name == default) name = $"b{Guid.NewGuid()}"; + + container.UploadBlob(name, BinaryData.FromObjectAsJson(json)); + + return name; + } + + public BinaryData DownloadBlob(string name) + { + BlobContainerClient container = GetDefaultContainer(); + BlobClient blob = container.GetBlobClient(name); + BlobDownloadResult result = blob.DownloadContent(); + return result.Content; + } + + public void WhenBlobUploaded(Action function) + { + throw new NotImplementedException(); + } + public void WhenBlobCreated(Func function) + { + throw new NotImplementedException(); + } +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OpenAIFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OpenAIFeature.cs deleted file mode 100644 index c56476ba98a9..000000000000 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/OpenAIFeature.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Azure.CloudMachine; -using Azure.Security.KeyVault.Secrets; - -namespace Azure.Provisioning.CloudMachine.OpenAI; - -public class OpenAIFeature : CloudMachineFeature -{ - public override void AddTo(CloudMachineInfrastructure cm) - { - throw new NotImplementedException(); - } -} - -public static class OpenAIFeatureExtensions -{ - public static SecretClient GetOpenAIClient(this CloudMachineClient client) - { - throw new NotImplementedException(); - //return new(new($"https://{client.Id}.vault.azure.net/"), client.Credential); - } -} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs index 0bcee4f59186..4e8ca445599b 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs @@ -6,8 +6,10 @@ using System; using Azure.Provisioning.CloudMachine; using Azure.Provisioning.CloudMachine.KeyVault; +using Azure.Provisioning.CloudMachine.OpenAI; using Azure.Security.KeyVault.Secrets; using NUnit.Framework; +using OpenAI.Chat; namespace Azure.CloudMachine.Tests; @@ -16,16 +18,14 @@ public class CloudMachineTests [Theory] [TestCase([new string[] { "--init" }])] [TestCase([new string[] { "" }])] - public void Configure(string[] args) + public void Provisioning(string[] args) { if (CloudMachineInfrastructure.Configure(args, (cm) => { - cm.AddFeature(new KeyVaultFeature() - { - //Sku = new KeyVaultSku { Name = KeyVaultSkuName.Premium, Family = KeyVaultSkuFamily.A, } - }); + cm.AddFeature(new KeyVaultFeature()); + cm.AddFeature(new OpenAIFeature("gpt-35-turbo", "0125")); })) return; - CloudMachineClient cm = new(); + CloudMachineWorkspace cm = new(); Console.WriteLine(cm.Id); } @@ -33,53 +33,94 @@ public void Configure(string[] args) [Theory] [TestCase([new string[] { "--init" }])] [TestCase([new string[] { "" }])] - public void KeyVault(string[] args) + public void Storage(string[] args) { if (CloudMachineInfrastructure.Configure(args, (cm) => { - cm.AddFeature(new KeyVaultFeature() - { - //Sku = new KeyVaultSku { Name = KeyVaultSkuName.Premium, Family = KeyVaultSkuFamily.A, } - }); })) return; CloudMachineClient cm = new(); - SecretClient secrets = cm.GetKeyVaultSecretClient(); - secrets.SetSecret("testsecret", "don't tell anybody"); + + var uploaded = cm.Storage.UploadBlob(new + { + Foo = 5, + Bar = true + }); + BinaryData downloaded = cm.Storage.DownloadBlob(uploaded); + Console.WriteLine(downloaded.ToString()); } [Ignore("no recordings yet")] [Theory] [TestCase([new string[] { "--init" }])] [TestCase([new string[] { "" }])] - public void Storage(string[] args) + public void OpenAI(string[] args) { if (CloudMachineInfrastructure.Configure(args, (cm) => { + cm.AddFeature(new OpenAIFeature("gpt-35-turbo", "0125")); })) return; - CloudMachineClient cm = new(); - var uploaded = cm.Upload(new + CloudMachineWorkspace cm = new(); + ChatClient chat = cm.GetOpenAIChatClient(); + ChatCompletion completion = chat.CompleteChat("Is Azure programming easy?"); + + ChatMessageContent content = completion.Content; + foreach (ChatMessageContentPart part in content) { - Foo = 5, - Bar = true - }); - BinaryData downloaded = cm.Download(uploaded); + Console.WriteLine(part.Text); + } } [Ignore("no recordings yet")] [Theory] [TestCase([new string[] { "--init" }])] [TestCase([new string[] { "" }])] - public void Messaging(string[] args) + public void KeyVault(string[] args) { if (CloudMachineInfrastructure.Configure(args, (cm) => { + cm.AddFeature(new KeyVaultFeature()); })) return; + CloudMachineWorkspace cm = new(); + SecretClient secrets = cm.GetKeyVaultSecretsClient(); + secrets.SetSecret("testsecret", "don't tell anybody"); + } + + [Ignore("no recordings yet")] + [Theory] + [TestCase([new string[] { "--init" }])] + [TestCase([new string[] { "" }])] + public void Messaging(string[] args) + { + if (CloudMachineInfrastructure.Configure(args)) return; + CloudMachineClient cm = new(); - cm.Send(new + cm.Messaging.WhenMessageReceived((string message) => Console.WriteLine(message)); + cm.Messaging.SendMessage(new { Foo = 5, Bar = true }); } + + [Ignore("no recordings yet")] + [Theory] + [TestCase([new string[] { "--init" }])] + [TestCase([new string[] { "" }])] + public void Demo(string[] args) + { + if (CloudMachineInfrastructure.Configure(args)) return; + + CloudMachineClient cm = new(); + + // setup + cm.Messaging.WhenMessageReceived((string message) => cm.Storage.UploadBlob(message)); + cm.Storage.WhenBlobUploaded((string content) => { + ChatCompletion completion = cm.GetOpenAIChatClient().CompleteChat(content); + Console.WriteLine(completion.Content[0].Text); + }); + + // go! + cm.Messaging.SendMessage("Tell me something about Redmond, WA."); + } }