diff --git a/OfflineInstallPreparation.ps1 b/OfflineInstallPreparation.ps1 index 838c77c..7a3b60f 100644 --- a/OfflineInstallPreparation.ps1 +++ b/OfflineInstallPreparation.ps1 @@ -150,7 +150,7 @@ foreach ($Plugin in (Get-Content $PSScriptRoot\files\jenkins.json | ConvertFrom- OutFile = Join-Path $PluginsWorkingDirectory "$($Plugin.Name).hpi" } if ($Plugin.Version -and $Plugin.Version -ne 'latest') { - $RestArgs.Uri = "https://updates.jenkins-ci.org/download/plugins/$($Plugin.Name)/$($Plugin.Version)/$($Plugin.Name).hpi" + $RestArgs.Uri = "https://updates.jenkins.io/download/plugins/$($Plugin.Name)/$($Plugin.Version)/$($Plugin.Name).hpi" } if (-not (Test-Path $RestArgs.OutFile)) { Invoke-WebRequest @RestArgs -UseBasicParsing diff --git a/files/chocolatey.json b/files/chocolatey.json index 34e1130..3cd6724 100644 --- a/files/chocolatey.json +++ b/files/chocolatey.json @@ -14,9 +14,9 @@ { "name": "chocolatey" }, { "name": "chocolateygui.extension" }, { "name": "chocolateygui" }, - { "name": "dotnet-8.0-aspnetruntime", "version": "8.0.16" }, - { "name": "dotnet-8.0-runtime", "version": "8.0.16" }, - { "name": "dotnet-aspnetcoremodule-v2", "version": "18.0.25074" }, + { "name": "dotnet-8.0-aspnetruntime", "version": "8.0.22" }, + { "name": "dotnet-8.0-runtime", "version": "8.0.22" }, + { "name": "dotnet-aspnetcoremodule-v2", "version": "18.0.25301" }, { "name": "dotnetfx" }, { "name": "jenkins" }, { "name": "KB2919355", "internalize": false }, diff --git a/files/jenkins.json b/files/jenkins.json index ba32c66..2d1ef7c 100644 --- a/files/jenkins.json +++ b/files/jenkins.json @@ -1,33 +1,34 @@ { "plugins": [ { "name": "apache-httpcomponents-client-4-api", "version": "4.5.14-269.vfa_2321039a_83" }, - { "name": "asm-api", "version": "9.8-135.vb_2239d08ee90" }, - { "name": "bouncycastle-api", "version": "2.30.1.80-256.vf98926042a_9b_" }, - { "name": "branch-api", "version": "2.1217.v43d8b_b_d8b_2c7" }, - { "name": "caffeine-api", "version": "3.2.0-166.v72a_6d74b_870f" }, - { "name": "cloudbees-folder", "version": "6.1012.v79a_86a_1ea_c1f" }, - { "name": "display-url-api", "version": "2.209.v582ed814ff2f" }, - { "name": "durable-task", "version": "587.v84b_877235b_45" }, - { "name": "instance-identity", "version": "201.vd2a_b_5a_468a_a_6" }, - { "name": "ionicons-api", "version": "82.v0597178874e1" }, + { "name": "asm-api", "version": "9.9-185.va_6c6b_3348b_c3" }, + { "name": "bouncycastle-api", "version": "2.30.1.82-277.v70ca_0b_877184" }, + { "name": "branch-api", "version": "2.1259.v45c101731c76" }, + { "name": "caffeine-api", "version": "3.2.3-194.v31a_b_f7a_b_5a_81" }, + { "name": "cloudbees-folder", "version": "6.1073.va_7888eb_dd514" }, + { "name": "commons-lang3-api", "version": "3.19.0-104.v12125f33a_255" }, + { "name": "display-url-api", "version": "2.217.va_6b_de84cc74b_" }, + { "name": "durable-task", "version": "635.v3733cef34b_5e" }, + { "name": "instance-identity", "version": "203.v15e81a_1b_7a_38" }, + { "name": "ionicons-api", "version": "94.vcc3065403257" }, { "name": "jakarta-activation-api", "version": "2.1.3-2" }, - { "name": "jakarta-mail-api", "version": "2.1.3-2" }, + { "name": "jakarta-mail-api", "version": "2.1.3-3" }, { "name": "javax-activation-api", "version": "1.2.0-8" }, { "name": "javax-mail-api", "version": "1.6.2-11" }, - { "name": "mailer", "version": "489.vd4b_25144138f" }, - { "name": "pipeline-groovy-lib", "version": "752.vdddedf804e72" }, - { "name": "scm-api", "version": "704.v3ce5c542825a_" }, - { "name": "script-security", "version": "1373.vb_b_4a_a_c26fa_00" }, - { "name": "structs", "version": "343.vdcf37b_a_c81d5" }, + { "name": "mailer", "version": "522.va_995fa_cfb_8b_d" }, + { "name": "pipeline-groovy-lib", "version": "787.ve2fef0efdca_6" }, + { "name": "scm-api", "version": "712.v8846fdd68c88" }, + { "name": "script-security", "version": "1385.v7d2d9ec4d909" }, + { "name": "structs", "version": "362.va_b_695ef4fdf9" }, { "name": "variant", "version": "70.va_d9f17f859e0" }, - { "name": "workflow-api", "version": "1371.ve334280b_d611" }, - { "name": "workflow-basic-steps", "version": "1079.vce64b_a_929c5a_" }, - { "name": "workflow-cps", "version": "4046.v90b_1b_9edec67" }, - { "name": "workflow-durable-task-step", "version": "1405.v1fcd4a_d00096" }, - { "name": "workflow-job", "version": "1520.v56d65e3b_4566" }, - { "name": "workflow-multibranch", "version": "806.vb_b_688f609ee9" }, - { "name": "workflow-scm-step", "version": "437.v05a_f66b_e5ef8" }, - { "name": "workflow-step-api", "version": "700.v6e45cb_a_5a_a_21" }, - { "name": "workflow-support", "version": "968.v8f17397e87b_8" } + { "name": "workflow-api", "version": "1384.vdc05a_48f535f" }, + { "name": "workflow-basic-steps", "version": "1098.v808b_fd7f8cf4" }, + { "name": "workflow-cps", "version": "4218.vff679a_5c0f3a_" }, + { "name": "workflow-durable-task-step", "version": "1464.v2d3f5c68f84c" }, + { "name": "workflow-job", "version": "1559.va_a_533730b_ea_d" }, + { "name": "workflow-multibranch", "version": "821.vc3b_4ea_780798" }, + { "name": "workflow-scm-step", "version": "466.va_d69e602552b_" }, + { "name": "workflow-step-api", "version": "710.v3e456cc85233" }, + { "name": "workflow-support", "version": "1004.veee3a_d67cdb_9" } ] } \ No newline at end of file diff --git a/modules/C4B-Environment/C4B-Environment.psm1 b/modules/C4B-Environment/C4B-Environment.psm1 index c8ce91f..ce05736 100644 --- a/modules/C4B-Environment/C4B-Environment.psm1 +++ b/modules/C4B-Environment/C4B-Environment.psm1 @@ -1207,7 +1207,7 @@ The host name of the C4B instance. } Copy-Item $PSScriptRoot\ReadmeTemplate.html.j2 -Destination $env:Public\Desktop\Readme.html -Force - + # Working around the existing j2 template, so we can keep them roughly in sync Invoke-TextReplacementInFile -Path $env:Public\Desktop\Readme.html -Replacement @{ # CCM Values @@ -1298,4 +1298,67 @@ if ( Invoke-Choco feature enable --name='useFipsCompliantChecksums' } +function Invoke-JenkinsApi { + <# + .Synopsis + Invokes an existing job on a Jenkins server + .Example + Invoke-JenkinsApi + #> + param( + # The name of the job to invoke + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$Slug, + + # The URI of the Jenkins server, including protocols and port if required + [ValidateNotNullOrEmpty()] + [string]$Uri = 'http://localhost:8080/jenkins', + + [string]$Method = "GET", + + # The Jenkins credential to authenticate with + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.CredentialAttribute()] + $Credential, + + [switch]$RequiresCrumb + ) + $RequestParams = @{} + + $Header = @{} + $Header['Authorization'] = 'Basic {0}' -f ([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($Credential.UserName):$($Credential.GetNetworkCredential().Password)"))) + $RequestParams['Headers'] = $Header + + if ($RequiresCrumb) { + $JenkinsWebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession + + $RequestParams['Uri'] = '{0}/crumbIssuer/api/json' -f $Uri + $RequestParams['Method'] = 'GET' + + $RequestParams['WebSession'] = $JenkinsWebSession + + $CrumbResponse = Invoke-RestMethod @RequestParams + + $Header['Jenkins-Crumb'] = $CrumbResponse.crumb + $RequestParams['Uri'] = '{0}/me/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken?newTokenName=GHA' -f $Uri + $RequestParams['Method'] = 'POST' + $RequestParams['Headers'] = $Header + + $Token = (Invoke-RestMethod @RequestParams).data.tokenValue + $RequestParams.Headers['Authorization'] = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($Credential.UserName):$($Token)")) + } + + $RequestParams['Uri'] = '{0}/{1}' -f $Uri.TrimEnd('/'), $Slug.TrimStart('/') + $RequestParams['Method'] = $Method + + if ($Parameters) { + $RequestParams['Body'] = $Parameters + } + + Invoke-RestMethod @RequestParams +} + Export-ModuleMember -Function "*" \ No newline at end of file diff --git a/tests/jenkins.tests.ps1 b/tests/jenkins.tests.ps1 index acea70e..60cece9 100644 --- a/tests/jenkins.tests.ps1 +++ b/tests/jenkins.tests.ps1 @@ -1,8 +1,16 @@ +#requires -Modules C4B-Environment [CmdletBinding()] -Param( +param( [Parameter(Mandatory)] - [String] - $Fqdn + [String]$Fqdn, + + [PSCredential]$JenkinsCredential = $( + if (Get-ChocoEnvironmentProperty JenkinsCredential) { + Get-ChocoEnvironmentProperty JenkinsCredential + } else { + Get-Credential -UserName admin -Message "Jenkins Account" + } + ) ) Describe "Jenkins Configuration" { @@ -12,7 +20,7 @@ Describe "Jenkins Configuration" { $service = Get-Service jenkins } - It "Jenkins is installed" { + It "Jenkins package is installed" { $jenkins | Should -Not -BeNullOrEmpty } @@ -23,7 +31,6 @@ Describe "Jenkins Configuration" { It "Service is running" { $service.Status | Should -Be 'Running' } - } Context "Required Scripts" { @@ -44,21 +51,17 @@ Describe "Jenkins Configuration" { } } - Context "Required Jobs" { + Context "Required Jobs" -Skip:$(-not $JenkinsCredential) { BeforeAll { - $jobs = (Get-ChildItem 'C:\ProgramData\Jenkins\.jenkins\jobs\' -Directory).Name - } - - It "'Internalize packages from the Community Repository' is present" { - 'Internalize packages from the Community Repository' -in $jobs | Should -Be $true + $Jobs = (Invoke-JenkinsApi -Uri "https://$($FQDN):7443" -Slug "/api/json" -Credential $JenkinsCredential).jobs } - It "'Update Production Repository' is present" { - 'Update Production Repository' -in $jobs | Should -Be $true - } - - It "'Update test repository from Chocolatey Community Repository' is present" { - 'Update test repository from Chocolatey Community Repository' -in $jobs | Should -Be $true + It "'<_>' is present" -ForEach @( + 'Internalize packages from the Community Repository' + 'Update Production Repository' + 'Update test repository from Chocolatey Community Repository' + ) { + $Jobs.Name | Should -Contain $_ } } @@ -68,17 +71,26 @@ Describe "Jenkins Configuration" { } } - Context "Required Plugins" { + Context "Required Plugins" -Skip:$(-not $JenkinsCredential) { BeforeDiscovery { - $ExpectedPlugins = (Get-Content $PSScriptRoot\..\files\jenkins.json | ConvertFrom-Json).plugins.name + $ExpectedPlugins = (Get-Content $PSScriptRoot\..\files\jenkins.json | ConvertFrom-Json).plugins + $InstalledPlugins = (Invoke-JenkinsApi -Uri "https://$($Fqdn):7443" -Slug "/manage/pluginManager/api/json?depth=1" -Credential $JenkinsCredential).plugins } BeforeAll { - $plugins = (Get-ChildItem 'C:\ProgramData\Jenkins\.jenkins\plugins\' -Directory).Name + $InstalledPlugins = (Invoke-JenkinsApi -Uri "https://$($Fqdn):7443" -Slug "/manage/pluginManager/api/json?depth=1" -Credential $JenkinsCredential).plugins + } + + It "<_.name> plugin is installed" -ForEach $ExpectedPlugins { + $PluginShortName, $PluginVersion = $_.name, $_.version + $InstalledPlugins.Where{ + $_.shortName -eq $PluginShortName + }.version | Should -Be $PluginVersion -Because "$($PluginShortName) should have been version '$($PluginVersion)'" } - It "<_> plugin is installed" -ForEach $ExpectedPlugins { - $_ -in $plugins | Should -be $true + # During our builds, we should check we're not merging outdated plugins - but on customer systems, that may be the case. + It "<_.shortName> is not outdated" -ForEach $InstalledPlugins -Skip:$(-not $env:CI) { + $_.hasUpdate | Should -Be $false -Because "$($_.longName) ('$($_.shortName)') should not have an available update" } } } \ No newline at end of file