Move cross platform DSC code to a subsystem (#15127)

This commit is contained in:
Andrew
2021-05-20 17:06:59 -07:00
committed by GitHub
parent 5febcad3db
commit 8d017d8a75
13 changed files with 149 additions and 2708 deletions
@@ -13,6 +13,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.DscSubsystem" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
namespace System.Management.Automation
{
@@ -1,68 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Security;
namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform
{
/// <summary>
/// Class that does high level Cim schema parsing.
/// </summary>
internal class CimDSCParser
{
private readonly JsonDeserializer _jsonDeserializer;
internal CimDSCParser()
{
_jsonDeserializer = JsonDeserializer.Create();
}
internal IEnumerable<PSObject> ParseSchemaJson(string filePath, bool useNewRunspace = false)
{
try
{
string json = File.ReadAllText(filePath);
string fileNameDefiningClass = Path.GetFileNameWithoutExtension(filePath);
int dotIndex = fileNameDefiningClass.IndexOf(".schema", StringComparison.InvariantCultureIgnoreCase);
if (dotIndex != -1)
{
fileNameDefiningClass = fileNameDefiningClass.Substring(0, dotIndex);
}
IEnumerable<PSObject> result = _jsonDeserializer.DeserializeClasses(json, useNewRunspace);
foreach (dynamic classObject in result)
{
string superClassName = classObject.SuperClassName;
string className = classObject.ClassName;
if (string.Equals(superClassName, "OMI_BaseResource", StringComparison.OrdinalIgnoreCase))
{
// Get the name of the file without schema.mof/json extension
if (!className.Equals(fileNameDefiningClass, StringComparison.OrdinalIgnoreCase))
{
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
ParserStrings.ClassNameNotSameAsDefiningFile, className, fileNameDefiningClass);
throw e;
}
}
}
return result;
}
catch (Exception exception)
{
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
exception, ParserStrings.CimDeserializationError, filePath);
e.SetErrorId("CimDeserializationError");
throw e;
}
}
}
}
@@ -1,72 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform
{
internal class JsonDeserializer
{
#region Constructors
/// <summary>
/// Instantiates a default deserializer.
/// </summary>
/// <returns>Default deserializer.</returns>
public static JsonDeserializer Create()
{
return new JsonDeserializer();
}
#endregion Constructors
#region Methods
/// <summary>
/// Returns schema of Cim classes from specified json file.
/// </summary>
/// <param name="json">Json text to deserialize.</param>
/// <param name="useNewRunspace">If a new runspace should be used.</param>
/// <returns>Deserialized PSObjects.</returns>
public IEnumerable<PSObject> DeserializeClasses(string json, bool useNewRunspace = false)
{
if (string.IsNullOrEmpty(json))
{
throw new ArgumentNullException(nameof(json));
}
System.Management.Automation.PowerShell powerShell = null;
if (useNewRunspace)
{
// currently using RunspaceMode.NewRunspace will reset PSModulePath env var for the entire process
// this is something we want to avoid in DSC GuestConfigAgent scenario, so we use following workaround
var s_iss = InitialSessionState.CreateDefault();
s_iss.EnvironmentVariables.Add(
new SessionStateVariableEntry(
"PSModulePath",
Environment.GetEnvironmentVariable("PSModulePath"),
description: null));
powerShell = System.Management.Automation.PowerShell.Create(s_iss);
}
else
{
powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
}
using (powerShell)
{
return powerShell.AddCommand("Microsoft.PowerShell.Utility\\ConvertFrom-Json")
.AddParameter("InputObject", json)
.AddParameter("Depth", 100) // maximum supported by cmdlet
.Invoke();
}
}
#endregion Methods
}
}
File diff suppressed because it is too large Load Diff
@@ -9,6 +9,8 @@ using System.Management.Automation.Language;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.DSC;
namespace System.Management.Automation
{
@@ -1721,9 +1723,16 @@ namespace System.Management.Automation
foreach (var keyword in matchedResults)
{
string usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache.NewApiIsUsed
? Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache.GetDSCResourceUsageString(keyword)
: Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword);
string usageString = string.Empty;
ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem<ICrossPlatformDsc>();
if (dscSubsystem != null)
{
usageString = dscSubsystem.GetDSCResourceUsageString(keyword);
}
else
{
usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword);
}
if (results == null)
{
@@ -125,9 +125,6 @@ namespace System.Management.Automation
new ExperimentalFeature(
name: "PSNotApplyErrorActionToStderr",
description: "Don't have $ErrorActionPreference affect stderr output"),
new ExperimentalFeature(
name: "PS7DscSupport",
description: "Support the cross-platform class-based DSC"),
new ExperimentalFeature(
name: "PSSubsystemPluginModel",
description: "A plugin model for registering and un-registering PowerShell subsystems"),
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Management.Automation.Language;
namespace System.Management.Automation.Subsystem.DSC
{
/// <summary>
/// Interface for implementing a cross platform desired state configuration component.
/// </summary>
public interface ICrossPlatformDsc : ISubsystem
{
/// <summary>
/// Subsystem kind.
/// </summary>
SubsystemKind ISubsystem.Kind => SubsystemKind.CrossPlatformDsc;
/// <summary>
/// Default implementation. No function is required for this subsystem.
/// </summary>
Dictionary<string, string>? ISubsystem.FunctionsToDefine => null;
/// <summary>
/// DSC initializer function.
/// </summary>
void LoadDefaultKeywords(Collection<Exception> errors);
/// <summary>
/// Clear internal class caches.
/// </summary>
void ClearCache();
/// <summary>
/// Returns resource usage string.
/// </summary>
string GetDSCResourceUsageString(DynamicKeyword keyword);
/// <summary>
/// Checks if a string is one of dynamic keywords that can be used in both configuration and meta configuration.
/// </summary>
bool IsSystemResourceName(string name);
/// <summary>
/// Checks if a string matches default module name used for meta configuration resources.
/// </summary>
bool IsDefaultModuleNameForMetaConfigResource(string name);
}
}
@@ -17,6 +17,11 @@ namespace System.Management.Automation.Subsystem
/// Component that provides predictive suggestions to commandline input.
/// </summary>
CommandPredictor = 1,
/// <summary>
/// Cross platform desired state configuration component.
/// </summary>
CrossPlatformDsc = 2,
}
/// <summary>
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Subsystem.DSC;
using System.Management.Automation.Subsystem.Prediction;
namespace System.Management.Automation.Subsystem
@@ -29,6 +30,11 @@ namespace System.Management.Automation.Subsystem
SubsystemKind.CommandPredictor,
allowUnregistration: true,
allowMultipleRegistration: true),
SubsystemInfo.Create<ICrossPlatformDsc>(
SubsystemKind.CrossPlatformDsc,
allowUnregistration: true,
allowMultipleRegistration: false),
};
var subSystemTypeMap = new Dictionary<Type, SubsystemInfo>(subsystems.Length);
@@ -12,6 +12,8 @@ using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.DSC;
using Dsc = Microsoft.PowerShell.DesiredStateConfiguration.Internal;
namespace System.Management.Automation.Language
@@ -2933,7 +2935,6 @@ namespace System.Management.Automation.Language
//
Runspaces.Runspace localRunspace = null;
bool topLevel = false;
bool useCrossPlatformSchema = false;
try
{
// At this point, we'll need a runspace to use to hold the metadata for the parse. If there is no
@@ -2996,38 +2997,13 @@ namespace System.Management.Automation.Language
{
// Load the default CIM keywords
Collection<Exception> CIMKeywordErrors = new Collection<Exception>();
if (ExperimentalFeature.IsEnabled(Dsc.CrossPlatform.DscClassCache.DscExperimentalFeatureName))
// DscSubsystem is auto-registered when PSDesiredStateConfiguration v3 module is loaded
// so if DscSubsystem is registered that means user intention to use v3 APIs.
ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem<ICrossPlatformDsc>();
if (dscSubsystem != null)
{
// In addition to checking if experimental feature is enabled
// also check if PSDesiredStateConfiguration is already loaded
// if pre-v3 is already loaded then use old mof-based APIs
// otherwise use json-based APIs
p.AddCommand(new CmdletInfo("Get-Module", typeof(Microsoft.PowerShell.Commands.GetModuleCommand)));
p.AddParameter("Name", "PSDesiredStateConfiguration");
bool prev3IsLoaded = false;
foreach (PSModuleInfo moduleInfo in p.Invoke<PSModuleInfo>())
{
if (moduleInfo.Version.Major < 3)
{
prev3IsLoaded = true;
break;
}
}
p.Commands.Clear();
useCrossPlatformSchema = !prev3IsLoaded;
if (useCrossPlatformSchema)
{
Dsc.CrossPlatform.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors);
}
else
{
Dsc.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors);
}
dscSubsystem.LoadDefaultKeywords(CIMKeywordErrors);
}
else
{
@@ -3274,9 +3250,10 @@ namespace System.Management.Automation.Language
// Clear out all of the cached classes and keywords.
// They will need to be reloaded when the generated function is actually run.
//
if (useCrossPlatformSchema)
ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem<ICrossPlatformDsc>();
if (dscSubsystem != null)
{
Dsc.CrossPlatform.DscClassCache.ClearCache();
dscSubsystem.ClearCache();
}
else
{
@@ -11,6 +11,8 @@ using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.PowerShell;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.DSC;
using Microsoft.PowerShell.DesiredStateConfiguration.Internal;
namespace System.Management.Automation.Language
@@ -1405,7 +1407,10 @@ namespace System.Management.Automation.Language
{
StringConstantExpressionAst nameAst = dynamicKeywordStatementAst.CommandElements[0] as StringConstantExpressionAst;
Diagnostics.Assert(nameAst != null, "nameAst should never be null");
if (!DscClassCache.SystemResourceNames.Contains(nameAst.Extent.Text.Trim()))
var extentText = nameAst.Extent.Text.Trim();
ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem<ICrossPlatformDsc>();
var extentTextIsASystemResourceName = (dscSubsystem != null) ? dscSubsystem.IsSystemResourceName(extentText) : DscClassCache.SystemResourceNames.Contains(extentText);
if (!extentTextIsASystemResourceName)
{
if (configAst.ConfigurationType == ConfigurationType.Meta && !dynamicKeywordStatementAst.Keyword.IsMetaDSCResource())
{
@@ -12,6 +12,8 @@ using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.PowerShell.Commands;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.DSC;
using Microsoft.PowerShell.DesiredStateConfiguration.Internal;
namespace System.Management.Automation.Language
@@ -361,7 +363,15 @@ namespace System.Management.Automation.Language
string implementingModule = keyword.ImplementingModule;
if (implementingModule != null)
{
return implementingModule.Equals(DscClassCache.DefaultModuleInfoForMetaConfigResource.Item1, StringComparison.OrdinalIgnoreCase);
ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem<ICrossPlatformDsc>();
if (dscSubsystem != null)
{
dscSubsystem.IsDefaultModuleNameForMetaConfigResource(implementingModule);
}
else
{
return implementingModule.Equals(DscClassCache.DefaultModuleInfoForMetaConfigResource.Item1, StringComparison.OrdinalIgnoreCase);
}
}
return false;
+44 -23
View File
@@ -4,6 +4,7 @@
using System;
using System.Collections.ObjectModel;
using System.Management.Automation.Subsystem;
using System.Management.Automation.Subsystem.DSC;
using System.Management.Automation.Subsystem.Prediction;
using System.Threading;
using Xunit;
@@ -20,8 +21,7 @@ namespace PSTests.Sequential
predictor2 = MyPredictor.SlowPredictor;
}
// This method needs to be updated when there are more than 1 subsystem defined.
private static void VerifySubsystemMetadata(SubsystemInfo ssInfo)
private static void VerifyCommandPredictorMetadata(SubsystemInfo ssInfo)
{
Assert.Equal(SubsystemKind.CommandPredictor, ssInfo.Kind);
Assert.Equal(typeof(ICommandPredictor), ssInfo.SubsystemType);
@@ -31,26 +31,51 @@ namespace PSTests.Sequential
Assert.Empty(ssInfo.RequiredFunctions);
}
private static void VerifyCrossPlatformDscMetadata(SubsystemInfo ssInfo)
{
Assert.Equal(SubsystemKind.CrossPlatformDsc, ssInfo.Kind);
Assert.Equal(typeof(ICrossPlatformDsc), ssInfo.SubsystemType);
Assert.True(ssInfo.AllowUnregistration);
Assert.False(ssInfo.AllowMultipleRegistration);
Assert.Empty(ssInfo.RequiredCmdlets);
Assert.Empty(ssInfo.RequiredFunctions);
}
[Fact]
public static void GetSubsystemInfo()
{
SubsystemInfo ssInfo = SubsystemManager.GetSubsystemInfo(typeof(ICommandPredictor));
SubsystemInfo predictorInfo = SubsystemManager.GetSubsystemInfo(typeof(ICommandPredictor));
VerifySubsystemMetadata(ssInfo);
Assert.False(ssInfo.IsRegistered);
Assert.Empty(ssInfo.Implementations);
VerifyCommandPredictorMetadata(predictorInfo);
Assert.False(predictorInfo.IsRegistered);
Assert.Empty(predictorInfo.Implementations);
SubsystemInfo ssInfo2 = SubsystemManager.GetSubsystemInfo(SubsystemKind.CommandPredictor);
Assert.Same(ssInfo2, ssInfo);
SubsystemInfo predictorInfo2 = SubsystemManager.GetSubsystemInfo(SubsystemKind.CommandPredictor);
Assert.Same(predictorInfo2, predictorInfo);
SubsystemInfo crossPlatformDscInfo = SubsystemManager.GetSubsystemInfo(typeof(ICrossPlatformDsc));
VerifyCrossPlatformDscMetadata(crossPlatformDscInfo);
Assert.False(crossPlatformDscInfo.IsRegistered);
Assert.Empty(crossPlatformDscInfo.Implementations);
SubsystemInfo crossPlatformDscInfo2 = SubsystemManager.GetSubsystemInfo(SubsystemKind.CrossPlatformDsc);
Assert.Same(crossPlatformDscInfo2, crossPlatformDscInfo);
ReadOnlyCollection<SubsystemInfo> ssInfos = SubsystemManager.GetAllSubsystemInfo();
Assert.Single(ssInfos);
Assert.Same(ssInfos[0], ssInfo);
Assert.Equal(2, ssInfos.Count);
Assert.Same(ssInfos[0], predictorInfo);
Assert.Same(ssInfos[1], crossPlatformDscInfo);
ICommandPredictor impl = SubsystemManager.GetSubsystem<ICommandPredictor>();
Assert.Null(impl);
ReadOnlyCollection<ICommandPredictor> impls = SubsystemManager.GetSubsystems<ICommandPredictor>();
Assert.Empty(impls);
ICommandPredictor predictorImpl = SubsystemManager.GetSubsystem<ICommandPredictor>();
Assert.Null(predictorImpl);
ReadOnlyCollection<ICommandPredictor> predictorImpls = SubsystemManager.GetSubsystems<ICommandPredictor>();
Assert.Empty(predictorImpls);
ICrossPlatformDsc crossPlatformDscImpl = SubsystemManager.GetSubsystem<ICrossPlatformDsc>();
Assert.Null(crossPlatformDscImpl);
ReadOnlyCollection<ICrossPlatformDsc> crossPlatformDscImpls = SubsystemManager.GetSubsystems<ICrossPlatformDsc>();
Assert.Empty(crossPlatformDscImpls);
}
[Fact]
@@ -73,7 +98,8 @@ namespace PSTests.Sequential
// Now validate the SubsystemInfo of the 'ICommandPredictor' subsystem
SubsystemInfo ssInfo = SubsystemManager.GetSubsystemInfo(typeof(ICommandPredictor));
VerifySubsystemMetadata(ssInfo);
SubsystemInfo crossPlatformDscInfo = SubsystemManager.GetSubsystemInfo(typeof(ICrossPlatformDsc));
VerifyCommandPredictorMetadata(ssInfo);
Assert.True(ssInfo.IsRegistered);
Assert.Single(ssInfo.Implementations);
@@ -85,11 +111,6 @@ namespace PSTests.Sequential
Assert.Equal(SubsystemKind.CommandPredictor, implInfo.Kind);
Assert.Same(typeof(MyPredictor), implInfo.ImplementationType);
// Now validate the all-subsystem-info collection.
ReadOnlyCollection<SubsystemInfo> ssInfos = SubsystemManager.GetAllSubsystemInfo();
Assert.Single(ssInfos);
Assert.Same(ssInfos[0], ssInfo);
// Now validate the subsystem implementation itself.
ICommandPredictor impl = SubsystemManager.GetSubsystem<ICommandPredictor>();
Assert.Same(impl, predictor1);
@@ -113,7 +134,7 @@ namespace PSTests.Sequential
SubsystemManager.RegisterSubsystem(SubsystemKind.CommandPredictor, predictor2);
// Now validate the SubsystemInfo of the 'ICommandPredictor' subsystem
VerifySubsystemMetadata(ssInfo);
VerifyCommandPredictorMetadata(ssInfo);
Assert.True(ssInfo.IsRegistered);
Assert.Equal(2, ssInfo.Implementations.Count);
@@ -158,7 +179,7 @@ namespace PSTests.Sequential
SubsystemManager.UnregisterSubsystem<ICommandPredictor>(predictor1.Id);
SubsystemInfo ssInfo = SubsystemManager.GetSubsystemInfo(SubsystemKind.CommandPredictor);
VerifySubsystemMetadata(ssInfo);
VerifyCommandPredictorMetadata(ssInfo);
Assert.True(ssInfo.IsRegistered);
Assert.Single(ssInfo.Implementations);
@@ -179,7 +200,7 @@ namespace PSTests.Sequential
// Unregister 'predictor2'
SubsystemManager.UnregisterSubsystem(SubsystemKind.CommandPredictor, predictor2.Id);
VerifySubsystemMetadata(ssInfo);
VerifyCommandPredictorMetadata(ssInfo);
Assert.False(ssInfo.IsRegistered);
Assert.Empty(ssInfo.Implementations);