DscResourcesMap.cs (9224B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="DscResourcesMap.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers 8 { 9 using System; 10 using System.Collections.Generic; 11 using System.Linq; 12 using Microsoft.Management.Configuration.Processor.Helpers; 13 using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; 14 15 /// <summary> 16 /// DscResources in the system. Get-DscResource looks the entire PSModulePath to find resources every time, so 17 /// calling it multiples times is expensive. This class will store them in memory per configuration set. 18 /// The resources are stored in a dictionary in the following hierarchy. 19 /// - Module name 20 /// - Resource name 21 /// - Module Version 22 /// - DscResourceInfo obj. 23 /// There are some resources that don't have a module name. These are modules that live under 24 /// C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources. 25 /// This is not currently used. For now, we let PowerShell handle all the versioning. In order to avoid 26 /// calls to Get-DscResource every time a unit is set, this class needs to be updated to handle min and max 27 /// version. As it is, it just does RequiredVersion. 28 /// </summary> 29 internal sealed class DscResourcesMap 30 { 31 private readonly Dictionary<string, ModuleDscResources> resources = new Dictionary<string, ModuleDscResources>(); 32 33 /// <summary> 34 /// Initializes a new instance of the <see cref="DscResourcesMap"/> class. 35 /// </summary> 36 /// <param name="resources">DSC Resources.</param> 37 public DscResourcesMap(IReadOnlyList<DscResourceInfoInternal> resources) 38 { 39 foreach (var resource in resources) 40 { 41 if (string.IsNullOrEmpty(resource.Name)) 42 { 43 throw new ArgumentException(nameof(resource.Name)); 44 } 45 46 if (resource.Version is null) 47 { 48 throw new ArgumentException(nameof(resource.Version)); 49 } 50 51 this.Insert(resource); 52 } 53 } 54 55 /// <summary> 56 /// Looks for a DSC resource in the specified module. 57 /// </summary> 58 /// <param name="dscResourceName">DSC resource name.</param> 59 /// <param name="moduleName">Optional module name.</param> 60 /// <param name="version">Optional module version.</param> 61 /// <returns>DscResourceInfoInternal. Null if not found.</returns> 62 public DscResourceInfoInternal? GetResource(string dscResourceName, string? moduleName, Version? version) 63 { 64 string normalizedResourceName = StringHelpers.Normalize(dscResourceName); 65 string? normalizedModuleName = null; 66 if (moduleName is not null) 67 { 68 normalizedModuleName = StringHelpers.Normalize(moduleName); 69 } 70 71 if (normalizedModuleName is null) 72 { 73 foreach (var module in this.resources.Values) 74 { 75 var resourceInfo = module.GetDscResourceInfo(normalizedResourceName, version); 76 if (resourceInfo is not null) 77 { 78 return resourceInfo; 79 } 80 } 81 } 82 else 83 { 84 if (this.resources.TryGetValue(normalizedModuleName, out ModuleDscResources? module)) 85 { 86 return module.GetDscResourceInfo(normalizedResourceName, version); 87 } 88 } 89 90 return null; 91 } 92 93 /// <summary> 94 /// Does a DSC resource exists. 95 /// </summary> 96 /// <param name="dscResourceName">DSC resource name.</param> 97 /// <param name="moduleName">Optional module name.</param> 98 /// <param name="version">Optional module version.</param> 99 /// <returns>True if resource exists.</returns> 100 public bool Exists(string dscResourceName, string? moduleName, Version? version) 101 { 102 return this.GetResource(dscResourceName, moduleName, version) != null; 103 } 104 105 private void Insert(DscResourceInfoInternal dscResourceInfo) 106 { 107 string? normalizedModuleName = dscResourceInfo.NormalizedModuleName; 108 109 // There are some resources that doesn't have module, use empty as key. 110 string moduleKey = string.Empty; 111 if (normalizedModuleName is not null) 112 { 113 moduleKey = normalizedModuleName; 114 } 115 116 if (!this.resources.ContainsKey(moduleKey)) 117 { 118 this.resources.Add(moduleKey, new ModuleDscResources(moduleKey, dscResourceInfo)); 119 } 120 else 121 { 122 this.resources[moduleKey].AddResource(moduleKey, dscResourceInfo); 123 } 124 } 125 126 /// <summary> 127 /// Represents a map versions for a DscResource. 128 /// </summary> 129 private class DscResourceVersions : Dictionary<Version, DscResourceInfoInternal> 130 { 131 private readonly string resourceName; 132 133 public DscResourceVersions(DscResourceInfoInternal dscResourceInfo) 134 { 135 this.resourceName = dscResourceInfo.NormalizedName; 136 this.AddVersion(dscResourceInfo); 137 } 138 139 /// <summary> 140 /// Adds a resource version. 141 /// </summary> 142 /// <param name="dscResourceInfo">DscResourceInfo.</param> 143 public void AddVersion(DscResourceInfoInternal dscResourceInfo) 144 { 145 if (this.resourceName != dscResourceInfo.NormalizedName) 146 { 147 throw new ArgumentException(dscResourceInfo.NormalizedName); 148 } 149 150 // There are some system resources without version or module name. 151 if (dscResourceInfo.Version is null) 152 { 153 this.Add(new Version(), dscResourceInfo); 154 } 155 else 156 { 157 // Get-DscResource will fail if the same module with same 158 // resources and same version exists in the module path 159 // so this shouldn't happen. Either way, we should throw there or here. 160 this.Add(dscResourceInfo.Version, dscResourceInfo); 161 } 162 } 163 164 public DscResourceInfoInternal? GetDscResourceInfo(Version? version) 165 { 166 if (version is null) 167 { 168 // Sort keys, get the latest. 169 var newestVersion = this.Keys.OrderByDescending(v => v).First(); 170 return this[newestVersion]; 171 } 172 173 if (this.TryGetValue(version, out DscResourceInfoInternal? resourceInfo)) 174 { 175 return resourceInfo; 176 } 177 178 return null; 179 } 180 } 181 182 /// <summary> 183 /// Represent a map of resources for a module. 184 /// </summary> 185 private class ModuleDscResources : Dictionary<string, DscResourceVersions> 186 { 187 private readonly string normalizedModuleName; 188 189 public ModuleDscResources(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) 190 { 191 this.normalizedModuleName = normalizedModuleName; 192 this.AddResource(this.normalizedModuleName, dscResourceInfo); 193 } 194 195 public void AddResource(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) 196 { 197 if (this.normalizedModuleName != normalizedModuleName) 198 { 199 throw new ArgumentException(nameof(normalizedModuleName)); 200 } 201 202 var normalizedResourceName = dscResourceInfo.NormalizedName; 203 if (!this.ContainsKey(normalizedResourceName)) 204 { 205 this.Add(normalizedResourceName, new DscResourceVersions(dscResourceInfo)); 206 } 207 else 208 { 209 this[normalizedResourceName].AddVersion(dscResourceInfo); 210 } 211 } 212 213 public DscResourceInfoInternal? GetDscResourceInfo(string dscResourceName, Version? version) 214 { 215 if (this.TryGetValue(dscResourceName, out DscResourceVersions? versions)) 216 { 217 return versions.GetDscResourceInfo(version); 218 } 219 220 return null; 221 } 222 } 223 } 224 }