Skip to content

Commit

Permalink
Added core types needed by CloudMachine incubation to Azure.Core.Expe…
Browse files Browse the repository at this point in the history
…rimental (#37787)

* Added core types needed by CloudMachine incubation

* removed config as it's harder than I thought and we don't need it now

* PR feedback

* PR feedback

* removed config package

* more PR feedback fixes

* PR feedback

* fixed test bug

* updated API files

* updates APIs
  • Loading branch information
KrzysztofCwalina authored Jul 25, 2023
1 parent c12f465 commit d9275d8
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 1 deletion.
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,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");
}
}
}
}

0 comments on commit d9275d8

Please sign in to comment.