PowerShell / DSC

This repo is for the DSC v3 project
MIT License
195 stars 24 forks source link

Simplified way to author PowerShell resources for DSCv3 #245

Open SteveL-MSFT opened 10 months ago

SteveL-MSFT commented 10 months ago

Summary of the new feature / enhancement

The feedback from customers is that authoring class-based resources is too complex and script-function resources require a MOF schema. Although we will continue to support existing script-function and class-based PS resources, we should consider them legacy and only via PSDSC module (with eventual 3.0 release). This also means legacy resources will never get support for export, for example.

This proposes a new way to author DSCv3 resources in PS that does not require PSDSC module and instead is part of the PowerShellGroup resource and does not require authoring JSONSchema.

A goal is to not require any changes in PS itself to make this work so this will also work for WinPS5.1.

Proposed technical implementation details (optional)

The new PS resource model will be a module with cmdlets for get, set, test, export. The mapping will be defined in the .psd1 within a PrivateData section specifically for DSC. This means that these resources will still be discovered via $env:PSModulePath.

Most cases where the JSON structure is flat, the type and constraint information will simply be directly mapped to the parameters of a cmdlet. The PowerShellGroup resource will handle validating the input JSON maps to the cmdlet parameters.

In some cases, the input JSON may require nested objects. To accommodate this, there are several options:

Here's an example for a simple object:

{
  "name": "powershell",
  "version": "7.4.0"
}

In this case, a get could look like:

function get-powershell {
  param(
    [parameter(mandatory)]
    [string]$name,
    [string]$version
  )
}

If we change this to have a nested object:

{
  "name": "powershell",
  "version": {
    "major": 7
    "minor": 4
    "patch": 0
  }
}

Then the required function would look like:

function get-powershell {
  param(
    [parameter(mandatory)]
    [string]$name,
    [hashtable]$version
  )

  # when dsc calls this resource, it would pass a hashtable that looks like the one below for `$version`
}

function version {
  param(
    [int]$major,
    [int]$minor,
    [int]$patch
  )

  # this part wouldn't be used by DSC, but could be used for unittests
  return @{major=$major;minor=$minor;patch=$patch}
} 
ThomasNieto commented 10 months ago

Would this design pollute Get-Command with DSC cmdlets?

Could you also explain how this design is simpler than a class based resource? If class based resources were defined as an interface it would simplify authorship and able to be implemented in PowerShell or C#. That could also allow for the removal of the PSDSC module by having all logic in the PowerShellGroup.

SteveL-MSFT commented 10 months ago

@ThomasNieto good question. My expectation is that these DSC resources would not be used as general cmdlets so they wouldn't be exported as they would in a normal module.

The primary feedback we've gotten from customers is that class-based is simply too hard for them, but script functions is considered simple.

Note that we should still extend class-based resources to at least add export support for those that prefer writing classes.

michaeltlombardi commented 10 months ago

I've thought about this a lot over the last couple of years, especially since working on the Puppet.Dsc module that had to inspect and metaprogram handlers to use DSC Resources as "nearly native" Puppet resources. I encountered a lot of DevX challenges and inspected a ton of DSC Resource implementations along the way.

I filed a few discussions in the PSDSC repository on this, in particular:

Leaving those aside for a second, I think the most significant advantage class-based resources offer authors is the definition of the resource schema as class properties. I don't think people primarily struggle with defining resource properties, my experience and discussions with community members indicates that they primarily struggle with the way they're expected to implement class methods.

I've been playing around with some prototype ideas, but I see two main ways forward, with different options for each:

  1. The v3 model for defining DSC Resources relies on convention - no dependencies at all.
  2. The v3 model for defining DSC Resources builds on a defined interface or base class, which means having a dependency on a module.

Personally, I favor the latter and having the dependency be on the PSDSC module itself - that module is already required for using Invoke-DscResource and discovering existing resources, so as dependencies go it's (functionally, or nearly so) free. The downside to using PSDSC is WinPS compatibility, so it could be a separate module[^1].

Conventional Resource Definitions

If we're going the conventional route without runtime dependencies, the most effective model I can see is for authors to define resources as standalone scripts. The body of the script can be generated from a template and validated to a degree with AST parsing checks.

The alternative outlined above, which includes a mapping of parameters, may still require type definitions for complex use cases. With a conventional implementation, we could extract schemas from the script so they "just work" with the rest of the tooling, even though they're called by the PowerShell provider.

The downside to conventional resources is that they would need to be regenerated any time the conventions change - you can't "freely" get improvements for them. An upside is that if their runtime implementation is purely a script, you don't need to do anything special to integrate with them.

Conventional Resource Workflow

  1. Author runs New-DscResourceDefinition to generate the definition file, like Tailspin.dsc.definition.ps1
  2. Author fills out the definition file defining their class (the resource schema), and the required functions, deleting any their resource doesn't support:
    • Get-TargetResource
    • Export-TargetResource
    • Test-TargetResource
    • Set-TargetResource
  3. Author runs Build-DscResourceDefinition to generate the self-contained script from their definition - this could also include updating the module manifest or defining a resource manifest. It should probably include a validation step to check the definition file, but authors should be able to run Test-DscResourceDefinition separately for that purpose too.

Interface Resource Definitions

Interface definitions have a lot of benefits outlined in the discussions, including:

Aside from the static author-time analysis, a base class gives a lot of the same benefits.

I think a shared interface for resources is the most robust option, but I understand that it introduces a runtime dependency we want to avoid.

Interface Resource Workflow

  1. Author runs New-DscResource to generate the script module or snippet to put in an existing script module.
  2. Author fills out the class definition, which inherits from the interface/base class.

    Instead of defining the logic for the operations in class methods, the author defines the functions to call for those operations as static properties. All implementation logic lives in normal functions that take an instance of the class as input.

[^1]: For a separate module, it would probably make sense to ship that module with the PowerShell provider for DSCv3 - so you have some assurance that the version you pin to works with the provider implementation.

SteveL-MSFT commented 10 months ago

If the primary problem is authoring the schema, I wonder if the answer is simply tooling to generate JSON Schema whether it comes from a class definition or something else. Such a tool could be useful outside of DSC as well.

michaeltlombardi commented 10 months ago

There's a two-way problem for non-class-based PSDSC resources - you have to define the schema and keep your implementation functions in sync with the schema. I definitely endorse being able to generate JSON Schema from PSDSC resource definitions (and generally for other objects/uses).

anmenaga commented 10 months ago

New PowerShell resources should support Export scenario.