diff --git a/.build.ps1 b/.build.ps1 index 47f6142..15f1279 100644 --- a/.build.ps1 +++ b/.build.ps1 @@ -19,7 +19,7 @@ Param ( $ForceEnvironmentVariables = [switch]$true, $MergeList = @('enum*',[PSCustomObject]@{Name='class*';order={(Import-PowerShellDataFile .\SampleModule\Classes\classes.psd1).order.indexOf($_.BaseName)}},'priv*','pub*') - + ,$CodeCoverageThreshold = 90 ) @@ -36,13 +36,22 @@ Process { Get-ChildItem -Path "$PSScriptRoot/.build/" -Recurse -Include *.ps1 -Verbose | Foreach-Object { "Importing file $($_.BaseName)" | Write-Verbose - . $_.FullName + . $_.FullName } task . DscClean,LoadResource,LoadConfigurations task DscClean { - Get-ChildItem -Path "$PSScriptRoot\$BuildOutput\" -Recurse | Remove-Item -force -Recurse -Exclude README.md + if(![io.path]::IsPathRooted($BuildOutput)) { + $BuildOutput = Join-Path $PSScriptRoot $BuildOutput + } + Get-ChildItem -Path $BuildOutput -Recurse | Remove-Item -force -Recurse -Exclude README.md + } + + task Noop { } + + task dsc { + . $PSScriptRoot\SharedDscConfig\examples\init.ps1 } task LoadResource { @@ -63,6 +72,15 @@ Process { begin { + + if (![io.path]::IsPathRooted($BuildOutput)) { + $BuildOutput = Join-Path -Path $PSScriptRoot -ChildPath $BuildOutput + } + + if(($Env:PSModulePath -split ';') -notcontains (Join-Path $BuildOutput 'modules') ) { + $Env:PSModulePath = (Join-Path $BuildOutput 'modules') + ';' + $Env:PSModulePath + } + function Resolve-Dependency { [CmdletBinding()] param() diff --git a/.gitignore b/.gitignore index 77265a9..b9c1739 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ modules/* .kitchen/* Resources/* RequiredResources/* +DSC_Resources/* !**/README.md \ No newline at end of file diff --git a/RequiredResources/README.md b/DSC_Resources/README.md similarity index 100% rename from RequiredResources/README.md rename to DSC_Resources/README.md diff --git a/PSDepend.build.psd1 b/PSDepend.build.psd1 index 0eaa3c0..d90f460 100644 --- a/PSDepend.build.psd1 +++ b/PSDepend.build.psd1 @@ -19,6 +19,6 @@ PSScriptAnalyzer = 'latest' PlatyPS = 'latest' psdeploy = 'latest' - 'gaelcolas/DscBuildHelpers' = 'master' - 'gaelcolas/Datum' = 'master' + DscBuildHelpers = 'latest' + Datum = 'latest' } \ No newline at end of file diff --git a/PSDepend.resources.psd1 b/PSDepend.resources.psd1 index ebd5259..26057d9 100644 --- a/PSDepend.resources.psd1 +++ b/PSDepend.resources.psd1 @@ -1,12 +1,12 @@ @{ PSDependOptions = @{ AddToPath = $True - Target = 'RequiredResources' + Target = 'DSC_Resources' Parameters = @{ #Force = $True #Import = $True } } - 'gaelcolas/Chocolatey' = 'master' + Chocolatey = '0.0.58' } \ No newline at end of file diff --git a/README.md b/README.md index c1e8ffb..c800ee3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Using Geoffrey Moore's value proposition model: > >Our __configuration sharing guidelines__ is __a re-usable build process for DSC configurations__ > ->That __transforms Configuration into re-usable and composable DSC Composite Resources__ +>That ~~transforms Configuration into re-usable and composable DSC Composite Resources~~ allow configurations to be composed, following the [Datum](https://github.com/gaelcolas/Datum) model. @@ -52,50 +52,50 @@ Below are ideas I think worth discussing and suggestions of implementation. Plea ### Repository Structure ``` +SHAREDDSCCONFIG C:\SRC\SHAREDDSCCONFIG │ .build.ps1 -│ .gitignore -│ .kitchen.yml -│ appveyor.yml -│ Dependencies.psd1 -│ LICENSE -│ README.md +│ PSDepend.build.psd1 +│ PSDepend.resources.psd1 │ ├───.build -│ README.md -├───.kitchen -│ ├───logs -│ ├───OtherTestSuite-2012r2-WMF5 -│ │ └───OtherTestSuite-2012r2-WMF5 -│ │ ├───Snapshots -│ │ └───Virtual Machines -│ └───SharedDscConfig-2012r2-WMF5 -│ └───SharedDscConfig-2012r2-WMF5 -│ ├───Snapshots -│ └───Virtual Machines +├───BuildOutput ├───docs -├───DscBuildOutput -│ └───modules [pulled according to Dependencies.psd1, modules not in git] -│ ├───BuildHelpers -│ ├───Datum -│ ├───DscBuildHelpers -│ ├───InvokeBuild -│ ├───Pester -│ ├───platyPS -│ ├───PSDeploy -│ └───PSScriptAnalyzer +├───DSC_Resources +│ └───SharedDscConfig + │ SharedDscConfig.psd1 + │ ├───DscResources - │ └───Shared1 - │ ├───ConfigData - │ │ └───common - │ └───Diagnostics - │ ├───Comprehensive - │ └───Simple + │ ├───Shared1 + │ │ │ Shared1.psd1 + │ │ │ Shared1.schema.psm1 + │ │ │ + │ │ ├───ConfigData + │ │ │ │ Datum.yml + │ │ │ │ + │ │ │ └───default + │ │ │ SharedDscConfig.psd1 + │ │ │ + │ │ └───Diagnostics + │ │ ├───Comprehensive + │ │ └───Simple + │ │ SharedDscConfig.tests.ps1 + │ │ + │ └───SoftwareBase + │ SoftwareBase.psd1 + │ SoftwareBase.schema.psm1 + │ └───examples - ├───ConfigData - │ └───AllNodes - └───DscBuildOutput + │ Default.ps1 + │ init.ps1 + │ + └───ConfigData + │ Datum.yml + │ + └───TestSuites + OtherTestSuite.yml + SharedDscConfig.yml ``` The Shared Configuration should be self contained, but will require files for building/testing or development. The repository will hence need some project files on top of the files required for functionality. diff --git a/SharedDscConfig/DscResources/Shared1/ConfigData/common/SharedDscConfig.psd1 b/SharedDscConfig/DscResources/Shared1/ConfigData/default/SharedDscConfig.psd1 similarity index 100% rename from SharedDscConfig/DscResources/Shared1/ConfigData/common/SharedDscConfig.psd1 rename to SharedDscConfig/DscResources/Shared1/ConfigData/default/SharedDscConfig.psd1 diff --git a/SharedDscConfig/DscResources/Shared1/Shared1.schema.psm1 b/SharedDscConfig/DscResources/Shared1/Shared1.schema.psm1 index 3766174..b5ac21c 100644 --- a/SharedDscConfig/DscResources/Shared1/Shared1.schema.psm1 +++ b/SharedDscConfig/DscResources/Shared1/Shared1.schema.psm1 @@ -5,15 +5,11 @@ Configuration Shared1 { ) Import-DscResource -ModuleName PSDesiredStateConfiguration - Import-DscResource -ModuleName Chocolatey - File TestFile { + File "TestFile_$(1..999|Get-Random)" { Ensure = 'Present' DestinationPath = $DestinationPath Contents = $Param1 } - ChocolateySoftware InstallChoco { - Ensure = 'Present' - } } \ No newline at end of file diff --git a/SharedDscConfig/DscResources/SoftwareBase/SoftwareBase.psd1 b/SharedDscConfig/DscResources/SoftwareBase/SoftwareBase.psd1 new file mode 100644 index 0000000..d542af8 --- /dev/null +++ b/SharedDscConfig/DscResources/SoftwareBase/SoftwareBase.psd1 @@ -0,0 +1,29 @@ +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'SoftwareBase.schema.psm1' + + # Version number of this module. + ModuleVersion = '0.0.2' + + # ID used to uniquely identify this module + GUID = '78db8da3-b47e-4272-a24c-037842ff6164' + + # Author of this module + Author = 'Gael Colas' + + # Company or vendor of this module + CompanyName = 'SynEdgy Ltd' + + # Copyright statement for this module + Copyright = '(c) 2017 Gael. All rights reserved.' + + # Modules that must be imported into the global environment prior to importing this module + RequiredModules = @('PSDesiredStateConfiguration') + + # DSC resources to export from this module + DscResourcesToExport = @('SoftwareBase') + + } + + \ No newline at end of file diff --git a/SharedDscConfig/DscResources/SoftwareBase/SoftwareBase.schema.psm1 b/SharedDscConfig/DscResources/SoftwareBase/SoftwareBase.schema.psm1 new file mode 100644 index 0000000..1f47767 --- /dev/null +++ b/SharedDscConfig/DscResources/SoftwareBase/SoftwareBase.schema.psm1 @@ -0,0 +1,71 @@ +Configuration SoftwareBase { + Param( + $PackageFeedUrl = 'https://chocolatey.org/api/v2', + $Sources = @(), + $ChocolateyLicenseXml, + $Settings = @(), + $Features = @(), + $Packages = @() + ) + + Import-DscResource -ModuleName PSDesiredStateConfiguration + Import-DscResource -ModuleName Chocolatey -ModuleVersion 0.0.58 + + # Install Choco Software if not present + ChocolateySoftware ChocoInstall { + Ensure = 'Present' + PackageFeedUrl = $PackageFeedUrl + } + + # Configure Choco Sources to pull packages from + foreach($source in $Sources) { + if(!$source.Ensure) { $source.add('Ensure', 'Present') } + (Get-DscSplattedResource -ResourceName 'ChocolateySource' -ExecutionName "$($Source.Name)_src" -Properties $source -NoInvoke).Invoke($Source) + } + + if ($ChocolateyLicenseXml) { + File ChocolateyLicense { + Ensure = 'Present' + Contents = $ChocolateyLicenseXml + DestinationPath = 'C:\ProgramData\chocolatey\license\chocolatey.license.xml' + Type = 'File' + Force = $true + } + + # If Choco is licensed, install Choco extension to enable Business Features + if($ChocoExtension = $Packages.Where{$_.Name -eq 'chocolatey.extension'}[0]) { + if(!$ChocoExtension.Ensure) {$ChocoExtension.Add('Ensure','present')} + if(!$ChocoExtension.Version) {$ChocoExtension.Add('Version','latest')} + (x ChocolateyPackage 'ChocoExtension' $ChocoExtension -NoInvoke).Invoke($ChocoExtension) + # Remove the Chocolatey Extension from the $Packages list if present + $Packages = $Packages.Where{$_.Name -ne 'chocolatey.extension'} + } + else { + # Install Default Extension if LIcensed and not specified + ChocolateyPackage ChocoExtension { + Ensure = 'Present' + Version = 'latest' + Name = 'Chocolatey.Extension' + DependsOn = '[File]ChocolateyLicense' + } + } + } + + # Configure Choco Settings + foreach ($Setting in $Settings) { + if(!$Setting.Ensure) {$Setting.Add('Ensure','Present')} + (x ChocolateySetting "$($Setting.Name)_set" $Setting -NoInvoke).Invoke($Setting) + } + + # Configure Choco Features + foreach ($Feature in $Features) { + if(!$Feature.Ensure) {$Feature.Add('Ensure','Present')} + (x ChocolateyFeature "$($Feature.Name)_Ftr" $Feature -NoInvoke).Invoke($Feature) + } + + foreach ($Package in $Packages) { + if(!$Package.Ensure) { $Package.add('Ensure','Present') } + if(!$Package.Version) { $Package.add('version', 'latest') } + (Get-DscSplattedResource -ResourceName ChocolateyPackage -ExecutionName "$($Package.Name)_pkg" -Properties $Package -NoInvoke).Invoke($Package) + } +} \ No newline at end of file diff --git a/SharedDscConfig/SharedDscConfig.psd1 b/SharedDscConfig/SharedDscConfig.psd1 index 87f03b0..5aa0eeb 100644 Binary files a/SharedDscConfig/SharedDscConfig.psd1 and b/SharedDscConfig/SharedDscConfig.psd1 differ diff --git a/SharedDscConfig/examples/ConfigData/Datum.yml b/SharedDscConfig/examples/ConfigData/Datum.yml index 68d7517..cb5227e 100644 --- a/SharedDscConfig/examples/ConfigData/Datum.yml +++ b/SharedDscConfig/examples/ConfigData/Datum.yml @@ -1,8 +1,2 @@ -DatumStructure: - - StoreName: AllNodes - StoreProvider: Datum::File - StoreOptions: - DataDir: "./ConfigData/AllNodes" - ResolutionPrecedence: - - 'AllNodes\$($Node.Name)' \ No newline at end of file + - 'TestSuites\$($Node.Name)' \ No newline at end of file diff --git a/SharedDscConfig/examples/ConfigData/AllNodes/localhost.yml b/SharedDscConfig/examples/ConfigData/TestSuites/OtherTestSuite.yml similarity index 61% rename from SharedDscConfig/examples/ConfigData/AllNodes/localhost.yml rename to SharedDscConfig/examples/ConfigData/TestSuites/OtherTestSuite.yml index 0f3693d..1d7785c 100644 --- a/SharedDscConfig/examples/ConfigData/AllNodes/localhost.yml +++ b/SharedDscConfig/examples/ConfigData/TestSuites/OtherTestSuite.yml @@ -1,5 +1,9 @@ -Nodename: localhost -Name: localhost +Nodename: OtherTestSuite + +Configurations: + - Shared1 + + Shared1: Param1: Data from Node config Data DestinationPath: C:\testfromconfigdata.txt \ No newline at end of file diff --git a/SharedDscConfig/examples/ConfigData/TestSuites/SharedDscConfig.yml b/SharedDscConfig/examples/ConfigData/TestSuites/SharedDscConfig.yml new file mode 100644 index 0000000..82ccf91 --- /dev/null +++ b/SharedDscConfig/examples/ConfigData/TestSuites/SharedDscConfig.yml @@ -0,0 +1,31 @@ +Nodename: SharedDscConfig + +Configurations: + - Shared1 + - SoftwareBase + +Shared1: + Param1: Data from Node config Data + DestinationPath: C:\testfromconfigdata.txt + +SoftwareBase: + Sources: + - Name: chocolatey + Source: https://chocolatey.org/api/v2 + - Name: Contoso + Ensure: Absent + Source: https://contosoweb/nuget/choco + + Settings: + - Name: cacheLocation + Value: C:\Windows\TEMP\Chocolatey + + Features: + - Name: allowGlobalConfirmation + - Name: allowPreviewFeatures + + Packages: + - Name: Chocolatey + - Name: chocolatey.extension + Version: '1.12.2' + - Name: Putty \ No newline at end of file diff --git a/SharedDscConfig/examples/Default.ps1 b/SharedDscConfig/examples/Default.ps1 index e370372..9cb7f4c 100644 --- a/SharedDscConfig/examples/Default.ps1 +++ b/SharedDscConfig/examples/Default.ps1 @@ -1,28 +1,27 @@ -$here = if(!$PSScriptRoot) { "$($Pwd.path)\SharedDscConfig\examples\" } else {$PSScriptRoot} +# if(!$PSScriptRoot) {$here = "$($Pwd.path)\SharedDscConfig\examples\" } else {$here = $PSScriptRoot} +# . .\SharedDscConfig\examples\init.ps1 -$ProjectPath = Get-item "$here\..\.." +Import-Module -force Datum -Global -errorAction Stop -if($Env:PSModulePath -split ';' -notcontains $ProjectPath) { - Write-Warning ">>> $ProjectPath" - $Env:PSModulePath = $Env:PSModulePath +';'+ $ProjectPath.FullName -} -# load the Configuration Property resolver +$DatumConfig = Join-Path $PSScriptRoot "ConfigData\Datum.yml" +Write-Warning "Loading $DatumConfig" + +$Global:Datum = New-DatumStructure -definitionFile $DatumConfig -Import-Module -force Datum -Global -#. $here\scripts\Resolve-DscConfigurationData.ps1 -$yml = Get-Content -raw $Here\ConfigData\Datum.yml | ConvertFrom-Yaml +$AllNodes = @($Datum.TestSuites.psobject.Properties | ForEach-Object { + $Node = $Datum.TestSuites.($_.Name) + if(!$Node.contains('Name') ) { + $null = $Node.Add('Name',$_.Name) + } + (@{} + $Node) #Remove order & Case Sensitivity +}) -Push-Location $Here -$Global:Datum = New-DatumStructure $yml $Global:ConfigurationData = @{ - AllNodes = @($Datum.AllNodes.psobject.properties|%{ $Datum.AllNodes.($_.Name)}) + AllNodes = $AllNodes Datum = $Global:Datum } -#Load configuration Data -#$ConfigurationData = Import-PowerShellDataFile $here\..\ConfigurationData\Default\Default.psd1 -#$Node = $ConfigurationData.AllNodes.Where{$_.Nodename -eq 'localhost'}[0] -ipmo -force $here\..\SharedDscConfig.psd1 +#ipmo -force $here\..\SharedDscConfig.psd1 Configuration Default { @@ -30,24 +29,28 @@ Configuration Default { Node $ConfigurationData.AllNodes.NodeName { - Shared1 SharedConfig_NoParam { - # When no param specified, the default params will be used - } + # When no param specified, the default params will be used + Shared1 SharedConfig_NoParam {} + #Specifying a parameter the traditional way Shared1 SharedConfig_Param1 { - Param1 = 'This is the content from the Root configuration' - DestinationPath = 'C:\Test2.txt' #Using the same file as previous block would fail (2 Resource with same key for same Node) + Param1 = 'This is the content from the Root configuration. Traditional approach' + DestinationPath = 'C:\Test_2.txt' #Using the same file as previous block would fail (2 Resource with same key for same Node) } - Write-Warning ($Node|out-String) - Write-Warning (Lookup $Node 'Shared1'|out-String) + + #Write-Warning ($Node|out-String) + $(Write-Warning "Shared1 Parameters: $(Lookup 'Shared1'|Convertto-Json)") #Auto lookup Parameters for $node with Configuration - $Properties = $(Lookup $Node 'Shared1' -DefaultValue @{}) - Get-DscSplattedResource -ResourceName 'Shared1' -ExecutionName 'Shared1' -Properties $Properties - - #or in short notation - #x 'Shared1' 'Shared1' $Properties - + $Properties = $(Lookup 'Shared1') + (Get-DscSplattedResource -ResourceName 'Shared1' -ExecutionName 'Shared1_FromNodeBlock' -Properties $Properties -NoInvoke).Invoke($Properties) + + #Auto lookup configurations for that node, and foreach of them, look for its params and splat them + (Lookup Configurations).Foreach{ + $ConfigName = $_ + $Properties = Lookup $ConfigName -DefaultValue @{} + (x $ConfigName $ConfigName $Properties -NoInvoke).Invoke($Properties) + } } } diff --git a/SharedDscConfig/examples/init.ps1 b/SharedDscConfig/examples/init.ps1 new file mode 100644 index 0000000..afaf932 --- /dev/null +++ b/SharedDscConfig/examples/init.ps1 @@ -0,0 +1,9 @@ +$ProjectPath = Get-item "$PSScriptRoot\..\.." + +if($Env:PSModulePath -split ';' -notcontains $ProjectPath) { + Write-Warning ">>> Adding $ProjectPath to `$Env:PSModulePath" + $Env:PSModulePath = $Env:PSModulePath +';'+ $ProjectPath.FullName +} + +#Now that the Variable has been set, you can call the DSC Test +. $PSScriptRoot\Default.ps1