|
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +using System; |
| 5 | +using System.ComponentModel; |
| 6 | +using System.IO; |
| 7 | +using System.Text.Json; |
| 8 | +using Azure.Core; |
| 9 | + |
| 10 | +namespace Azure |
| 11 | +{ |
| 12 | + /// <summary> |
| 13 | + /// Stores configuration of a CloudMachine. |
| 14 | + /// </summary> |
| 15 | + public class CloudMachine |
| 16 | + { |
| 17 | + /// <summary> |
| 18 | + /// Unique identifier of a CloudMachine. It's the name of the resource group of all the CloudMachine resources. |
| 19 | + /// </summary> |
| 20 | + public string Id { get; } |
| 21 | + |
| 22 | + /// <summary> |
| 23 | + /// Friendly name of CloudMachine. It's stored as a Tag of all Azure resources associated with the machine. |
| 24 | + /// </summary> |
| 25 | + public string DisplayName { get; set; } |
| 26 | + |
| 27 | + /// <summary> |
| 28 | + /// Azure subscription ID. |
| 29 | + /// </summary> |
| 30 | + public string SubscriptionId { get; } |
| 31 | + |
| 32 | + /// <summary> |
| 33 | + /// Azure region, e.g. westus2 |
| 34 | + /// </summary> |
| 35 | + public string Region { get; } |
| 36 | + |
| 37 | + private int Version { get; } |
| 38 | + |
| 39 | + /// <summary> |
| 40 | + /// Creates a new CloudMachine |
| 41 | + /// </summary> |
| 42 | + /// <param name="subscriptionId"></param> |
| 43 | + /// <param name="region"></param> |
| 44 | + /// <returns></returns> |
| 45 | + /// <remarks>DisplayName is initialized to id. It can be changed by setting the DisplayName property.</remarks> |
| 46 | + public static CloudMachine Create(string subscriptionId, string region) |
| 47 | + { |
| 48 | + if (string.IsNullOrEmpty(subscriptionId)) throw new ArgumentNullException(nameof(subscriptionId)); |
| 49 | + if (string.IsNullOrEmpty(region)) throw new ArgumentNullException(nameof(region)); |
| 50 | + |
| 51 | + var id = GenerateCloudMachineId(); |
| 52 | + var defaultDisplayName = $"{id}@{DateTime.UtcNow:d}"; |
| 53 | + return new CloudMachine(id, defaultDisplayName, subscriptionId, region, 1); |
| 54 | + } |
| 55 | + |
| 56 | + /// <summary> |
| 57 | + /// Loads CloudMachine settings from configurationFile |
| 58 | + /// </summary> |
| 59 | + /// <param name="configurationFile"></param> |
| 60 | + /// <exception cref="InvalidCloudMachineConfigurationException"></exception> |
| 61 | + public CloudMachine(string configurationFile = "cloudconfig.json") |
| 62 | + { |
| 63 | + try |
| 64 | + { |
| 65 | + byte[] configurationContent = File.ReadAllBytes(configurationFile); |
| 66 | + var document = JsonDocument.Parse(configurationContent); |
| 67 | + JsonElement json = document.RootElement.GetProperty("CloudMachine"); |
| 68 | + |
| 69 | + Id = ReadString(json, "id", configurationFile); |
| 70 | + SubscriptionId = ReadString(json, "subscriptionId", configurationFile); |
| 71 | + Region = ReadString(json, "region", configurationFile); |
| 72 | + DisplayName = ReadString(json, "name", configurationFile); |
| 73 | + Version = ReadInt32(json, "version", configurationFile); |
| 74 | + } |
| 75 | + catch (Exception e) when (e is not InvalidCloudMachineConfigurationException) |
| 76 | + { |
| 77 | + throw new InvalidCloudMachineConfigurationException(configurationFile, setting: null, e); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + /// <summary> |
| 82 | + /// Loads CloudMachine settings from stream. |
| 83 | + /// </summary> |
| 84 | + /// <param name="configurationContent"></param> |
| 85 | + /// <exception cref="InvalidCloudMachineConfigurationException"></exception> |
| 86 | + public CloudMachine(Stream configurationContent) |
| 87 | + { |
| 88 | + try |
| 89 | + { |
| 90 | + var document = JsonDocument.Parse(configurationContent); |
| 91 | + JsonElement json = document.RootElement.GetProperty("CloudMachine"); |
| 92 | + |
| 93 | + Id = ReadString(json, "id", nameof(configurationContent)); |
| 94 | + SubscriptionId = ReadString(json, "subscriptionId", nameof(configurationContent)); |
| 95 | + Region = ReadString(json, "region", nameof(configurationContent)); |
| 96 | + DisplayName = ReadString(json, "name", nameof(configurationContent)); |
| 97 | + Version = ReadInt32(json, "version", nameof(configurationContent)); |
| 98 | + } |
| 99 | + catch (Exception e) when (e is not InvalidCloudMachineConfigurationException) |
| 100 | + { |
| 101 | + throw new InvalidCloudMachineConfigurationException(nameof(configurationContent), setting: null, e); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + private CloudMachine(string id, string displayName, string subscriptionId, string region, int version) |
| 106 | + { |
| 107 | + Argument.AssertNotNullOrEmpty(id, nameof(id)); |
| 108 | + Argument.AssertNotNullOrEmpty(displayName, nameof(displayName)); |
| 109 | + Argument.AssertNotNullOrEmpty(subscriptionId, nameof(subscriptionId)); |
| 110 | + Argument.AssertNotNullOrEmpty(region, nameof(region)); |
| 111 | + Argument.AssertInRange(version, 0, int.MaxValue, nameof(version)); |
| 112 | + |
| 113 | + Id = id; |
| 114 | + DisplayName = displayName; |
| 115 | + SubscriptionId = subscriptionId; |
| 116 | + Region = region; |
| 117 | + Version = version; |
| 118 | + } |
| 119 | + |
| 120 | + /// <summary> |
| 121 | + /// Save CloudMachine configuration to a stream. |
| 122 | + /// </summary> |
| 123 | + /// <param name="stream"></param> |
| 124 | + [EditorBrowsable(EditorBrowsableState.Never)] |
| 125 | + public void Save(Stream stream) |
| 126 | + { |
| 127 | + var options = new JsonWriterOptions() { Indented = true }; |
| 128 | + using var json = new Utf8JsonWriter(stream, options); |
| 129 | + json.WriteStartObject(); |
| 130 | + json.WriteStartObject("CloudMachine"); |
| 131 | + json.WriteString("id", Id); |
| 132 | + json.WriteString("name", DisplayName); |
| 133 | + json.WriteString("subscriptionId", SubscriptionId); |
| 134 | + json.WriteString("region", Region); |
| 135 | + json.WriteNumber("version", Version); |
| 136 | + json.WriteEndObject(); |
| 137 | + json.WriteEndObject(); |
| 138 | + json.Flush(); |
| 139 | + } |
| 140 | + |
| 141 | + /// <summary> |
| 142 | + /// Save CloudMachine configuration to a file. |
| 143 | + /// </summary> |
| 144 | + /// <param name="filepath"></param> |
| 145 | + [EditorBrowsable(EditorBrowsableState.Never)] |
| 146 | + public void Save(string filepath) |
| 147 | + { |
| 148 | + using var stream = File.OpenWrite(filepath); |
| 149 | + Save(stream); |
| 150 | + } |
| 151 | + |
| 152 | + private static string ReadString(JsonElement json, string key, string configurationStoreDisplayName) |
| 153 | + { |
| 154 | + try { |
| 155 | + var value = json.GetProperty(key).GetString()!; |
| 156 | + if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(key); |
| 157 | + return value; |
| 158 | + } |
| 159 | + catch (Exception e) { |
| 160 | + throw new InvalidCloudMachineConfigurationException(configurationStoreDisplayName, key, e); |
| 161 | + } |
| 162 | + } |
| 163 | + private static int ReadInt32(JsonElement json, string key, string configurationStoreDisplayName) |
| 164 | + { |
| 165 | + try { |
| 166 | + var value = json.GetProperty(key).GetInt32(); |
| 167 | + return value; |
| 168 | + } |
| 169 | + catch (Exception e) { |
| 170 | + throw new InvalidCloudMachineConfigurationException(configurationStoreDisplayName, key, e); |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + private static string GenerateCloudMachineId() |
| 175 | + { |
| 176 | + var guid = Guid.NewGuid(); |
| 177 | + var guidString = guid.ToString("N"); |
| 178 | + var cnId = "cm" + guidString.Substring(0, 15); // we can increase it to 20, but the template name cannot be that long |
| 179 | + return cnId; |
| 180 | + } |
| 181 | + |
| 182 | + internal class InvalidCloudMachineConfigurationException : InvalidOperationException |
| 183 | + { |
| 184 | + public InvalidCloudMachineConfigurationException(string configurationStoreDisplayName, string? setting, Exception innerException) : |
| 185 | + base(CreateMessage(configurationStoreDisplayName, setting), innerException) |
| 186 | + { } |
| 187 | + |
| 188 | + public static string CreateMessage(string configurationStoreDisplayName, string? setting) |
| 189 | + { |
| 190 | + if (setting != null) |
| 191 | + return $"ERROR: Configuration setting {setting} not found in {configurationStoreDisplayName} or invalid format."; |
| 192 | + else |
| 193 | + return $"ERROR: Configuration store {configurationStoreDisplayName} not found or invalid format."; |
| 194 | + } |
| 195 | + } |
| 196 | + } |
| 197 | +} |
0 commit comments