diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f6d0a76..d73bfd83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added stages to cloud pipeline and added steps to publish modules to Azure Automation DSC. ### Changed @@ -13,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migration to Pester 5+ ### Fixed - - Config data test 'No duplicate IP addresses should be used' threw when there is no IP address configured -- Fix typo in ConfigData tests +- Module versions incremented +- Fix typo in ConfigData tests \ No newline at end of file diff --git a/Exercises/Task2/Exercise3.md b/Exercises/Task2/Exercise3.md index d742c1db..84f81461 100644 --- a/Exercises/Task2/Exercise3.md +++ b/Exercises/Task2/Exercise3.md @@ -23,7 +23,7 @@ Create a new file in 'DSC\DscConfigData\Roles' named 'WsusServer.yml'. Paste the - WindowsFeatures WindowsFeatures: - Name: + Names: - +UpdateServices ``` diff --git a/Exercises/Task2/Exercise4.md b/Exercises/Task2/Exercise4.md index 3ea2c688..6a18e252 100644 --- a/Exercises/Task2/Exercise4.md +++ b/Exercises/Task2/Exercise4.md @@ -93,7 +93,7 @@ DscTagging: ```yaml WindowsFeatures: - Name: + Names: - -Telnet-Client ``` @@ -101,9 +101,9 @@ DscTagging: ```yaml WindowsFeatures: - Name: - - +Web-Server - - -WoW64-Support + Names: + - +Web-Server + - -WoW64-Support ``` The 'Datum.yml' defines the merge behavior for the path 'WindowsFeatures\Name': @@ -117,10 +117,10 @@ DscTagging: ```yaml WindowsFeatures: - Name: - - +Web-Server - - -WoW64-Support - - -Telnet-Client + Names: + - +Web-Server + - -WoW64-Support + - -Telnet-Client ``` More complex merging scenarios are supported that will be explained in later articles. diff --git a/azure-pipelines-azautomation.yml b/azure-pipelines-azautomation.yml new file mode 100644 index 00000000..b3064017 --- /dev/null +++ b/azure-pipelines-azautomation.yml @@ -0,0 +1,300 @@ +trigger: + branches: + include: + - '*' + paths: + exclude: + - CHANGELOG.md + tags: + include: + - "v*" + exclude: + - "*-*" + +variables: + buildFolderName: output + testResultFolderName: testResults + defaultBranch: main + azureServiceConnectionName: 'asc-dscws' # Service Connection used in AzurePowerShell tasks. Create in Project Settings. + automationAccountName: aa-dscws # Automation Account hosting the Pull Server + automationResourceGroup: dscws # Resource Group of Automation Account + storageAccountName: dscws # Storage Account to host zipped modules for import into Azure Automation Modules + storageAccountResourceGroup: dscws # Resource Group of Storage Account + storageContainerName: modules # Name of storage container that will store the module zips for an upload to AA + +stages: + - stage: Build + jobs: + - job: Compile_Dsc + displayName: 'Compile DSC Configuration' + pool: + vmImage: 'windows-2019' + steps: + + - task: GitVersion@5 + name: gitVersion + displayName: 'Evaluate Next Version' + inputs: + runtime: 'core' + configFilePath: 'GitVersion.yml' + + - task: PowerShell@2 + name: build + displayName: 'Build DSC Artifacts' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks build' + pwsh: false + env: + ModuleVersion: $(gitVersion.NuGetVersionV2) + + - task: PowerShell@2 + name: pack + displayName: 'Pack DSC Artifacts' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks pack' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Output Folder' + inputs: + targetPath: '$(buildFolderName)/' + artifact: 'output' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish MOF Files' + inputs: + targetPath: '$(buildFolderName)/MOF' + artifact: 'MOF' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Meta MOF Files' + inputs: + targetPath: '$(buildFolderName)/MetaMOF' + artifact: 'MetaMOF' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Compressed Modules' + inputs: + targetPath: '$(buildFolderName)/CompressedModules' + artifact: 'CompressedModules' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish RSOP Files' + inputs: + targetPath: '$(buildFolderName)/RSOP' + artifact: 'RSOP' + publishLocation: 'pipeline' + parallel: true + - stage: DscDeployModules + dependsOn: build + condition: succeeded() + jobs: + - job: publish_modules + displayName: Publish DSC resource modules + pool: + vmImage: 'windows-2019' + steps: + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact: CompressedModules' + inputs: + source: 'current' + artifact: CompressedModules + path: '$(Build.SourcesDirectory)/CompressedModules' + + - task: AzurePowerShell@5 + name: deployModulesToAzAutomation + displayName: Deploy modules to Azure Automation + inputs: + azureSubscription: $(azureServiceConnectionName) + scriptType: 'inlineScript' # Optional. Options: filePath, inlineScript + inline: | + $account = Get-AzStorageAccount -Name $env:storageAccountName -ResourceGroupName $env:storageAccountResourceGroup -ErrorAction SilentlyContinue + + if ( -not $account) + { + Write-Error -Message "There is no storage account called $env:storageAccountName ..." + return + } + + $container = Get-AzStorageContainer -Name $env:storageContainerName -Context $account.Context -ErrorAction SilentlyContinue + if (-not $container) + { + $container = New-AzStorageContainer -Name $env:storageContainerName -Context $account.Context -ErrorAction Stop + } + + $modulePath = Join-Path -Path $env:BUILD_SOURCESDIRECTORY -ChildPath CompressedModules + foreach ($module in (Get-ChildItem $modulePath -Filter *.zip)) + { + $moduleName = $module.BaseName -replace '_\d+\.\d+\.\d+(\.\d+)?' + $content = Set-AzStorageBlobContent -File $module.FullName -CloudBlobContainer $container.CloudBlobContainer -Blob $module.Name -Context $account.Context -Force -ErrorAction Stop + $token = New-AzStorageBlobSASToken -CloudBlob $content.ICloudBlob -StartTime (Get-Date) -ExpiryTime (Get-Date).AddYears(5) -Protocol HttpsOnly -Context $account.Context -Permission r -ErrorAction Stop + $uri = 'https://{3}.blob.core.windows.net/{0}/{1}{2}' -f $env:storageContainerName, $module.Name, $token, $env:StorageAccountName + + New-AzAutomationModule -Name $moduleName -ContentLinkUri $uri -ResourceGroupName $env:automationResourceGroup -AutomationAccountName $env:automationAccountName + } + errorActionPreference: 'stop' + failOnStandardError: false + azurePowerShellVersion: 'latestVersion' + pwsh: false + + - stage: DscDeploymentDev + condition: succeeded() + dependsOn: + - build + - DscDeployModules + jobs: + - deployment: Dev + displayName: Dev Deployment + environment: Dev + pool: + vmImage: 'windows-2019' + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - download: None + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact: MOF' + inputs: + source: 'current' + artifact: MOF + path: '$(Build.SourcesDirectory)/MOF' + + - task: PowerShell@2 + name: displayEnvironmentVariables + displayName: 'Display Environment Variables' + inputs: + targetType: 'inline' + script: | + dir -Path env: | Out-String | Write-Host + + - task: AzurePowerShell@5 + name: deployMofToAzAutomation + displayName: Deploy to Azure Automation Pull + inputs: + azureSubscription: $(azureServiceConnectionName) + scriptType: 'inlineScript' # Optional. Options: filePath, inlineScript + inline: | + $mofPath = Join-Path -Path $env:BUILD_SOURCESDIRECTORY -ChildPath "/MOF/$env:ENVIRONMENT_NAME" + foreach ($mof in (Get-ChildItem -Path $mofPath -Filter *.mof)) + { + Import-AzAutomationDscNodeConfiguration -Path $mof.FullName -ConfigurationName $env:ENVIRONMENT_NAME -AutomationAccountName $env:automationAccountName -ResourceGroupName $env:automationResourceGroup -Force -IncrementNodeConfigurationBuild + } + errorActionPreference: 'stop' + failOnStandardError: false + azurePowerShellVersion: 'latestVersion' + pwsh: false + + - stage: DscDeploymentTest + condition: succeeded() + dependsOn: + - build + - DscDeploymentDev + jobs: + - deployment: Test + displayName: Test Deployment + environment: Test + pool: + vmImage: 'windows-2019' + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - download: None + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact: MOF' + inputs: + source: 'current' + artifact: MOF + path: '$(Build.SourcesDirectory)/MOF' + + - task: PowerShell@2 + name: displayEnvironmentVariables + displayName: 'Display Environment Variables' + inputs: + targetType: 'inline' + script: | + dir -Path env: | Out-String | Write-Host + + - task: AzurePowerShell@5 + name: deployMofToAzAutomation + displayName: Deploy to Azure Automation Pull + inputs: + azureSubscription: $(azureServiceConnectionName) + scriptType: 'inlineScript' # Optional. Options: filePath, inlineScript + inline: | + $mofPath = Join-Path -Path $env:BUILD_SOURCESDIRECTORY -ChildPath "/MOF/$env:ENVIRONMENT_NAME" + foreach ($mof in (Get-ChildItem -Path $mofPath -Filter *.mof)) + { + Import-AzAutomationDscNodeConfiguration -Path $mof.FullName -ConfigurationName $env:ENVIRONMENT_NAME -AutomationAccountName $env:automationAccountName -ResourceGroupName $env:automationResourceGroup -Force -IncrementNodeConfigurationBuild + } + errorActionPreference: 'stop' + failOnStandardError: false + azurePowerShellVersion: 'latestVersion' + pwsh: false + - stage: DscDeploymentProd + condition: succeeded() + dependsOn: + - build + - DscDeploymentTest + jobs: + - deployment: Prod + displayName: Prod Deployment + environment: Prod + pool: + vmImage: 'windows-2019' + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - download: None + + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact: MOF' + inputs: + source: 'current' + artifact: MOF + path: '$(Build.SourcesDirectory)/MOF' + + - task: PowerShell@2 + name: displayEnvironmentVariables + displayName: 'Display Environment Variables' + inputs: + targetType: 'inline' + script: | + dir -Path env: | Out-String | Write-Host + + - task: AzurePowerShell@5 + name: deployMofToAzAutomation + displayName: Deploy to Azure Automation Pull + inputs: + azureSubscription: $(azureServiceConnectionName) + scriptType: 'inlineScript' # Optional. Options: filePath, inlineScript + inline: | + $mofPath = Join-Path -Path $env:BUILD_SOURCESDIRECTORY -ChildPath "/MOF/$env:ENVIRONMENT_NAME" + foreach ($mof in (Get-ChildItem -Path $mofPath -Filter *.mof)) + { + Import-AzAutomationDscNodeConfiguration -Path $mof.FullName -ConfigurationName $env:ENVIRONMENT_NAME -AutomationAccountName $env:automationAccountName -ResourceGroupName $env:automationResourceGroup -Force -IncrementNodeConfigurationBuild + } + errorActionPreference: 'stop' + failOnStandardError: false + azurePowerShellVersion: 'latestVersion' + pwsh: false