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

Added core types needed by CloudMachine incubation to Azure.Core.Experimental #37787

Merged
merged 13 commits into from
Jul 25, 2023
Merged
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
namespace Azure
{
public partial class CloudMachine
{
public CloudMachine(System.IO.Stream configurationContent) { }
public CloudMachine(string configurationFile = "cloudconfig.json") { }
public string DisplayName { get { throw null; } set { } }
public string Id { get { throw null; } }
public string Region { get { throw null; } }
public string SubscriptionId { get { throw null; } }
KrzysztofCwalina marked this conversation as resolved.
Show resolved Hide resolved
public static Azure.CloudMachine Create(string subscriptionId, string region) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public void Save(System.IO.Stream stream) { }
KrzysztofCwalina marked this conversation as resolved.
Show resolved Hide resolved
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
annelo-msft marked this conversation as resolved.
Show resolved Hide resolved
public void Save(string filepath) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct Value
{
Expand Down Expand Up @@ -118,6 +132,12 @@ public void AddOrUpdate(TKey key, TValue? val, int length) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGet(TKey key, out TValue? value) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class, Inherited=false, AllowMultiple=true)]
public partial class ProvisionableTemplateAttribute : System.Attribute
{
public ProvisionableTemplateAttribute(string resourceName) { }
public string ResourceName { get { throw null; } }
}
public abstract partial class SchemaValidator
{
protected SchemaValidator() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
namespace Azure
{
public partial class CloudMachine
{
public CloudMachine(System.IO.Stream configurationContent) { }
public CloudMachine(string configurationFile = "cloudconfig.json") { }
public string DisplayName { get { throw null; } set { } }
public string Id { get { throw null; } }
public string Region { get { throw null; } }
public string SubscriptionId { get { throw null; } }
public static Azure.CloudMachine Create(string subscriptionId, string region) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public void Save(System.IO.Stream stream) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public void Save(string filepath) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct Value
{
Expand Down Expand Up @@ -118,6 +132,12 @@ public void AddOrUpdate(TKey key, TValue? val, int length) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGet(TKey key, out TValue? value) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class, Inherited=false, AllowMultiple=true)]
public partial class ProvisionableTemplateAttribute : System.Attribute
{
public ProvisionableTemplateAttribute(string resourceName) { }
public string ResourceName { get { throw null; } }
}
public abstract partial class SchemaValidator
{
protected SchemaValidator() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
namespace Azure
{
public partial class CloudMachine
{
public CloudMachine(System.IO.Stream configurationContent) { }
public CloudMachine(string configurationFile = "cloudconfig.json") { }
public string DisplayName { get { throw null; } set { } }
public string Id { get { throw null; } }
public string Region { get { throw null; } }
public string SubscriptionId { get { throw null; } }
public static Azure.CloudMachine Create(string subscriptionId, string region) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public void Save(System.IO.Stream stream) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public void Save(string filepath) { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct Value
{
Expand Down Expand Up @@ -118,6 +132,12 @@ public void AddOrUpdate(TKey key, TValue? val, int length) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGet(TKey key, out TValue? value) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class, Inherited=false, AllowMultiple=true)]
public partial class ProvisionableTemplateAttribute : System.Attribute
{
public ProvisionableTemplateAttribute(string resourceName) { }
public string ResourceName { get { throw null; } }
}
public abstract partial class SchemaValidator
{
protected SchemaValidator() { }
Expand Down
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>Experimental types that might eventually move to Azure.Core</Description>
<AssemblyTitle>Microsoft Azure Client Pipeline Experimental Extensions</AssemblyTitle>
Expand Down
197 changes: 197 additions & 0 deletions sdk/core/Azure.Core.Experimental/src/CloudMachine.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.ComponentModel;
using System.IO;
using System.Text.Json;
using Azure.Core;

namespace Azure
{
/// <summary>
/// Stores configuration of a CloudMachine.
/// </summary>
public class CloudMachine
{
/// <summary>
/// Unique identifier of a CloudMachine. It's the name of the resource group of all the CloudMachine resources.
/// </summary>
public string Id { get; }

/// <summary>
/// Friendly name of CloudMachine. It's stored as a Tag of all Azure resources associated with the machine.
/// </summary>
public string DisplayName { get; set; }

/// <summary>
/// Azure subscription ID.
/// </summary>
public string SubscriptionId { get; }

/// <summary>
/// Azure region, e.g. westus2
/// </summary>
public string Region { get; }

private int Version { get; }

/// <summary>
/// Creates a new CloudMachine
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="region"></param>
/// <returns></returns>
/// <remarks>DisplayName is initialized to id. It can be changed by setting the DisplayName property.</remarks>
public static CloudMachine Create(string subscriptionId, string region)
{
if (string.IsNullOrEmpty(subscriptionId)) throw new ArgumentNullException(nameof(subscriptionId));
if (string.IsNullOrEmpty(region)) throw new ArgumentNullException(nameof(region));

var id = GenerateCloudMachineId();
var defaultDisplayName = $"{id}@{DateTime.UtcNow:d}";
return new CloudMachine(id, defaultDisplayName, subscriptionId, region, 1);
}

/// <summary>
/// Loads CloudMachine settings from configurationFile
/// </summary>
/// <param name="configurationFile"></param>
/// <exception cref="InvalidCloudMachineConfigurationException"></exception>
public CloudMachine(string configurationFile = "cloudconfig.json")
{
try
{
byte[] configurationContent = File.ReadAllBytes(configurationFile);
var document = JsonDocument.Parse(configurationContent);
JsonElement json = document.RootElement.GetProperty("CloudMachine");

Id = ReadString(json, "id", configurationFile);
SubscriptionId = ReadString(json, "subscriptionId", configurationFile);
Region = ReadString(json, "region", configurationFile);
DisplayName = ReadString(json, "name", configurationFile);
Version = ReadInt32(json, "version", configurationFile);
}
catch (Exception e) when (e is not InvalidCloudMachineConfigurationException)
{
throw new InvalidCloudMachineConfigurationException(configurationFile, setting: null, e);
}
}

/// <summary>
/// Loads CloudMachine settings from stream.
/// </summary>
/// <param name="configurationContent"></param>
/// <exception cref="InvalidCloudMachineConfigurationException"></exception>
public CloudMachine(Stream configurationContent)
{
try
{
var document = JsonDocument.Parse(configurationContent);
JsonElement json = document.RootElement.GetProperty("CloudMachine");

Id = ReadString(json, "id", nameof(configurationContent));
SubscriptionId = ReadString(json, "subscriptionId", nameof(configurationContent));
Region = ReadString(json, "region", nameof(configurationContent));
DisplayName = ReadString(json, "name", nameof(configurationContent));
Version = ReadInt32(json, "version", nameof(configurationContent));
}
catch (Exception e) when (e is not InvalidCloudMachineConfigurationException)
{
throw new InvalidCloudMachineConfigurationException(nameof(configurationContent), setting: null, e);
}
}

private CloudMachine(string id, string displayName, string subscriptionId, string region, int version)
{
Argument.AssertNotNullOrEmpty(id, nameof(id));
Argument.AssertNotNullOrEmpty(displayName, nameof(displayName));
Argument.AssertNotNullOrEmpty(subscriptionId, nameof(subscriptionId));
Argument.AssertNotNullOrEmpty(region, nameof(region));
Argument.AssertInRange(version, 0, int.MaxValue, nameof(version));

Id = id;
DisplayName = displayName;
SubscriptionId = subscriptionId;
Region = region;
Version = version;
}

/// <summary>
/// Save CloudMachine configuration to a stream.
/// </summary>
/// <param name="stream"></param>
[EditorBrowsable(EditorBrowsableState.Never)]
public void Save(Stream stream)
{
var options = new JsonWriterOptions() { Indented = true };
using var json = new Utf8JsonWriter(stream, options);
json.WriteStartObject();
json.WriteStartObject("CloudMachine");
json.WriteString("id", Id);
json.WriteString("name", DisplayName);
json.WriteString("subscriptionId", SubscriptionId);
json.WriteString("region", Region);
json.WriteNumber("version", Version);
json.WriteEndObject();
json.WriteEndObject();
json.Flush();
}

/// <summary>
/// Save CloudMachine configuration to a file.
/// </summary>
/// <param name="filepath"></param>
[EditorBrowsable(EditorBrowsableState.Never)]
public void Save(string filepath)
{
using var stream = File.OpenWrite(filepath);
Save(stream);
}

private static string ReadString(JsonElement json, string key, string configurationStoreDisplayName)
{
try {
var value = json.GetProperty(key).GetString()!;
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(key);
return value;
}
catch (Exception e) {
throw new InvalidCloudMachineConfigurationException(configurationStoreDisplayName, key, e);
}
}
private static int ReadInt32(JsonElement json, string key, string configurationStoreDisplayName)
{
try {
var value = json.GetProperty(key).GetInt32();
return value;
}
catch (Exception e) {
throw new InvalidCloudMachineConfigurationException(configurationStoreDisplayName, key, e);
}
}

private 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;
}

internal class InvalidCloudMachineConfigurationException : InvalidOperationException
{
public InvalidCloudMachineConfigurationException(string configurationStoreDisplayName, string? setting, Exception innerException) :
base(CreateMessage(configurationStoreDisplayName, setting), innerException)
{ }

public static string CreateMessage(string configurationStoreDisplayName, string? setting)
{
if (setting != null)
return $"ERROR: Configuration setting {setting} not found in {configurationStoreDisplayName} or invalid format.";
else
return $"ERROR: Configuration store {configurationStoreDisplayName} not found or invalid format.";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Azure.Core
{
/// <summary>
/// Attribute used to describe a deployment template.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
public class ProvisionableTemplateAttribute : Attribute
{
/// <summary>
/// Deployment teplate stored in resources.
/// </summary>
/// <param name="resourceName"></param>
public ProvisionableTemplateAttribute(string resourceName)
=> ResourceName = resourceName;

/// <summary>
/// Name of assembly resource file containing a deployment template.
/// </summary>
public string ResourceName { get; }
}
}
46 changes: 46 additions & 0 deletions sdk/core/Azure.Core.Experimental/tests/CloudMachineTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.IO;
using NUnit.Framework;

namespace Azure
{
public class CloudMachineTests
{
[Test]
public void SerializationRoundTrip()
{
var cm = CloudMachine.Create(Guid.NewGuid().ToString(), "westus2");
using var stream = new MemoryStream();
cm.Save(stream);
stream.Position = 0;

var deserialized = new CloudMachine(stream);
Assert.AreEqual(cm.Id, deserialized.Id);
Assert.AreEqual(cm.DisplayName, deserialized.DisplayName);
Assert.AreEqual(cm.Region, deserialized.Region);
Assert.AreEqual(cm.SubscriptionId, deserialized.SubscriptionId);
}

[Test]
public void DefaultCtor()
{
try
{
var cm = CloudMachine.Create(Guid.NewGuid().ToString(), "westus2");
cm.Save("cloudconfig.json");
var deserialized = new CloudMachine();
Assert.AreEqual(cm.Id, deserialized.Id);
Assert.AreEqual(cm.DisplayName, deserialized.DisplayName);
Assert.AreEqual(cm.Region, deserialized.Region);
Assert.AreEqual(cm.SubscriptionId, deserialized.SubscriptionId);
}
finally
{
File.Delete("cloudconfig.json");
}
}
}
}