3 Commits

Author SHA1 Message Date
Subir Ghosh 7114759dc4 Update CODEOWNERS 2024-11-13 17:39:08 -07:00
Subir Ghosh 33e216db1d feature: add win 25 changes 2024-11-13 17:22:28 -07:00
Subir Ghosh fcf3d0ac96 feature: initial commit for all new image changes 2024-11-13 17:16:01 -07:00
498 changed files with 44147 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+1
View File
@@ -0,0 +1 @@
* @actions/runner-images-team
+56
View File
@@ -0,0 +1,56 @@
name: Announcement
description: Submit an announcement
labels: [Announcement]
body:
- type: textarea
attributes:
label: Breaking changes
placeholder: Short description of the upcoming change
validations:
required: true
- type: textarea
attributes:
label: Target date
placeholder: Date of changes propagation start
validations:
required: true
- type: textarea
attributes:
label: The motivation for the changes
placeholder: Description of main reasons for this change
validations:
required: true
- type: textarea
attributes:
label: Possible impact
placeholder: Description of who might be impacted by this change
validations:
required: true
- type: checkboxes
attributes:
label: Platforms affected
options:
- label: Azure DevOps
- label: GitHub Actions
- type: checkboxes
attributes:
label: Runner images affected
options:
- label: Ubuntu 20.04
- label: Ubuntu 22.04
- label: Ubuntu 24.04
- label: macOS 12
- label: macOS 13
- label: macOS 13 Arm64
- label: macOS 14
- label: macOS 14 Arm64
- label: macOS 15
- label: macOS 15 Arm64
- label: Windows Server 2019
- label: Windows Server 2022
- type: textarea
attributes:
label: Mitigation ways
description: Steps or options for impact mitigation
validations:
required: true
+71
View File
@@ -0,0 +1,71 @@
name: Bug Report
description: Submit a bug report.
labels: [bug report, needs triage]
body:
- type: textarea
attributes:
label: Description
description: A clear and concise description of what the bug is, and why you consider it to be a bug.
validations:
required: true
- type: checkboxes
attributes:
label: Platforms affected
options:
- label: Azure DevOps
- label: GitHub Actions - Standard Runners
- label: GitHub Actions - Larger Runners
- type: checkboxes
attributes:
label: Runner images affected
options:
- label: Ubuntu 20.04
- label: Ubuntu 22.04
- label: Ubuntu 24.04
- label: macOS 12
- label: macOS 13
- label: macOS 13 Arm64
- label: macOS 14
- label: macOS 14 Arm64
- label: macOS 15
- label: macOS 15 Arm64
- label: Windows Server 2019
- label: Windows Server 2022
- type: textarea
attributes:
label: Image version and build link
description: |
Image version where you are experiencing the issue. Where to find image version in build logs:
1. For GitHub Actions, under "Set up job" -> "Runner Image" -> "Version".
2. For Azure DevOps, under "Initialize job" -> "Runner Image" -> "Version".
If you have a public example, please, provide a link to the failed build.
validations:
required: true
- type: input
attributes:
label: Is it regression?
description: If yes, please, provide the latest image version where the issue didn't persist, and a link to the latest successful build.
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: A description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual behavior
description: A description of what is actually happening.
validations:
required: true
- type: textarea
attributes:
label: Repro steps
placeholder: |
A description with steps to reproduce the issue.
1. Step 1
2. Step 2
validations:
required: true
+6
View File
@@ -0,0 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: Get help in GitHub Discussions
url: https://github.com/actions/runner-images/discussions
about: Have a question? Feel free to ask in the runner-images GitHub Discussions!
+84
View File
@@ -0,0 +1,84 @@
name: Tool request
description: Request a new tool or update to a tool
title: Update/Add [tool name]
labels: [feature request, needs triage]
body:
- type: markdown
attributes:
value: "## Tool information"
- type: input
attributes:
label: Tool name
validations:
required: true
- type: input
attributes:
label: Tool license
description: Type of licensing for desired tool.
validations:
required: true
- type: checkboxes
attributes:
label: Add or update?
options:
- label: Add
- label: Update
- type: input
attributes:
label: Desired version
description: Let us know if you're requesting a specific version, dev/RC, whatever is latest, etc.
validations:
required: true
- type: input
attributes:
label: Approximate size
description: Leave blank if unknown.
- type: markdown
attributes:
value: "## If this is an add request"
- type: textarea
attributes:
label: Brief description of tool
- type: input
attributes:
label: URL for tool's homepage
- type: textarea
attributes:
label: Provide a basic test case to validate the tool's functionality.
description: This will be automatically formatted into code.
render: bash
- type: checkboxes
attributes:
label: Platforms where you need the tool
options:
- label: Azure DevOps
- label: GitHub Actions
- type: checkboxes
attributes:
label: Runner images where you need the tool
options:
- label: Ubuntu 20.04
- label: Ubuntu 22.04
- label: Ubuntu 24.04
- label: macOS 12
- label: macOS 13
- label: macOS 13 Arm64
- label: macOS 14
- label: macOS 14 Arm64
- label: macOS 15
- label: macOS 15 Arm64
- label: Windows Server 2019
- label: Windows Server 2022
- type: textarea
attributes:
label: Can this tool be installed during the build?
description: If so, please provide a description with required steps. This will be automatically formatted into code.
render: bash
- type: input
attributes:
label: Tool installation time in runtime
description: How long does it take to install the tool?
- type: input
attributes:
label: Are you willing to submit a PR?
description: We accept contributions!
+14
View File
@@ -0,0 +1,14 @@
# Description
New tool, Bug fixing, or Improvement?
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.
**For new tools, please provide total size and installation time.**
<!-- Currently, we can't accept external contributions to macOS source. Please find more details in [CONTRIBUTING.md](CONTRIBUTING.md#macOS) guide -->
#### Related issue:
## Check list
- [ ] Related issue / work item is attached
- [ ] Tests are written (if applicable)
- [ ] Documentation is updated (if applicable)
- [ ] Changes are tested and related VM images are successfully generated
+72
View File
@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '32 4 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
# only required for workflows in private repositories
actions: read
contents: read
# required for all workflows
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python', 'ruby' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
@@ -0,0 +1,22 @@
name: Create GitHub release
on:
repository_dispatch:
types: [create-github-release]
jobs:
Create_GitHub_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create release for ${{ github.event.client_payload.ReleaseBranchName }}
uses: ncipollo/release-action@v1.14.0
with:
tag: ${{ github.event.client_payload.ReleaseBranchName }}
name: ${{ github.event.client_payload.ReleaseTitle }}
body: ${{ github.event.client_payload.ReleaseBody }}
prerelease: ${{ github.event.client_payload.Prerelease }}
commit: ${{ github.event.client_payload.Commitish }}
+49
View File
@@ -0,0 +1,49 @@
name: Create Pull Request
on:
repository_dispatch:
types: [create-pr]
jobs:
Create_pull_request:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Clone release branch to create pull request
run: |
git checkout ${{ github.event.client_payload.ReleaseBranchName }}
git branch ${{ github.event.client_payload.ReleaseBranchName }}-docs
git push origin ${{ github.event.client_payload.ReleaseBranchName }}-docs
- name: Create pull request for ${{ github.event.client_payload.ReleaseBranchName }}
id: create-pr
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
let response = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "${{ github.event.client_payload.PullRequestTitle }}",
head: "${{ github.event.client_payload.ReleaseBranchName }}-docs",
base: "${{ github.event.client_payload.PullRequestBase }}",
body: `${{ github.event.client_payload.PullRequestBody }}`
});
return response.data.number
- name: Request reviewers
uses: actions/github-script@v7
with:
github-token: ${{secrets.PRAPPROVAL_SECRET}}
script: |
github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: ${{ steps.create-pr.outputs.result }},
team_reviewers: ['runner-images-team']
})
+92
View File
@@ -0,0 +1,92 @@
name: Create SBOM for the release
# Inherited variables:
# github.event.client_payload.agentSpec - Current YAML Label
# github.event.client_payload.ReleaseID - Current release ID
# github.event.client_payload.imageVersion - AzDO image version "major.minor"
# github.event.client_payload.ReleaseBranchName - Necessary to identify workflow run
#
# Current SYFT tool issues:
# macOS (major): prompt privilegies that blocking process indefinetely (https://github.com/anchore/syft/issues/1367)
on:
repository_dispatch:
types: [generate-sbom]
defaults:
run:
shell: pwsh
jobs:
#Checking current release for SBOM
sbom-check:
outputs:
check_status: ${{ steps.check.outputs.status }}
runs-on: ubuntu-latest
steps:
- name: Check release for ${{ github.event.client_payload.ReleaseBranchName }}
id: check
shell: pwsh
run: |
$apiUrl = "https://api.github.com/repos/actions/runner-images/releases/${{ github.event.client_payload.ReleaseID }}"
$response = Invoke-RestMethod -Uri $apiUrl -Method Get -SkipHttpErrorCheck
if ($response.message -ilike "Not Found") {
echo "status=release_not_found" >> $env:GITHUB_OUTPUT
Write-Error "Release ${{ github.event.client_payload.ReleaseID }} wasn't found"
exit 1
}
foreach ($asset in $response.assets) {
if ($asset.name -like '*sbom*') {
echo "status=sbom_exists" >> $env:GITHUB_OUTPUT
return "Release ${{ github.event.client_payload.ReleaseID }} already contains a SBOM"
}
}
Write-Host "Release has been found, SBOM is not attached, starting generation."
echo "status=okay" >> $env:GITHUB_OUTPUT
#Generating SBOM
building-sbom:
needs: sbom-check
if: ${{ needs.sbom-check.outputs.check_status == 'okay' }}
runs-on: ${{ github.event.client_payload.agentSpec }}
steps:
- name: Available image version check for ${{ github.event.client_payload.ReleaseBranchName }}
run: |
$imageVersionComponents = $env:ImageVersion.Split('.')
$imageMajorVersion = $imageVersionComponents[0]
$imageMinorVersion = $imageVersionComponents[1]
if ("$imageMajorVersion.$imageMinorVersion" -ne '${{ github.event.client_payload.imageVersion }}') {
throw "Current runner $imageMajorVersion.$imageMinorVersion image version doesn't match ${{ github.event.client_payload.imageVersion }}."
}
- name: Install SYFT tool on Windows
if: ${{ runner.os == 'Windows' }}
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b D:/syft
- name: Install SYFT tool on Ubuntu or macOS
if: ${{ runner.os != 'Windows' }}
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v0.100.0
#Running section.
- name: Run SYFT on Windows
if: ${{ runner.os == 'Windows' }}
run: D:/syft/syft dir:C:/ -vv -o spdx-json=sbom.json
- name: Run SYFT on Ubuntu
if: ${{ runner.os == 'Linux' }}
run: syft dir:/ -vv -o spdx-json=sbom.json
- name: Run SYFT on macOS
if: ${{ runner.os == 'macOS' }}
run: sudo syft dir:/ -vv -o spdx-json=sbom.json --exclude ./Users --exclude ./System/Volumes --exclude ./private
shell: bash
#Preparing artifact (raw SBOM.json is too big)
- name: Compress SBOM file
run: Compress-Archive sbom.json sbom.json.zip
#Upload artifact action
- uses: actions/upload-artifact@v4
with:
name: sbom-${{ github.event.client_payload.agentSpec }}-${{ github.event.client_payload.imageVersion }}
path: sbom.json.zip
if-no-files-found: warn
#Upload release asset action
#Might be changed to softprops/action-gh-release after additional check
- name: Upload release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: "https://uploads.github.com/repos/actions/runner-images/releases/${{ github.event.client_payload.ReleaseID }}/assets{?name,label}"
asset_path: ./sbom.json.zip
asset_name: sbom.${{ github.event.client_payload.agentSpec }}.json.zip
asset_content_type: application/zip
+36
View File
@@ -0,0 +1,36 @@
# CI Validation
name: Linter
on:
pull_request:
branches: [ main ]
paths:
- '**.json'
- '**.md'
- '**.sh'
jobs:
build:
name: Lint JSON & MD files
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Lint Code Base
uses: github/super-linter/slim@v4
env:
VALIDATE_ALL_CODEBASE: false
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_JSON: true
VALIDATE_MARKDOWN: true
DEFAULT_BRANCH: ${{ github.base_ref }}
FILTER_REGEX_EXCLUDE: .*images/*/.*-Readme.md
- name: Checking shebang lines in MacOS and Ubuntu releases.
run: ./images.CI/shebang-linter.ps1
shell: pwsh
+48
View File
@@ -0,0 +1,48 @@
name: Merge pull request
on:
repository_dispatch:
types: [merge-pr]
jobs:
Merge_pull_request:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve possible conflicts ${{ github.event.client_payload.ReleaseBranchName }} with main
run: |
git config --global user.email "no-reply@github.com"
git config --global user.name "Actions service account"
git checkout ${{ github.event.client_payload.ReleaseBranchName }}-docs
git merge --no-edit --strategy-option=ours main
git push origin ${{ github.event.client_payload.ReleaseBranchName }}-docs
sleep 30
- name: Approve pull request by GitHub-Actions bot
uses: actions/github-script@v7
with:
github-token: ${{secrets.PRAPPROVAL_SECRET}}
script: |
github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: ${{ github.event.client_payload.PullRequestNumber }},
event: "APPROVE"
});
- name: Merge pull request for ${{ github.event.client_payload.ReleaseBranchName }}
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.rest.pulls.merge({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: ${{ github.event.client_payload.PullRequestNumber }},
merge_method: "squash"
})
+25
View File
@@ -0,0 +1,25 @@
# CI Validation
name: PowerShell Tests
on:
pull_request:
branches: [ main ]
paths:
- 'helpers/software-report-base/**'
jobs:
powershell-tests:
name: PowerShell tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Run Software Report module tests
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
Invoke-Pester -Output Detailed "helpers/software-report-base/tests"
@@ -0,0 +1,47 @@
name: Trigger Build workflow
on:
workflow_call:
inputs:
image_type:
required: true
type: string
defaults:
run:
shell: pwsh
jobs:
trigger-workflow:
runs-on: ubuntu-latest
steps:
- name: Trigger Build workflow
env:
CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }}
PR_TITLE: ${{ github.event.pull_request.title }}
CI_PR: ${{ secrets.CI_REPO }}
run: |
$headers = @{
Authorization="Bearer $env:CI_PR_TOKEN"
}
# Private repository for builds
$apiRepoUrl = "https://api.github.com/repos/$env:CI_PR"
$eventType = "trigger-${{ inputs.image_type }}-build"
$body = @{
event_type = $eventType;
client_payload = @{
pr_title = "$env:PR_TITLE"
custom_repo = "${{ github.event.pull_request.head.repo.full_name }}"
custom_repo_commit_hash = "${{ github.event.pull_request.head.sha }}"
}
}
$bodyString = $body | ConvertTo-Json
try {
Invoke-WebRequest -Uri "$apiRepoUrl/dispatches" -Method Post -Headers $headers -Body $bodyString | Out-Null
} catch {
throw "$($_.exception[0].message)"
}
+20
View File
@@ -0,0 +1,20 @@
name: Trigger Ubuntu20.04 CI
run-name: Ubuntu20.04 - ${{ github.event.pull_request.title }}
on:
pull_request_target:
types: labeled
paths:
- 'images/ubuntu/**'
defaults:
run:
shell: pwsh
jobs:
Ubuntu_2004:
if: github.event.label.name == 'CI ubuntu-all' || github.event.label.name == 'CI ubuntu-2004'
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
with:
image_type: 'ubuntu2004'
secrets: inherit
+20
View File
@@ -0,0 +1,20 @@
name: Trigger Ubuntu22.04 CI
run-name: Ubuntu22.04 - ${{ github.event.pull_request.title }}
on:
pull_request_target:
types: labeled
paths:
- 'images/ubuntu/**'
defaults:
run:
shell: pwsh
jobs:
Ubuntu_2204:
if: github.event.label.name == 'CI ubuntu-all' || github.event.label.name == 'CI ubuntu-2204'
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
with:
image_type: 'ubuntu2204'
secrets: inherit
+20
View File
@@ -0,0 +1,20 @@
name: Trigger Ubuntu24.04 CI
run-name: Ubuntu24.04 - ${{ github.event.pull_request.title }}
on:
pull_request_target:
types: labeled
paths:
- 'images/ubuntu/**'
defaults:
run:
shell: pwsh
jobs:
Ubuntu_2404:
if: github.event.label.name == 'CI ubuntu-all' || github.event.label.name == 'CI ubuntu-2404'
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
with:
image_type: 'ubuntu2404'
secrets: inherit
@@ -0,0 +1,30 @@
name: Update release
on:
repository_dispatch:
types: [update-github-release]
jobs:
Update_GitHub_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update release for ${{ github.event.client_payload.ReleaseBranchName }}
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const response = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: "${{ github.event.client_payload.ReleaseBranchName }}"
});
github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: response.data.id,
prerelease: ${{ github.event.client_payload.Prerelease }}
});
+20
View File
@@ -0,0 +1,20 @@
name: Trigger Windows19 CI
run-name: Windows2019 - ${{ github.event.pull_request.title }}
on:
pull_request_target:
types: labeled
paths:
- 'images/windows/**'
defaults:
run:
shell: pwsh
jobs:
Windows_2019:
if: github.event.label.name == 'CI windows-all' || github.event.label.name == 'CI windows-2019'
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
with:
image_type: 'windows2019'
secrets: inherit
+20
View File
@@ -0,0 +1,20 @@
name: Trigger Windows22 CI
run-name: Windows2022 - ${{ github.event.pull_request.title }}
on:
pull_request_target:
types: labeled
paths:
- 'images/windows/**'
defaults:
run:
shell: pwsh
jobs:
Windows_2022:
if: github.event.label.name == 'CI windows-all' || github.event.label.name == 'CI windows-2022'
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
with:
image_type: 'windows2022'
secrets: inherit
+20
View File
@@ -0,0 +1,20 @@
name: Trigger Windows25 CI
run-name: Windows2025 - ${{ github.event.pull_request.title }}
on:
pull_request_target:
types: labeled
paths:
- 'images/windows/**'
defaults:
run:
shell: pwsh
jobs:
Windows_2025:
if: github.event.label.name == 'CI windows-all' || github.event.label.name == 'CI windows-2025'
uses: ./.github/workflows/trigger-ubuntu-win-build.yml
with:
image_type: 'windows2025'
secrets: inherit
+399
View File
@@ -0,0 +1,399 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Typescript v1 declaration files
typings/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# VSCode settings
.vscode/**
!.vscode/extensions.json
!.vscode/settings.json
!.vscode/tasks.json
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# Ignore files generated by packer
InstalledSoftware.md
# Desktop Service Store
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# gatsby files
.cache/
public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# visual studio code launch configuration
launch.json
# Ignore dynamic template
images/*/*-temp.json
+9
View File
@@ -0,0 +1,9 @@
{
"recommendations": [
"streetsidesoftware.code-spell-checker",
"hashicorp.hcl",
"davidanson.vscode-markdownlint",
"ms-vscode.powershell",
"timonwong.shellcheck"
]
}
+25
View File
@@ -0,0 +1,25 @@
{
"files.trimFinalNewlines": true,
"files.insertFinalNewline": true,
"powershell.codeFormatting.addWhitespaceAroundPipe": true,
"powershell.codeFormatting.alignPropertyValuePairs": true,
"powershell.codeFormatting.autoCorrectAliases": true,
"powershell.codeFormatting.newLineAfterCloseBrace": false,
"powershell.codeFormatting.newLineAfterOpenBrace": true,
"powershell.codeFormatting.openBraceOnSameLine": true,
"powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline",
"powershell.codeFormatting.preset": "OTBS",
"powershell.codeFormatting.trimWhitespaceAroundPipe": true,
"powershell.codeFormatting.whitespaceAfterSeparator": true,
"powershell.codeFormatting.whitespaceAroundOperator": true,
"powershell.codeFormatting.whitespaceBeforeOpenBrace": true,
"powershell.codeFormatting.whitespaceBeforeOpenParen": true,
"powershell.codeFormatting.whitespaceBetweenParameters": true,
"powershell.codeFormatting.whitespaceInsideBrace": true,
"shellcheck.exclude": [
"SC1090","SC2096"
],
"shellcheck.customArgs": [
"-x"
]
}
+42
View File
@@ -0,0 +1,42 @@
// Available variables which can be used inside of strings.
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${relativeFile}: the current opened file relative to workspaceRoot
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
// Start PowerShell
"windows": {
"command": "${env:windir}/System32/WindowsPowerShell/v1.0/powershell.exe",
//"command": "${env:ProgramFiles}/PowerShell/6.0.0/powershell.exe",
"args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ]
},
"linux": {
"command": "/usr/bin/powershell",
"args": [ "-NoProfile" ]
},
"osx": {
"command": "/usr/local/bin/powershell",
"args": [ "-NoProfile" ]
},
// Associate with test task runner
"tasks": [
{
"taskName": "Test",
"suppressTaskName": true,
"isTestCommand": true,
"args": [
"Write-Host 'Invoking Pester...'; $ProgressPreference = 'SilentlyContinue'; Invoke-Pester -Script test -PesterOption @{IncludeVSCodeMarker=$true};",
"Invoke-Command { Write-Host 'Completed Test task in task runner.' }"
],
"problemMatcher": "$pester"
}
]
}
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at opensource@github.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+67
View File
@@ -0,0 +1,67 @@
# Contributing
[fork]: https://github.com/actions/runner-images/fork
[pr]: https://github.com//actions/runner-images/compare
[code-of-conduct]: CODE_OF_CONDUCT.md
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [MIT](LICENSE.md) license.
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project, you agree to abide by its terms.
## Submitting a pull request
1. [Fork][fork] and clone the repository.
1. Create a new branch: `git checkout -b my-branch-name`.
1. Make your changes, ensuring that they include steps to install, validate post-install, and update the software report (please see [How to add a new tool](CONTRIBUTING.md#how-to-add-a-new-tool) for details).
1. Test your changes by [creating an image and deploying a VM](docs/create-image-and-azure-resources.md).
1. Push to your fork and [submit a pull request][pr].
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
- Follow the style guide for [Powershell](https://github.com/PoshCode/PowerShellPracticeAndStyle) when writing Windows scripts. There is currently no set style for the Shell scripts that run Linux installs :soon:.
- Include complete details of why this is needed in the PR description.
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- For new tools:
- Make sure that the tool satisfies the [Software Guidelines](README.md#software-guidelines).
- Create an issue and get approval from us to add this tool to the image before creating the pull request.
## How to add a new tool
### General rules
- For every new tool, add validation scripts and update the software report script to ensure that it is included in the documentation.
- If the tool is available on multiple platforms (macOS, Windows, Linux), make sure you include it on as many as possible.
- If installing multiple versions of the tool, consider putting the list of versions in the corresponding `toolset.json` file. This will help other customers configure their builds flexibly. See [toolset-windows-2019.json](images/windows/toolsets/toolset-2019.json) as an example.
- Use consistent naming across all files.
- Validation scripts should be simple and shouldn't change the image content.
### Windows
- Add a script that will install the tool and put the script in the `scripts/build` folder.
There are a bunch of helper functions that could simplify your code: `Install-ChocoPackage`, `Install-Binary`, `Install-VSIXFromFile`, `Install-VSIXFromUrl`, `Invoke-DownloadWithRetry`, `Test-IsWin19`, `Test-IsWin22` (find the full list of helpers in [ImageHelpers.psm1](images/windows/scripts/helpers/ImageHelpers.psm1)).
- Add a script that will validate the tool installation and put the script in the `scripts/tests` folder.
We use [Pester v5](https://github.com/pester/pester) for validation scripts. If the tests for the tool are complex enough, create a separate `*.Tests.ps1`. Otherwise, use `Tools.Tests.ps1` for simple tests.
Add `Invoke-PesterTests -TestFile <testFileName> [-TestName <describeName>]` at the end of the installation script to ensure that your tests will be run.
- Add changes to the software report generator `images/windows/scripts/docs-gen/Generate-SoftwareReport.ps1`. The software report generator is used to generate an image's README file, e.g. [Windows2019-Readme.md](images/windows/Windows2019-Readme.md) and uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS).
### Ubuntu
- Add a script that will install and validate the tool and put the script in the `scripts/build` folder.
Use existing scripts such as [github-cli.sh](images/ubuntu/scripts/build/github-cli.sh) as a starting point.
- Use [helpers](images/ubuntu/scripts/helpers/install.sh) to simplify the installation process.
- The validation part should `exit 1` if there is any issue with the installation.
- Add changes to the software report generator `images/ubuntu/scripts/docs-gen/Generate-SoftwareReport.ps1`. The software report generator is used to generate an image's README file, e.g. [Ubuntu2004-Readme.md](images/ubuntu/Ubuntu2004-README.md) and it uses [MarkdownPS](https://github.com/Sarafian/MarkdownPS).
### macOS
The macOS source lives in this repository and is available for everyone. However, the macOS image-generation CI doesn't support external contributions yet, so we are not able to accept pull requests for now.
We are in the process of preparing the macOS CI to accept contributions. Until then, we appreciate your patience and ask that you continue to make tool requests by filing issues.
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+216 -1
View File
@@ -1 +1,216 @@
# create-win-version
# GitHub Actions Runner Images
**Table of Contents**
- [About](#about)
- [Available Images](#available-images)
- [Announcements](#announcements)
- [Image Definitions](#image-definitions)
- [Image Releases](#image-releases)
- [Software and Image Support](#software-and-image-support)
- [How to Interact with the Repo](#how-to-interact-with-the-repo)
- [FAQs](#faqs)
## About
This repository contains the source code used to create the VM images for [GitHub-hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners) used for Actions, as well as for [Microsoft-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops#use-a-microsoft-hosted-agent) used for Azure Pipelines.
To build a VM machine from this repo's source, see the [instructions](docs/create-image-and-azure-resources.md).
## Available Images
| Image | YAML Label | Included Software | Rollout Status of Latest Image Release |
| --------------------|---------------------|--------------------|--------------------|
| Ubuntu 24.04 | `ubuntu-latest` or `ubuntu-24.04` | [ubuntu-24.04] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fubuntu24.json) |
| Ubuntu 22.04 | `ubuntu-22.04` | [ubuntu-22.04] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fubuntu22.json) |
| Ubuntu 20.04 | `ubuntu-20.04` | [ubuntu-20.04] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fubuntu20.json) |
| macOS 15 <sup>beta</sup> | `macos-15-large`| [macOS-15] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-15.json) |
| macOS 15 Arm64 <sup>beta</sup> | `macos-15` or `macos-15-xlarge` | [macOS-15-arm64] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-15-arm64.json) |
| macOS 14 | `macos-latest-large` or `macos-14-large`| [macOS-14] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-14.json) |
| macOS 14 Arm64 |`macos-latest`, `macos-14`, `macos-latest-xlarge` or `macos-14-xlarge`| [macOS-14-arm64] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-14-arm64.json) |
| macOS 13 | `macos-13` or `macos-13-large` | [macOS-13] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-13.json) |
| macOS 13 Arm64 | `macos-13-xlarge` | [macOS-13-arm64] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-13-arm64.json) |
| macOS 12 <sup>deprecated</sup> | `macos-12` or `macos-12-large`| [macOS-12] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fmacos-12.json) |
| Windows Server 2022 | `windows-latest` or `windows-2022` | [windows-2022] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fwin22.json) |
| Windows Server 2019 | `windows-2019` | [windows-2019] | ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fijunaidm%2F0b5459bbe18604a639e1d2f7accd204f%2Fraw%2Fwin19.json) |
### Label scheme
- In general the `-latest` label is used for the latest OS image version that is GA
- Before moving the`-latest` label to a new OS version we will announce the change and give sufficient lead time for users to update their workflows
[ubuntu-24.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md
[ubuntu-22.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md
[ubuntu-20.04]: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2004-Readme.md
[windows-2022]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md
[windows-2019]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2019-Readme.md
[macOS-12]: https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md
[macOS-13]: https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md
[macOS-13-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-13-arm64-Readme.md
[macOS-14]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
[macOS-14-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md
[macOS-15]: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
[macOS-15-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md
[self-hosted runners]: https://help.github.com/en/actions/hosting-your-own-runners
## Announcements
See notable upcoming changes by viewing issues with the [Announcement](https://github.com/actions/runner-images/labels/Announcement) label.
## Image Definitions
### Beta
The purpose of a Beta is to collect feedback on an image before it is released to GA. The goal of a Beta is to identify and fix any potential issues that exist on that
image. Images are updated on a weekly cadence. Any workflows that run on a beta image do not fall under the customer [SLA](https://github.com/customer-terms/github-online-services-sla) in place for Actions.
Customers choosing to use Beta images are encouraged to provide feedback in the runner-images repo by creating an issue. A Beta may take on different availability, i.e. public vs private.
### GA
A GA (General Availability) image has been through a Beta period and is deemed ready for general use. Images are updated on a weekly cadence. In order to be moved to
GA the image must meet the following criteria:
1. Has been through a Beta period (public or private)
2. Most major software we install on the image has a compatible
version for the underlying OS and
3. All major bugs reported during the Beta period have been addressed.
This image type falls under the customer [SLA](https://github.com/customer-terms/github-online-services-sla) for actions. GA images are eventually deprecated according to our guidelines as we only support the
latest 2 versions of an OS.
#### Latest Migration Process
GitHub Actions and Azure DevOps use the `-latest` YAML label (ex: `ubuntu-latest`, `windows-latest`, and `macos-latest`). These labels point towards the newest stable OS version available.
The `-latest` migration process is gradual and happens over 1-2 months in order to allow customers to adapt their workflows to the newest OS version. During this process, any workflow using the `-latest` label, may see changes in the OS version in their workflows or pipelines. To avoid unwanted migration, users can specify a specific OS version in the yaml file (ex: macos-14, windows-2022, ubuntu-22.04).
## Image Releases
*How to best follow along with changes*
1. Find the latest releases for this repository [here.](https://github.com/actions/runner-images/releases)
2. Subscribe to the releases coming out of this repository, instructions [here.](https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#configuring-your-watch-settings-for-an-individual-repository)
3. Upcoming changes: A pre-release is created when the deployment of an image has started. As soon as the deployment is finished, the pre-release is converted to a release. If you have subscribed to releases, you will get notified of pre-releases as well.
- You can also track upcoming changes using the [awaiting-deployment](https://github.com/actions/runner-images/labels/awaiting-deployment) label.
4. For high impact changes, we will post these in advance to the GitHub Changelog on our [blog](https://github.blog/changelog/) and on [twitter](https://twitter.com/GHchangelog).
- Ex: breaking changes, GA or deprecation of images
*Cadence*
- We typically deploy weekly updates to the software on the runner images.
## Software and Image Support
### Support Policy
- Tools and versions will typically be removed 6 months after they are deprecated or have reached end-of-life
- We support (at maximum) 2 GA images and 1 beta image at a time. We begin the deprecation process of the oldest image label once the newest OS image label has been released to GA.
- The images generally contain the latest versions of packages installed except for Ubuntu LTS where we mostly rely on the Canonical-provided repositories.
- Popular tools can have several versions installed side-by-side with the following strategy:
| Tool name | Installation strategy |
|-----------|-----------------------|
| Docker images | not more than 3 latest LTS OS\tool versions. New images or new versions of current images are added using the standard tool request process |
| Java | all LTS versions |
| Node.js | 3 latest LTS versions |
| Go | 3 latest minor versions |
| Python <br/> Ruby | 5 most popular `major.minor` versions |
| PyPy | 3 most popular `major.minor` versions |
| .NET Core | 2 latest LTS versions and 1 latest version. For each feature version only latest patch is installed |
| GCC <br/> GNU Fortran <br/> Clang <br/> GNU C++ | 3 latest major versions |
| Android NDK | 1 latest non-LTS, 2 latest LTS versions |
| Xcode | - all OS compatible versions side-by-side <br/> - for beta, GM versions - latest beta only <br/> - old patch versions are deprecated in 3 months |
### Package managers usage
We use third-party package managers to install software during the image generation process. The table below lists the package managers and the software installed.
> **Note**: third-party repositories are re-evaluated every year to identify if they are still useful and secure.
| Operating system | Package manager | Third-party repos and packages |
| :--- | :---: | ---: |
| Ubuntu | [APT](https://wiki.debian.org/Apt) | [containers](https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable) (Ubuntu 20 only) <br/> [docker](https://download.docker.com/linux/ubuntu) (Ubuntu20 0nly) <br/> [Eclipse-Temurin (Adoptium)](https://packages.adoptium.net/artifactory/deb/) <br/> [Erlang](https://packages.erlang-solutions.com/ubuntu) <br/> [Firefox](http://ppa.launchpad.net/mozillateam/ppa/ubuntu) <br/> [git-lfs](https://packagecloud.io/install/repositories/github/git-lfs) <br/> [git](https://launchpad.net/~git-core/+archive/ubuntu/ppa) <br/> [Google Cloud CLI](https://packages.cloud.google.com/apt) <br/> [Heroku](https://cli-assets.heroku.com/channels/stable/apt) <br/> [HHvm](https://dl.hhvm.com/ubuntu) <br/> [MongoDB](https://repo.mongodb.org/apt/ubuntu) <br/> [Mono](https://download.mono-project.com/repo/ubuntu) <br/> [MS Edge](https://packages.microsoft.com/repos/edge) <br/> [PostgreSQL](https://apt.postgresql.org/pub/repos/apt/) <br/> [R](https://cloud.r-project.org/bin/linux/ubuntu) |
| | [pipx](https://pypa.github.io/pipx) | ansible-core <br/>yamllint |
| Windows | [Chocolatey](https://chocolatey.org) | No third-party repos installed |
| macOS | [Homebrew](https://brew.sh) | [aws-cli v2](https://github.com/aws/homebrew-tap) </br> [azure/bicep](https://github.com/Azure/homebrew-bicep) </br> [mongodb/brew](https://github.com/mongodb/homebrew-brew) |
| | [pipx](https://pypa.github.io/pipx/) | yamllint |
### Image Deprecation Policy
- Images begin the deprecation process of the oldest image label once a new GA OS version has been released.
- Deprecation process begins with an announcement that sets a date for deprecation
- As it gets closer to the date, GitHub begins doing scheduled brownouts of the image
- During this time there will be an Announcement pinned in the repo to remind users of the deprecation.
- Finally GitHub will deprecate the image and it will no longer be available
### Preinstallation Policy
In general, these are the guidelines we follow when deciding what to pre-install on our images:
- Popularity: widely-used tools and ecosystems will be given priority.
- Latest Technology: recent versions of tools will be given priority.
- Deprecation: end-of-life tools and versions will not be added.
- Licensing: MIT, Apache, or GNU licenses are allowed.
- Time & Space on the Image: we will evaluate how much time is saved and how much space is used by having the tool pre-installed.
- Support: If a tool requires the support of more than one version, we will consider the cost of this maintenance.
### Default Version Update Policy
- In general, once a new version is installed on the image, we announce the default version update 2 weeks prior to deploying it.
- For potentially dangerous updates, we may extend the timeline up to 1 month between the announcement and deployment.
## How to Interact with the Repo
- **Issues**: To file a bug report, or request tools to be added/updated, please [open an issue using the appropriate template](https://github.com/actions/runner-images/issues/new/choose)
- **Discussions**: If you want to share your thoughts about image configuration, installed software, or bring a new idea, please create a new topic in a [discussion](https://github.com/actions/runner-images/discussions) for a corresponding category. Before making a new discussion please make sure no similar topics were created earlier.
- For general questions about using the runner images or writing your Actions workflow, please open requests in the [GitHub Actions Community Forum](https://github.community/c/github-actions/41).
## FAQs
<details>
<summary><b><i>What images are available for GitHub Actions and Azure DevOps?</b></i></summary>
The availability of images for GitHub Actions and Azure DevOps is the same. However, deprecation policies may differ. See documentation for more details:
- [GitHub Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners#supported-runners-and-hardware-resources)
- [Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software)
</details>
<details>
<summary><b><i>What image version is used in my build?</b></i></summary>
Usually, image deployment takes 2-3 days, and documentation in the `main` branch is only updated when deployment is finished. To find out which image version and what software versions are used in a specific build, see `Set up job` (GitHub Actions) or `Initialize job` (Azure DevOps) step log.
<img width="1440" alt="actions-runner-image" src="https://github.com/actions/runner-images/assets/88318005/922a8bf5-3e4d-4265-9527-b3b51e6bf9c8">
</details>
<details>
<summary><b><i>Looking for other Linux distributions?</b></i></summary>
We do not plan to offer other Linux distributions. We recommend using Docker if you'd like to build using other distributions with the hosted runner images. Alternatively, you can leverage [self-hosted runners] and fully customize your VM image to your needs.
</details>
<details>
<summary><b><i>How do I contribute to the macOS source?</b></i></summary>
macOS source lives in this repository and is available for everyone. However, macOS image-generation CI doesn't support external contributions yet so we are not able to accept pull-requests for now.
We are in the process of preparing macOS CI to accept contributions. Until then, we appreciate your patience and ask you to continue to make tool requests by filing issues.
</details>
<details>
<summary><b><i>How does GitHub determine what tools are installed on the images?</b></i></summary>
For some tools, we always install the latest at the time of the deployment; for others, we pin the tool to specific version(s). For more details please see the [Preinstallation Policy](#preinstallation-policy)
</details>
<details>
<summary><b><i>How do I request that a new tool be pre-installed on the image?</b></i></summary>
Please create an issue and get an approval from us to add this tool to the image before creating the pull request.
</details>
<details>
<summary><b><i>What branch should I use to build custom image?</b></i></summary>
We strongly encourage customers to build their own images using the main branch.
This repository contains multiple branches and releases that serve as document milestones to reflect what software is installed in the images at certain point of time. Current builds are not idempotent and if one tries to build a runner image using the specific tag it is not guaranteed that the build will succeed.
</details>
+3
View File
@@ -0,0 +1,3 @@
If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty](https://hackerone.com/github)
Thanks for helping make GitHub Actions safe for everyone.
+311
View File
@@ -0,0 +1,311 @@
# GitHub Actions Runner Images
The runner-images project uses [Packer](https://www.packer.io/) to generate disk images for Windows 2019/2022 and Ubuntu 20.04/22.04.
Each image is configured by a HCL2 Packer template that specifies where to build the image (Azure, in this case),
and what steps to run to install software and prepare the disk.
The Packer process initializes a connection to the Azure subscription using Azure CLI and creates temporary resources
required for the build process: a resource group, network interfaces and a virtual machine from the "clean" image specified in the template.
If the VM deployment succeeds, Packer connects to it using SSH or WinRM and begins executing installation steps from the template one-by-one.
If any step fails, image generation is aborted, and the temporary VM is terminated.
Packer also attempts to clean up all the temporary resources it created (unless otherwise configured).
After successful completion of all installation steps, Packer creates a managed image from the temporary VM's disk and deletes the VM.
- [Build Agent Preparation](#build-agent-preparation)
- [Manual image generation](#manual-image-generation)
- [Manual Image Generation Customization](#manual-image-generation-customization)
- [Network Security](#network-security)
- [Azure Subscription Authentication](#azure-subscription-authentication)
- [Generated Machine Deployment](#generated-machine-deployment)
- [Automated image generation](#automated-image-generation)
- [Required variables](#required-variables)
- [Optional variables](#optional-variables)
- [Builder variables](#builder-variables)
- [Toolset](#toolset)
- [Post-generation scripts](#post-generation-scripts)
- [Running scripts](#running-scripts)
- [Script Details: Ubuntu](#script-details-ubuntu)
- [Script Details: Windows](#script-details-windows)
## Build Agent Preparation
The build agent is a machine where the Packer process will be started.
You can use any physical or virtual machine running Windows or Linux OS.
Of course, you may also use an [Azure VM](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/quick-create-cli).
In any case, you will need these software installed:
- Packer 1.8.2 or higher.
Download and install it manually from [here](https://www.packer.io/downloads) or use [Chocolatey](https://chocolatey.org/):
```powershell
choco install packer
```
- Git.
For Linux - install the latest version from your distro's package repo.
For Windows - download and install it from [here](https://gitforwindows.org/) of use [Chocolatey](https://chocolatey.org/):
```powershell
choco install git -params '"/GitAndUnixToolsOnPath"'
```
- Powershell 5.0 or higher.
In Windows you already have it.
For Linux follow instructions [here](https://learn.microsoft.com/en-us/windows-server/administration/linux-package-repository-for-microsoft-software)
to add Microsoft's Linux Software Repository and then install the `powershell` package.
- Azure CLI.
Follow the instructions [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli).
Or if you use Windows, you may run this command in Powershell instead:
```powershell
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
```
## Manual image generation
This repository includes a script that assists in generating images in Azure.
All you need is an Azure subscription and a build agent configured as described above.
We suggest starting with building the UbuntuMinimal image because it includes only basic software and builds in less than 30 minutes.
All the commands below should be executed in PowerShell.
First, clone the runner-images repository and set the current directory to it:
```powershell
git clone https://github.com/actions/runner-images.git
Set-Location runner-images
```
Then, import the [GenerateResourcesAndImage](../helpers/GenerateResourcesAndImage.ps1) script from the `helpers` subdirectory:
```powershell
Import-Module .\helpers\GenerateResourcesAndImage.ps1
```
Finally, run the `GenerateResourcesAndImage` function, setting the mandatory arguments: image type and where to build and store the resulting managed image:
- `SubscriptionId` - your Azure Subscription ID;
- `ResourceGroupName` - the name of the resource group that will be created within your subscription (e.g., "imagegen-test");
- `AzureLocation` - the location where resources will be created (e.g., "East US");
- `ImageType` - the type of image to build (we suggest choosing "UbuntuMinimal" here; other valid options are "Windows2019", "Windows2022", "Ubuntu2004", "Ubuntu2204").
This function automatically creates all required Azure resources and initiates the Packer image generation for the selected image type.
When the image is ready, you may proceed to [deployment](#generated-machine-deployment).
## Manual Image Generation Customization
The `GenerateResourcesAndImage` function accepts a number of arguments that may assist you in generating an image in your specific environment.
For example, you may want all the resources involved in the image generation process to be tagged.
In this case, pass a HashTable of tags as a value for the `Tags` parameter.
If you don't want the function to authenticate interactively, you should create a Service Principal and invoke the function with the parameters `AzureClientId`, `AzureClientSecret` and `AzureTenantId`.
You can find more details in the [corresponding section below](#azure-subscription-authentication).
Use `get-help GenerateResourcesAndImage -Detailed` for the complete list of available parameters.
### Network Security
To connect to a temporary virtual machine, Packer uses WinRM or SSH.
If your build agent is located outside of the Azure subscription where the temporary VM is created, a public network interface and public IP address are used.
Make sure that firewalls are configured properly and that WinRM (TCP port 5986) and SSH (TCP port 22) connections are allowed both outgoing for the build agent and incoming for the temporary VM.
Also, if you don't want the temporary VM to be accessible from everywhere, set the `RestrictToAgentIpAddress` parameter value to `$true`
to set up firewall rules allowing access only from your build agent's public IP address.
If your build agent and temporary VM are in the same subscription, you can configure Packer to connect using a private virtual network.
To achieve this, set proper values for the environment variables `VNET_RESOURCE_GROUP`, `VNET_NAME` and `VNET_SUBNET`.
### Azure Subscription Authentication
Packer uses a Service Principal to authenticate in Azure infrastructure.
For more information about Service Principals, refer to the
[Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal).
The `GenerateResourcesAndImage` function is able to create a Service Principal to be used by Packer.
It uses the Connect-AzAccount cmdlet that invokes an interactive authentication process by default.
If you don't want to use interactive authentication, you should create a Service Principal with full read-write permissions for the selected Azure subscription on your own
and provide proper values for the parameters `AzureClientId`, `AzureClientSecret` and `AzureTenantId`.
Here is an example of how to create a Service Principal using the Az PowerShell module:
```powershell
$credentials = [Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential]@{
StartDateTime = Get-Date
EndDateTime = (Get-Date).AddDays(7)
}
$sp = New-AzADServicePrincipal -DisplayName "imagegen-app"
$appCred = New-AzADAppCredential -ApplicationId $sp.AppId -PasswordCredentials $credentials
Start-Sleep -Seconds 30
New-AzRoleAssignment -RoleDefinitionName "Contributor" -PrincipalId $sp.Id
Start-Sleep -Seconds 30
@{
ClientId = $sp.AppId
ClientSecret = $appCred.SecretText
TenantId = (Get-AzSubscription -SubscriptionId $SubscriptionId).TenantId
}
```
## Generated Machine Deployment
After successful image generation, a Virtual Machine can be created from the generated image using the [CreateAzureVMFromPackerTemplate](../helpers/CreateAzureVMFromPackerTemplate.ps1) script.
```powershell
Import-Module .\helpers\CreateAzureVMFromPackerTemplate.ps1
CreateAzureVMFromPackerTemplate -SubscriptionId {YourSubscriptionId} -ResourceGroupName {ResourceGroupName} -ManagedImageName "Runner-Image-Ubuntu2204" -VirtualMachineName "testvm1" -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
```
Where:
- `SubscriptionId` - the Azure subscription ID where resources will be created;
- `ResourceGroupName` - the Azure resource group name where the Azure virtual machine will be created;
- `ManagedImageName` - the name of the managed image to be used for the virtual machine creation;
- `VirtualMachineName` - the name of the virtual machine to be generated;
- `AdminUserName` - the administrator username for the virtual machine to be created;
- `AdminPassword` - the administrator password for the virtual machine to be created;
- `AzureLocation` - the location where the Azure virtual machine will be provisioned (e.g., "eastus").
This function creates an Azure VM and generates network resources in Azure to make the VM accessible.
## Automated image generation
If you want to generate images automatically (e.g., as a part of a CI/CD pipeline),
you can use Packer directly. To do this, you will need:
- a build agent configured as described in the
[Build agent preparation](#build-agent-preparation) section;
- an Azure subscription and Service Principal configured as described in the
[Azure subscription authentication](#azure-subscription-authentication) section;
- a resource group created in your Azure subscription where the managed image will be stored;
- a string to be used as a password for the user used to install software (Windows only).
Then, you can invoke Packer in your CI/CD pipeline using the following command:
```powershell
packer build -var "subscription_id=$SubscriptionId" `
-var "client_id=$ClientId" `
-var "client_secret=$ClientSecret" `
-var "install_password=$InstallPassword" `
-var "location=$Location" `
-var "managed_image_name=$ImageName" `
-var "managed_image_resource_group_name=$ImageResourceGroupName" `
-var "tenant_id=$TenantId" `
$TemplatePath
```
Where:
- `SubscriptionId` - your Azure Subscription ID;
- `ClientId` and `ClientSecret` - Service Principal credentials;
- `TenantId` - Azure Tenant ID;
- `InstallPassword` - password for the user used to install software (Windows only);
- `Location` - location where resources will be created (e.g., "East US");
- `ImageName` and `ImageResourceGroupName` - name of the resource group where the managed image will be stored;
- `TemplatePath` - path to the Packer template file (e.g., "images/windows/templates/windows-2022.pkr.hcl").
### Required variables
The following variables are required to be passed to the Packer process:
| Template var | Env var | Description
| ------------ | ------- | -----------
| `subscription_id` | `ARM_SUBSCRIPTION_ID` | The subscription under which the build will be performed.
| `client_id` | `ARM_CLIENT_ID` | The Active Directory service principal associated with your builder.
| `client_secret` | `ARM_CLIENT_SECRET` | The password or secret for your service principal; may be omitted if `client_cert_path` is set.
| `client_cert_path` | `ARM_CLIENT_CERT_PATH` | The location of a PEM file containing a certificate and private key for the service principal; may be omitted if `client_secret` is set.
| `location` | `ARM_RESOURCE_LOCATION` | The Azure datacenter in which your VM will be built.
| `managed_image_resource_group_name` | `ARM_RESOURCE_GROUP` | The resource group under which the final artifact will be stored.
### Optional variables
The following variables are optional:
- `managed_image_name` - the name of the managed image to create. If not specified, "Runner-Image-{{ImageType}}" will be used;
- `build_resource_group_name` - specify an existing resource group to run the build in; by default, a temporary resource group will be created and destroyed as part of the build; if you do not have permission to do so, use `build_resource_group_name` to specify an existing resource group to run the build in;
- `object_id` - the object ID for the AAD SP; will be derived from the oAuth token if empty;
- `tenant_id` - the Active Directory tenant identifier with which your `client_id` and `subscription_id` are associated; if not specified, `tenant_id` will be looked up using `subscription_id`;
- `temp_resource_group_name` - the name assigned to the temporary resource group created during the build; if this value is not set, a random value will be assigned; this resource group is deleted at the end of the build;
- `private_virtual_network_with_public_ip` - this value allows you to set a `virtual_network_name` and obtain a public IP; if this value is not set and `virtual_network_name` is defined, Packer is only allowed to be executed from a host on the same subnet / virtual network;
- `virtual_network_name` - use a pre-existing virtual network for the VM; this option enables private communication with the VM, no public IP address is used or provisioned (unless you set `private_virtual_network_with_public_ip`);
- `virtual_network_resource_group_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the resource group containing the virtual network; if the resource group cannot be found, or it cannot be disambiguated, this value should be set;
- `virtual_network_subnet_name` - if `virtual_network_name` is set, this value may also be set; if `virtual_network_name` is set, and this value is not set, the builder attempts to determine the subnet to use with the virtual network; if the subnet cannot be found, or it cannot be disambiguated, this value should be set.
## Builder variables
The `builders` section contains variables for the `azure-arm` builder used in the project. Most of the builder variables are inherited from the `user variables` section, however, the variables can be overwritten to adjust image-generation performance.
- `vm_size` - the size of the VM used for building; this can be changed when you deploy a VM from your image;
- `image_os` - the type of OS that will be deployed as a temporary VM;
- `image_version` - specify the version of an OS to boot from.
**Detailed Azure builders documentation can be found in the [packer documentation](https://www.packer.io/docs/builders/azure).**
## Toolset
The configuration for some installed software is located in `toolset.json` files. These files define the list of Ruby, Python, Go versions, the list of PowerShell modules and VS components that will be installed on the image. They can be changed if these tools are not required, to reduce image generation time or image size.
Generated tool versions and details can be found in related projects:
- [Python](https://github.com/actions/python-versions/)
- [Go](https://github.com/actions/go-versions)
- [Node](https://github.com/actions/node-versions)
## Post-generation scripts
> :warning: These scripts are intended to be run on a VM deployed in Azure
The user, created during the image generation, does not exist in the resulting image. Hence, some configuration files related to the user's home directory need to be changed, as well as the file permissions for some directories. Scripts for that are located in the `post-gen` folder in the repository:
- Windows: <https://github.com/actions/runner-images/tree/main/images/windows/assets/post-gen>
- Linux: <https://github.com/actions/runner-images/tree/main/images/ubuntu/assets/post-gen>
**Note:** The default user for Linux should have `sudo privileges`.
The scripts are copied to the image during the generation process to the following paths:
- Windows: `C:\post-generation`
- Linux: `/opt/post-generation`
### Running scripts
- Ubuntu
```bash
sudo su -c "find /opt/post-generation -mindepth 1 -maxdepth 1 -type f -name '*.sh' -exec bash {} \;"
```
- Windows
```powershell
Get-ChildItem C:\post-generation -Filter *.ps1 | ForEach-Object { & $_.FullName }
```
### Script Details: Ubuntu
- **cleanup-logs.sh** - removes all build process logs from the machine;
- **environment-variables.sh** - replaces `$HOME` with the default user's home directory for environment variables related to the default user home directory;
- **homebrew-permissions.sh** - resets the Homebrew repository directory by running `git reset --hard` to make the working tree clean after changing permissions in /home and changes the repository directory owner to the current user;
- **rust-permissions.sh** - fixes permissions for the Rust folder; a detailed issue explanation is provided in [runner-images/issues/572](https://github.com/actions/runner-images/issues/572).
### Script Details: Windows
- **GenerateIISExpressCertificate.ps1** - generates and imports a certificate to run applications with IIS Express through HTTPS;
- **InternetExplorerConfiguration.ps1** - turns off the Internet Explorer Enhanced Security feature;
- **Msys2FirstLaunch.ps1** - initializes the bash user profile in MSYS2;
- **VSConfiguration.ps1** - performs initial Visual Studio configuration.
@@ -0,0 +1,85 @@
Function CreateAzureVMFromPackerTemplate {
<#
.SYNOPSIS
A helper function to deploy a VM from a generated image.
.DESCRIPTION
Creates an Azure VM from a template. Also generates network resources in Azure to make the VM accessible.
.PARAMETER SubscriptionId
The Azure subscription Id where resources will be created.
.PARAMETER ResourceGroupName
The Azure resource group name where the Azure virtual machine will be created.
.PARAMETER ManagedImageName
The name of the managed image to be used to create the virtual machine.
.PARAMETER VirtualMachineName
The name of the virtual machine to be generated.
.PARAMETER AdminUserName
The administrator username for the virtual machine to be created.
.PARAMETER AdminPassword
The administrator password for the virtual machine to be created.
.PARAMETER AzureLocation
The location where the Azure virtual machine will be provisioned. Example: "eastus"
.EXAMPLE
CreateAzureVMFromPackerTemplate -SubscriptionId {SubscriptionId} -ResourceGroupName {ResourceGroupName} -VirtualMachineName "testvm1" -ManagedImageName {ManagedImageName} -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
#>
param (
[Parameter(Mandatory = $True)]
[string] $SubscriptionId,
[Parameter(Mandatory = $True)]
[string] $ResourceGroupName,
[Parameter(Mandatory = $True)]
[string] $ManagedImageName,
[Parameter(Mandatory = $True)]
[string] $VirtualMachineName,
[Parameter(Mandatory = $True)]
[string] $AdminUsername,
[Parameter(Mandatory = $True)]
[string] $AdminPassword,
[Parameter(Mandatory = $True)]
[string] $AzureLocation
)
$vmSize = "Standard_DS2_v2"
$guid = [System.GUID]::NewGuid().ToString().ToUpper()
$vnetName = $env:UserName + "vnet-" + $guid
$subnetName = $env:UserName + "subnet-" + $guid
$nicName = $env:UserName + "nic-" + $guid
$publicIpName = $env:UserName + "pip-" + $guid
Write-Host "Creating a virtual network and subnet"
($vnet = az network vnet create -g $ResourceGroupName -l $AzureLocation -n $vnetName --address-prefixes 10.0.0.0/16 --subnet-name $subnetName --subnet-prefixes 10.0.1.0/24 --subscription $subscriptionId -o json)
$subnetId = ($vnet | ConvertFrom-Json).newVNet.subnets[0].id
Write-Host "`nCreating a network interface controller (NIC)"
($nic = az network nic create -g $ResourceGroupName -l $AzureLocation -n $nicName --subnet $subnetId --subscription $subscriptionId -o json)
$networkId = ($nic | ConvertFrom-Json).NewNIC.id
Write-Host "`nCreating a public IP address"
($publicIp = az network public-ip create -g $ResourceGroupName -l $AzureLocation -n $publicIpName --allocation-method Static --sku Basic --version IPv4 --subscription $subscriptionId -o json)
$publicIpId = ($publicIp | ConvertFrom-Json).publicIp.id
Write-Host "`nAdding the public IP to the NIC"
az network nic ip-config update -g $ResourceGroupName -n ipconfig1 --nic-name $nicName --public-ip-address $publicIpId --subscription $subscriptionId
Write-Host "`nCreating the VM"
az vm create `
--resource-group $ResourceGroupName `
--name $VirtualMachineName `
--image $ManagedImageName `
--size $vmSize `
--admin-username $AdminUsername `
--admin-password $AdminPassword `
--nics $networkId `
--subscription $subscriptionId `
--location $AzureLocation
Write-Host "`nCreated in ${ResourceGroupName}:`n vnet ${vnetName}`n subnet ${subnetName}`n nic ${nicName}`n publicip ${publicIpName}`n vm ${VirtualMachineName}"
}
+396
View File
@@ -0,0 +1,396 @@
$ErrorActionPreference = 'Stop'
enum ImageType {
Windows2019 = 1
Windows2022 = 2
Ubuntu2004 = 3
Ubuntu2204 = 4
Ubuntu2404 = 5
UbuntuMinimal = 6
Windows2025 = 7
}
Function Get-PackerTemplatePath {
param (
[Parameter(Mandatory = $True)]
[string] $RepositoryRoot,
[Parameter(Mandatory = $True)]
[ImageType] $ImageType
)
switch ($ImageType) {
# Note: Double Join-Path is required to support PowerShell 5.1
([ImageType]::Windows2019) {
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "windows-2019.pkr.hcl"
}
([ImageType]::Windows2022) {
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "windows-2022.pkr.hcl"
}
([ImageType]::Windows2025) {
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "windows-2025.pkr.hcl"
}
([ImageType]::Ubuntu2004) {
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-20.04.pkr.hcl"
}
([ImageType]::Ubuntu2204) {
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-22.04.pkr.hcl"
}
([ImageType]::Ubuntu2404) {
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-24.04.pkr.hcl"
}
([ImageType]::UbuntuMinimal) {
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "ubuntu-minimal.pkr.hcl"
}
default { throw "Unknown type of image" }
}
$imageTemplatePath = [IO.Path]::Combine($RepositoryRoot, "images", $relativeTemplatePath)
if (-not (Test-Path $imageTemplatePath)) {
throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'."
}
return $imageTemplatePath;
}
Function Show-LatestCommit {
[CmdletBinding()]
param()
process {
$latestCommit = (git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1)
Write-Host "Latest commit: $latestCommit."
}
}
function Start-Sleep($seconds) {
$doneDT = (Get-Date).AddSeconds($seconds)
while ($doneDT -gt (Get-Date)) {
$secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds
$percent = ($seconds - $secondsLeft) / $seconds * 100
Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining $secondsLeft -PercentComplete $percent
[System.Threading.Thread]::Sleep(500)
}
Write-Progress -Activity "Sleeping" -Status "Sleeping..." -SecondsRemaining 0 -Completed
}
Function GenerateResourcesAndImage {
<#
.SYNOPSIS
A helper function to help generate an image.
.DESCRIPTION
This function will generate the Azure resources and image for the specified image type.
.PARAMETER SubscriptionId
The Azure subscription id where the Azure resources will be created.
.PARAMETER ResourceGroupName
The name of the resource group to create the Azure resources in.
.PARAMETER ImageType
The type of image to generate. Valid values are: Windows2019, Windows2022, Ubuntu2004, Ubuntu2204, UbuntuMinimal.
.PARAMETER ManagedImageName
The name of the managed image to create. The default is "Runner-Image-{{ImageType}}".
.PARAMETER AzureLocation
The Azure location where the Azure resources will be created. For example: "East US"
.PARAMETER ImageGenerationRepositoryRoot
The root directory of the image generation repository. This is used to locate the packer template.
.PARAMETER SecondsToWaitForServicePrincipalSetup
The number of seconds to wait for the service principal to be setup. The default is 120 seconds.
.PARAMETER AzureClientId
The Azure client id to use to authenticate with Azure. If not specified, the current user's credentials will be used.
.PARAMETER AzureClientSecret
The Azure client secret to use to authenticate with Azure. If not specified, the current user's credentials will be used.
.PARAMETER AzureTenantId
The Azure tenant id to use to authenticate with Azure. If not specified, the current user's credentials will be used.
.PARAMETER RestrictToAgentIpAddress
If set, access to the VM used by packer to generate the image is restricted to the public IP address this script is run from.
This parameter cannot be used in combination with the virtual_network_name packer parameter.
.PARAMETER Force
Delete the resource group if it exists without user confirmation.
This parameter is deprecated and will be removed in a future release.
.PARAMETER ReuseResourceGroup
Reuse the resource group if it exists without user confirmation.
This parameter is deprecated and will be removed in a future release.
.PARAMETER OnError
Specify how packer handles an error during image creation.
Options:
abort - abort immediately
ask - ask user for input
cleanup - attempt to cleanup and then abort
run-cleanup-provisioner - run the cleanup provisioner and then abort
The default is 'ask'.
.PARAMETER Tags
Tags to be applied to the Azure resources created.
.EXAMPLE
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\runner-images" -ImageType Ubuntu2004 -AzureLocation "East US"
#>
param (
[Parameter(Mandatory = $True)]
[string] $SubscriptionId,
[Parameter(Mandatory = $True)]
[string] $ResourceGroupName,
[Parameter(Mandatory = $True)]
[ImageType] $ImageType,
[Parameter(Mandatory = $False)]
[string] $ManagedImageName = "Runner-Image-$($ImageType)",
[Parameter(Mandatory = $True)]
[string] $AzureLocation,
[Parameter(Mandatory = $False)]
[string] $ImageGenerationRepositoryRoot = $pwd,
[Parameter(Mandatory = $False)]
[int] $SecondsToWaitForServicePrincipalSetup = 120,
[Parameter(Mandatory = $False)]
[string] $AzureClientId,
[Parameter(Mandatory = $False)]
[string] $AzureClientSecret,
[Parameter(Mandatory = $False)]
[string] $AzureTenantId,
[Parameter(Mandatory = $False)]
[switch] $RestrictToAgentIpAddress,
[Parameter(Mandatory = $False)]
[switch] $Force,
[Parameter(Mandatory = $False)]
[switch] $ReuseResourceGroup,
[Parameter(Mandatory = $False)]
[ValidateSet("abort", "ask", "cleanup", "run-cleanup-provisioner")]
[string] $OnError = "ask",
[Parameter(Mandatory = $False)]
[hashtable] $Tags = @{}
)
if ($Force -or $ReuseResourceGroup) {
Write-Warning "The `ReuseResourceGroup` and `Force` parameters are deprecated and will be removed in a future release. The resource group will be reused when it already exists and an error will be thrown when it doesn't. If you want to delete the resource group, please delete it manually."
}
if ($Force -and $ReuseResourceGroup) {
throw "Force and ReuseResourceGroup cannot be used together."
}
Show-LatestCommit -ErrorAction SilentlyContinue
# Validate packer is installed
$PackerBinary = Get-Command "packer"
if (-not ($PackerBinary)) {
throw "'packer' binary is not found on PATH."
}
# Get template path
$TemplatePath = Get-PackerTemplatePath -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
Write-Debug "Template path: $TemplatePath."
# Prepare list of allowed inbound IP addresses
if ($RestrictToAgentIpAddress) {
$AgentIp = (Invoke-RestMethod http://ipinfo.io/json).ip
if (-not $AgentIp) {
throw "Unable to determine agent IP address."
}
Write-Host "Access to packer generated VM will be restricted to agent IP Address: $AgentIp."
if ($PSVersionTable.PSVersion.Major -eq 5) {
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses."
$AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp
}
elseif ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) {
Write-Verbose "PowerShell 7.0-7.2 detected. Replacing double quotes with escaped double quotes in allowed inbound IP addresses."
$AllowedInboundIpAddresses = '[\"{0}\"]' -f $AgentIp
}
else {
$AllowedInboundIpAddresses = '["{0}"]' -f $AgentIp
}
}
else {
$AllowedInboundIpAddresses = "[]"
}
Write-Debug "Allowed inbound IP addresses: $AllowedInboundIpAddresses."
# Prepare tags
$TagsList = $Tags.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }
Write-Debug "Tags list: $TagsList."
$TagsJson = $Tags | ConvertTo-Json -Compress
if ($PSVersionTable.PSVersion.Major -eq 5) {
Write-Verbose "PowerShell 5 detected. Replacing double quotes with escaped double quotes in tags JSON."
$TagsJson = $TagsJson -replace '"', '\"'
}
elseif ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) {
Write-Verbose "PowerShell 7.0-7.2 detected. Replacing double quotes with escaped double quotes in tags JSON."
$TagsJson = $TagsJson -replace '"', '\"'
}
Write-Debug "Tags JSON: $TagsJson."
$InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
Write-Host "Downloading packer plugins..."
& $PackerBinary init $TemplatePath
if ($LastExitCode -ne 0) {
throw "Packer plugins download failed."
}
Write-Host "Validating packer template..."
& $PackerBinary validate `
"-var=client_id=fake" `
"-var=client_secret=fake" `
"-var=subscription_id=$($SubscriptionId)" `
"-var=tenant_id=fake" `
"-var=location=$($AzureLocation)" `
"-var=managed_image_name=$($ManagedImageName)" `
"-var=managed_image_resource_group_name=$($ResourceGroupName)" `
"-var=install_password=$($InstallPassword)" `
"-var=allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
"-var=azure_tags=$($TagsJson)" `
$TemplatePath
if ($LastExitCode -ne 0) {
throw "Packer template validation failed."
}
try {
# Login to Azure subscription
if ([string]::IsNullOrEmpty($AzureClientId)) {
Write-Verbose "No AzureClientId was provided, will use interactive login."
az login --output none
}
else {
Write-Verbose "AzureClientId was provided, will use service principal login."
az login --service-principal --username $AzureClientId --password=$AzureClientSecret --tenant $AzureTenantId --output none
}
az account set --subscription $SubscriptionId
if ($LastExitCode -ne 0) {
throw "Failed to login to Azure subscription '$SubscriptionId'."
}
# Check resource group
$ResourceGroupExists = [System.Convert]::ToBoolean((az group exists --name $ResourceGroupName));
if ($ResourceGroupExists) {
Write-Verbose "Resource group '$ResourceGroupName' already exists."
}
# Remove resource group if it exists and we are not reusing it
if ($ResourceGroupExists -and -not $ReuseResourceGroup) {
if ($Force) {
# Delete and recreate the resource group
Write-Host "Deleting resource group '$ResourceGroupName'..."
az group delete --name $ResourceGroupName --yes --output none
if ($LastExitCode -ne 0) {
throw "Failed to delete resource group '$ResourceGroupName'."
}
Write-Host "Resource group '$ResourceGroupName' was deleted."
$ResourceGroupExists = $false
}
else {
# are we running in a non-interactive session?
# https://stackoverflow.com/questions/9738535/powershell-test-for-noninteractive-mode
if ([System.Console]::IsOutputRedirected -or ![Environment]::UserInteractive -or !!([Environment]::GetCommandLineArgs() | Where-Object { $_ -ilike '-noni*' })) {
throw "Non-interactive mode, resource group '$ResourceGroupName' already exists, either specify -Force to delete it, or -ReuseResourceGroup to reuse."
}
else {
# Resource group already exists, ask the user what to do
$title = "Resource group '$ResourceGroupName' already exists"
$message = "Do you want to delete the resource group and all resources in it?"
$options = @(
[System.Management.Automation.Host.ChoiceDescription]::new("&Yes", "Delete the resource group and all resources in it."),
[System.Management.Automation.Host.ChoiceDescription]::new("&No", "Keep the resource group and continue."),
[System.Management.Automation.Host.ChoiceDescription]::new("&Abort", "Abort execution.")
)
$result = $Host.UI.PromptForChoice($title, $message, $options, 0)
}
switch ($result) {
0 {
# Delete and recreate the resource group
Write-Host "Deleting resource group '$ResourceGroupName'..."
az group delete --name $ResourceGroupName --yes
if ($LastExitCode -ne 0) {
throw "Failed to delete resource group '$ResourceGroupName'."
}
Write-Host "Resource group '$ResourceGroupName' was deleted."
$ResourceGroupExists = $false
}
1 {
# Keep the resource group and continue
}
2 {
# Stop the current action
Write-Error "User stopped the action."
exit 1
}
}
}
}
# Create resource group
if (-not $ResourceGroupExists) {
Write-Host "Creating resource group '$ResourceGroupName' in location '$AzureLocation'..."
if ($TagsList) {
az group create --name $ResourceGroupName --location $AzureLocation --tags $TagsList --query id
}
else {
az group create --name $ResourceGroupName --location $AzureLocation --query id
}
if ($LastExitCode -ne 0) {
throw "Failed to create resource group '$ResourceGroupName'."
}
}
# Create service principal
if ([string]::IsNullOrEmpty($AzureClientId)) {
Write-Host "Creating service principal for packer..."
$ADCleanupRequired = $true
$ServicePrincipalName = "packer-" + [System.GUID]::NewGuid().ToString().ToUpper()
$ServicePrincipal = az ad sp create-for-rbac --name $ServicePrincipalName --role Contributor --scopes /subscriptions/$SubscriptionId --only-show-errors | ConvertFrom-Json
if ($LastExitCode -ne 0) {
throw "Failed to create service principal '$ServicePrincipalName'."
}
$ServicePrincipalAppId = $ServicePrincipal.appId
$ServicePrincipalPassword = $ServicePrincipal.password
$TenantId = $ServicePrincipal.tenant
Write-Verbose "Waiting for service principal to propagate..."
Start-Sleep $SecondsToWaitForServicePrincipalSetup
Write-Host "Service principal created with id '$ServicePrincipalAppId'. It will be deleted after the build."
}
else {
$ServicePrincipalAppId = $AzureClientId
$ServicePrincipalPassword = $AzureClientSecret
$TenantId = $AzureTenantId
}
Write-Debug "Service principal app id: $ServicePrincipalAppId."
Write-Debug "Tenant id: $TenantId."
& $PackerBinary build -on-error="$($OnError)" `
-var "client_id=$($ServicePrincipalAppId)" `
-var "client_secret=$($ServicePrincipalPassword)" `
-var "subscription_id=$($SubscriptionId)" `
-var "tenant_id=$($TenantId)" `
-var "location=$($AzureLocation)" `
-var "managed_image_name=$($ManagedImageName)" `
-var "managed_image_resource_group_name=$($ResourceGroupName)" `
-var "install_password=$($InstallPassword)" `
-var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
-var "azure_tags=$($TagsJson)" `
$TemplatePath
if ($LastExitCode -ne 0) {
throw "Failed to build image."
}
} catch {
Write-Error $_
} finally {
Write-Verbose "`nCleaning up..."
# Remove ADServicePrincipal and ADApplication
if ($ADCleanupRequired) {
Write-Host "Removing ADServicePrincipal..."
if (az ad sp show --id $ServicePrincipalAppId --query id) {
az ad sp delete --id $ServicePrincipalAppId
}
Write-Host "Removing ADApplication..."
if (az ad app show --id $ServicePrincipalAppId --query id) {
az ad app delete --id $ServicePrincipalAppId
}
}
Write-Verbose "Cleanup completed."
}
}
@@ -0,0 +1,66 @@
using module ./SoftwareReport.psm1
using module ./SoftwareReport.DifferenceCalculator.psm1
<#
.SYNOPSIS
Calculates the difference between two software reports and saves it to a file.
.PARAMETER PreviousJsonReportPath
Path to the previous software report.
.PARAMETER CurrentJsonReportPath
Path to the current software report.
.PARAMETER OutputFile
Path to the file where the difference will be saved.
.PARAMETER ReleaseBranchName
Name of the release branch to build image docs URL.
.PARAMETER ReadmePath
Path to the README file in repository to build image docs URL.
#>
Param (
[Parameter(Mandatory=$true)]
[string] $PreviousJsonReportPath,
[Parameter(Mandatory=$true)]
[string] $CurrentJsonReportPath,
[Parameter(Mandatory=$true)]
[string] $OutputFile,
[Parameter(Mandatory=$false)]
[string] $ReleaseBranchName,
[Parameter(Mandatory=$false)]
[string] $ReadmePath
)
$ErrorActionPreference = "Stop"
$global:ErrorView = "NormalView"
function Read-SoftwareReport {
Param (
[Parameter(Mandatory=$true)]
[string] $JsonReportPath
)
if (-not (Test-Path $JsonReportPath)) {
throw "File '$JsonReportPath' does not exist"
}
$jsonReport = Get-Content -Path $JsonReportPath -Raw
$report = [SoftwareReport]::FromJson($jsonReport)
return $report
}
$previousReport = Read-SoftwareReport -JsonReportPath $PreviousJsonReportPath
$currentReport = Read-SoftwareReport -JsonReportPath $CurrentJsonReportPath
$comparer = [SoftwareReportDifferenceCalculator]::new($previousReport, $currentReport)
$comparer.CompareReports()
$diff = $comparer.GetMarkdownReport()
if ($ReleaseBranchName -and $ReadmePath) {
# https://github.com/actions/runner-images/blob/releases/macOS-12/20221215/images/macos/macos-12-Readme.md
$ImageDocsUrl = "https://github.com/actions/runner-images/blob/${ReleaseBranchName}/${ReadmePath}"
$diff += "`n`n`nFor comprehensive list of software installed on this image please click [here]($ImageDocsUrl)."
}
$parentDirectory = Split-Path $OutputFile -Parent
if (-not (Test-Path $parentDirectory)) { New-Item -Path $parentDirectory -ItemType Directory | Out-Null }
$diff | Out-File -Path $OutputFile -Encoding utf8NoBOM
@@ -0,0 +1,56 @@
############################
### Abstract base nodes ####
############################
# Abstract base class for all nodes
class BaseNode {
[Boolean] ShouldBeIncludedToDiff() {
return $false
}
[String] ToMarkdown() {
return $this.ToMarkdown(1)
}
[String] ToMarkdown([Int32] $Level) {
throw "Abstract method 'ToMarkdown(level)' is not implemented for '$($this.GetType().Name)'"
}
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
throw "Abstract method 'IsSimilarTo' is not implemented for '$($this.GetType().Name)'"
}
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
throw "Abstract method 'IsIdenticalTo' is not implemented for '$($this.GetType().Name)'"
}
}
# Abstract base class for all nodes that describe a tool and should be rendered inside diff table
class BaseToolNode: BaseNode {
[ValidateNotNullOrEmpty()]
[String] $ToolName
BaseToolNode([String] $ToolName) {
$this.ToolName = $ToolName
}
[Boolean] ShouldBeIncludedToDiff() {
return $true
}
[String] GetValue() {
throw "Abstract method 'GetValue' is not implemented for '$($this.GetType().Name)'"
}
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
if ($this.GetType() -ne $OtherNode.GetType()) {
return $false
}
return $this.ToolName -eq $OtherNode.ToolName
}
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
return $this.IsSimilarTo($OtherNode) -and ($this.GetValue() -eq $OtherNode.GetValue())
}
}
@@ -0,0 +1,136 @@
using module ./SoftwareReport.psm1
using module ./SoftwareReport.BaseNodes.psm1
using module ./SoftwareReport.Nodes.psm1
using module ./SoftwareReport.DifferenceRender.psm1
class SoftwareReportDifferenceCalculator {
[ValidateNotNullOrEmpty()]
hidden [SoftwareReport] $PreviousReport
[ValidateNotNullOrEmpty()]
hidden [SoftwareReport] $CurrentReport
hidden [Collections.Generic.List[ReportDifferenceItem]] $AddedItems
hidden [Collections.Generic.List[ReportDifferenceItem]] $ChangedItems
hidden [Collections.Generic.List[ReportDifferenceItem]] $DeletedItems
SoftwareReportDifferenceCalculator([SoftwareReport] $PreviousReport, [SoftwareReport] $CurrentReport) {
$this.PreviousReport = $PreviousReport
$this.CurrentReport = $CurrentReport
}
[void] CompareReports() {
$this.AddedItems = @()
$this.ChangedItems = @()
$this.DeletedItems = @()
$this.CompareInternal($this.PreviousReport.Root, $this.CurrentReport.Root, @())
}
[String] GetMarkdownReport() {
$reporter = [SoftwareReportDifferenceRender]::new()
$report = $reporter.GenerateMarkdownReport($this.CurrentReport, $this.PreviousReport, $this.AddedItems, $this.ChangedItems, $this.DeletedItems)
return $report
}
hidden [void] CompareInternal([HeaderNode] $previousReportPointer, [HeaderNode] $currentReportPointer, [String[]] $Headers) {
$currentReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
$currentReportNode = $_
$sameNodeInPreviousReport = $previousReportPointer ? $previousReportPointer.FindSimilarChildNode($currentReportNode) : $null
if ($currentReportNode -is [HeaderNode]) {
# Compare HeaderNode recursively
$this.CompareInternal($sameNodeInPreviousReport, $currentReportNode, $Headers + $currentReportNode.Title)
} else {
if ($sameNodeInPreviousReport -and ($currentReportNode.IsIdenticalTo($sameNodeInPreviousReport))) {
# Nodes are identical, nothing changed, just ignore it
} elseif ($sameNodeInPreviousReport) {
# Nodes are equal but not identical, something was changed
if ($currentReportNode -is [TableNode]) {
$this.CompareSimilarTableNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
} elseif ($currentReportNode -is [ToolVersionsListNode]) {
$this.CompareSimilarToolVersionsListNodes($sameNodeInPreviousReport, $currentReportNode, $Headers)
} else {
$this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers))
}
} else {
# Node was not found in previous report, new node was added
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $currentReportNode, $Headers))
}
}
}
# Detecting nodes that were removed
$previousReportPointer.Children ?? @() | Where-Object { $_.ShouldBeIncludedToDiff() -and $this.FilterExcludedNodes($_) } | ForEach-Object {
$previousReportNode = $_
$sameNodeInCurrentReport = $currentReportPointer ? $currentReportPointer.FindSimilarChildNode($previousReportNode) : $null
if (-not $sameNodeInCurrentReport) {
if ($previousReportNode -is [HeaderNode]) {
# Compare removed HeaderNode recursively
$this.CompareInternal($previousReportNode, $null, $Headers + $previousReportNode.Title)
} else {
# Node was not found in current report, node was removed
$this.DeletedItems.Add([ReportDifferenceItem]::new($previousReportNode, $null, $Headers))
}
}
}
}
hidden [void] CompareSimilarTableNodes([TableNode] $PreviousReportNode, [TableNode] $CurrentReportNode, [String[]] $Headers) {
$addedRows = $CurrentReportNode.Rows | Where-Object { $_ -notin $PreviousReportNode.Rows }
$deletedRows = $PreviousReportNode.Rows | Where-Object { $_ -notin $CurrentReportNode.Rows }
if (($addedRows.Count -eq 0) -and ($deletedRows.Count -eq 0)) {
# Unexpected state: TableNodes are identical
return
}
if ($PreviousReportNode.Headers -ne $CurrentReportNode.Headers) {
# If headers are changed and rows are changed at the same time, we should track it as removing table and adding new one
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $null, $Headers))
$this.AddedItems.Add([ReportDifferenceItem]::new($null, $CurrentReportNode, $Headers))
} elseif (($addedRows.Count -gt 0) -and ($deletedRows.Count -eq 0)) {
# If new rows were added and no rows were deleted, then it is AddedItem
$this.AddedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
} elseif (($deletedRows.Count -gt 0) -and ($addedRows.Count -eq 0)) {
# If no rows were added and some rows were deleted, then it is DeletedItem
$this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
} else {
# If some rows were added and some rows were removed, then it is UpdatedItem
$this.ChangedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers))
}
}
hidden [void] CompareSimilarToolVersionsListNodes([ToolVersionsListNode] $PreviousReportNode, [ToolVersionsListNode] $CurrentReportNode, [String[]] $Headers) {
$previousReportMajorVersions = $PreviousReportNode.Versions | ForEach-Object { $PreviousReportNode.ExtractMajorVersion($_) }
$currentReportMajorVersion = $CurrentReportNode.Versions | ForEach-Object { $CurrentReportNode.ExtractMajorVersion($_) }
$addedVersions = $CurrentReportNode.Versions | Where-Object { $CurrentReportNode.ExtractMajorVersion($_) -notin $previousReportMajorVersions }
$deletedVersions = $PreviousReportNode.Versions | Where-Object { $PreviousReportNode.ExtractMajorVersion($_) -notin $currentReportMajorVersion }
$changedPreviousVersions = $PreviousReportNode.Versions | Where-Object { ($PreviousReportNode.ExtractMajorVersion($_) -in $currentReportMajorVersion) -and ($_ -notin $CurrentReportNode.Versions) }
$changedCurrentVersions = $CurrentReportNode.Versions | Where-Object { ($CurrentReportNode.ExtractMajorVersion($_) -in $previousReportMajorVersions) -and ($_ -notin $PreviousReportNode.Versions) }
if ($addedVersions.Count -gt 0) {
$this.AddedItems.Add([ReportDifferenceItem]::new($null, [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $addedVersions, $CurrentReportNode.MajorVersionRegex, "List"), $Headers))
}
if ($deletedVersions.Count -gt 0) {
$this.DeletedItems.Add([ReportDifferenceItem]::new([ToolVersionsListNode]::new($PreviousReportNode.ToolName, $deletedVersions, $PreviousReportNode.MajorVersionRegex, "List"), $null, $Headers))
}
$previousChangedNode = ($changedPreviousVersions.Count -gt 0) ? [ToolVersionsListNode]::new($PreviousReportNode.ToolName, $changedPreviousVersions, $PreviousReportNode.MajorVersionRegex, "List") : $null
$currentChangedNode = ($changedCurrentVersions.Count -gt 0) ? [ToolVersionsListNode]::new($CurrentReportNode.ToolName, $changedCurrentVersions, $CurrentReportNode.MajorVersionRegex, "List") : $null
if ($previousChangedNode -and $currentChangedNode) {
$this.ChangedItems.Add([ReportDifferenceItem]::new($previousChangedNode, $currentChangedNode, $Headers))
}
}
hidden [Boolean] FilterExcludedNodes([BaseNode] $Node) {
# We shouldn't show "Image Version" diff because it is already shown in report header
if (($Node -is [ToolVersionNode]) -and ($Node.ToolName -eq "Image Version:")) {
return $false
}
return $true
}
}
@@ -0,0 +1,225 @@
using module ./SoftwareReport.psm1
using module ./SoftwareReport.BaseNodes.psm1
using module ./SoftwareReport.Nodes.psm1
class SoftwareReportDifferenceRender {
[String] GenerateMarkdownReport([SoftwareReport] $CurrentReport, [SoftwareReport] $PreviousReport, [ReportDifferenceItem[]] $AddedItems, [ReportDifferenceItem[]] $ChangedItems, [ReportDifferenceItem[]] $DeletedItems) {
$sb = [System.Text.StringBuilder]::new()
$rootNode = $CurrentReport.Root
$imageVersion = $CurrentReport.GetImageVersion()
$previousImageVersion = $PreviousReport.GetImageVersion()
#############################
### Render report header ####
#############################
$sb.AppendLine("# :desktop_computer: Actions Runner Image: $($rootNode.Title)")
# ToolVersionNodes on root level contains main image description so just copy-paste them to final report
$rootNode.Children | Where-Object { $_ -is [ToolVersionNode] } | ForEach-Object {
$sb.AppendLine($_.ToMarkdown())
}
$sb.AppendLine()
$sb.AppendLine("## :mega: What's changed?").AppendLine()
###########################
### Render added items ####
###########################
[ReportDifferenceItem[]] $addedItemsBaseTools = $AddedItems | Where-Object { $_.IsBaseToolNode() }
[ReportDifferenceItem[]] $addedItemsTables = $AddedItems | Where-Object { $_.IsTableNode() }
if ($addedItemsBaseTools.Count + $addedItemsTables.Count -gt 0) {
$sb.AppendLine("### Added :heavy_plus_sign:").AppendLine()
}
if ($addedItemsBaseTools.Count -gt 0) {
$tableItems = $addedItemsBaseTools | ForEach-Object {
[PSCustomObject]@{
"Category" = $this.RenderCategory($_.Headers, $true);
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
}
}
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
}
if ($addedItemsTables.Count -gt 0) {
$addedItemsTables | ForEach-Object {
$sb.AppendLine($this.RenderTableNodesDiff($_))
}
}
#############################
### Render deleted items ####
#############################
[ReportDifferenceItem[]] $deletedItemsBaseTools = $DeletedItems | Where-Object { $_.IsBaseToolNode() }
[ReportDifferenceItem[]] $deletedItemsTables = $DeletedItems | Where-Object { $_.IsTableNode() }
if ($deletedItemsBaseTools.Count + $deletedItemsTables.Count -gt 0) {
$sb.AppendLine("### Deleted :heavy_minus_sign:").AppendLine()
}
if ($deletedItemsBaseTools.Count -gt 0) {
$tableItems = $deletedItemsBaseTools | ForEach-Object {
[PSCustomObject]@{
"Category" = $this.RenderCategory($_.Headers, $true);
"Tool name" = $this.RenderToolName($_.PreviousReportNode.ToolName);
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
}
}
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
}
if ($deletedItemsTables.Count -gt 0) {
$deletedItemsTables | ForEach-Object {
$sb.AppendLine($this.RenderTableNodesDiff($_))
}
}
#############################
### Render updated items ####
#############################
[ReportDifferenceItem[]] $changedItemsBaseTools = $ChangedItems | Where-Object { $_.IsBaseToolNode() }
[ReportDifferenceItem[]] $changedItemsTables = $ChangedItems | Where-Object { $_.IsTableNode() }
if ($changedItemsBaseTools.Count + $changedItemsTables.Count -gt 0) {
$sb.AppendLine("### Updated").AppendLine()
}
if ($changedItemsBaseTools.Count -gt 0) {
$tableItems = $changedItemsBaseTools | ForEach-Object {
[PSCustomObject]@{
"Category" = $this.RenderCategory($_.Headers, $true);
"Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName);
"Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue();
"Current ($imageVersion)" = $_.CurrentReportNode.GetValue();
}
}
$sb.AppendLine($this.RenderHtmlTable($tableItems, "Category"))
}
if ($changedItemsTables.Count -gt 0) {
$changedItemsTables | ForEach-Object {
$sb.AppendLine($this.RenderTableNodesDiff($_))
}
}
return $sb.ToString()
}
[String] RenderHtmlTable([PSCustomObject[]] $Table, [String] $RowSpanColumnName) {
$headers = $Table[0].PSObject.Properties.Name
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine("<table>")
$sb.AppendLine(" <thead>")
$headers | ForEach-Object {
$sb.AppendLine(" <th>$_</th>")
}
$sb.AppendLine(" </thead>")
$sb.AppendLine(" <tbody>")
$tableRowSpans = $this.CalculateHtmlTableRowSpan($Table, $RowSpanColumnName)
for ($rowIndex = 0; $rowIndex -lt $Table.Count; $rowIndex++) {
$row = $Table[$rowIndex]
$sb.AppendLine(" <tr>")
$headers | ForEach-Object {
if ($_ -eq $RowSpanColumnName) {
if ($tableRowSpans[$rowIndex] -gt 0) {
$sb.AppendLine(" <td rowspan=`"$($tableRowSpans[$rowIndex])`">$($row.$_)</td>")
} else {
# Skip rendering this cell at all
}
} else {
$sb.AppendLine(" <td>$($row.$_)</td>")
}
}
$sb.AppendLine(" </tr>")
}
$sb.AppendLine(" </tbody>")
$sb.AppendLine("</table>")
return $sb.ToString()
}
[int[]] CalculateHtmlTableRowSpan([PSCustomObject[]] $Table, [String] $keyColumn) {
$result = @(0) * $Table.Count
for ($rowIndex = $Table.Count - 1; $rowIndex -ge 0; $rowIndex--) {
if (($rowIndex -lt ($Table.Count - 1)) -and ($Table[$rowIndex].$keyColumn -eq $Table[$rowIndex + 1].$keyColumn)) {
# If the current row is the same as the next row
# Then rowspan of current row should be equal to rowspan of the next row + 1
# And rowspan of the next row should be 0 because it is already included in the rowspan of the current row
$result[$rowIndex] = $result[$rowIndex + 1] + 1
$result[$rowIndex + 1] = 0
} else {
$result[$rowIndex] = 1
}
}
return $result
}
[String] RenderTableNodesDiff([ReportDifferenceItem] $DiffItem) {
# Use the simplest approach for now: first, print all removed lines. Then print added lines
# It will work well for most cases like changing existing rows, adding new rows and removing rows
# But can produce not so pretty results for cases when some rows are changed and some rows are added at the same time
# Let's see how it works in practice and improve it later if needed
[String] $tableHeaders = ($DiffItem.CurrentReportNode ?? $DiffItem.PreviousReportNode).Headers
[Collections.Generic.List[String]] $tableRows = @()
$DiffItem.PreviousReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.CurrentReportNode.Rows } | ForEach-Object {
$tableRows.Add($this.StrikeTableRow($_))
}
$DiffItem.CurrentReportNode.Rows ?? @() | Where-Object { $_ -notin $DiffItem.PreviousReportNode.Rows } | ForEach-Object {
$tableRows.Add($_)
}
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $false))")
$sb.AppendLine([TableNode]::new($tableHeaders, $tableRows).ToMarkdown())
return $sb.ToString()
}
[String] RenderCategory([String[]] $Headers, [Boolean] $AddLineSeparator) {
# Always skip the first header because it is "Installed Software"
[String[]] $takeHeaders = $Headers | Select-Object -Skip 1
if ($takeHeaders.Count -eq 0) {
return ""
}
$lineSeparator = $AddLineSeparator ? "<br>": ""
return [String]::Join(" >$lineSeparator ", $takeHeaders)
}
[String] RenderToolName([String] $ToolName) {
return $ToolName.TrimEnd(":")
}
[String] StrikeTableRow([String] $Row) {
# Convert "a|b|c" to "~~a~~|~~b~~|~~c~~
$cells = $Row.Split("|")
$strikedCells = $cells | ForEach-Object { "~~$($_)~~"}
return [String]::Join("|", $strikedCells)
}
}
# Temporary structure to store the single difference between two reports
class ReportDifferenceItem {
[BaseNode] $PreviousReportNode
[BaseNode] $CurrentReportNode
[String[]] $Headers
ReportDifferenceItem([BaseNode] $PreviousReportNode, [BaseNode] $CurrentReportNode, [String[]] $Headers) {
$this.PreviousReportNode = $PreviousReportNode
$this.CurrentReportNode = $CurrentReportNode
$this.Headers = $Headers
}
[Boolean] IsBaseToolNode() {
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
return $node -is [BaseToolNode]
}
[Boolean] IsTableNode() {
$node = $this.CurrentReportNode ?? $this.PreviousReportNode
return $node -is [TableNode]
}
}
@@ -0,0 +1,439 @@
using module ./SoftwareReport.BaseNodes.psm1
#########################################
### Nodes to describe image software ####
#########################################
# NodesFactory is used to simplify parsing different types of notes
# Every node has own logic of parsing and this method just invokes "FromJsonObject" of correct node type
class NodesFactory {
static [BaseNode] ParseNodeFromObject([object] $JsonObj) {
if ($JsonObj.NodeType -eq [HeaderNode].Name) {
return [HeaderNode]::FromJsonObject($JsonObj)
} elseif ($JsonObj.NodeType -eq [ToolVersionNode].Name) {
return [ToolVersionNode]::FromJsonObject($JsonObj)
} elseif ($JsonObj.NodeType -eq [ToolVersionsListNode].Name) {
return [ToolVersionsListNode]::FromJsonObject($JsonObj)
} elseif ($JsonObj.NodeType -eq [TableNode].Name) {
return [TableNode]::FromJsonObject($JsonObj)
} elseif ($JsonObj.NodeType -eq [NoteNode].Name) {
return [NoteNode]::FromJsonObject($JsonObj)
}
throw "Unknown node type in ParseNodeFromObject '$($JsonObj.NodeType)'"
}
}
class HeaderNode: BaseNode {
[ValidateNotNullOrEmpty()]
[String] $Title
[Collections.Generic.List[BaseNode]] $Children
HeaderNode([String] $Title) {
$this.Title = $Title
$this.Children = @()
}
[Boolean] ShouldBeIncludedToDiff() {
return $true
}
[void] AddNode([BaseNode] $node) {
$similarNode = $this.FindSimilarChildNode($node)
if ($similarNode) {
throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.`nFound node: $($similarNode.ToJsonObject() | ConvertTo-Json)`nNew node: $($node.ToJsonObject() | ConvertTo-Json)"
}
if (-not $this.IsNodeHasMarkdownHeader($node)) {
# If the node doesn't print own header to markdown, we should check that there is no other nodes that print header to markdown before it.
# It is done to avoid unexpected situation like this:
#
# HeaderNode A -> # A
# HeaderNode B -> ## B
# ToolVersionNode C -> - C
# ToolVersionNode D -> - D
#
# In this example, we add 'HeaderNode B" to 'HeaderNode A' and add 'ToolVersionNode C' to 'HeaderNode B'.
# Then we add 'ToolVersionNode D' to 'HeaderNode A'.
# But the result markdown will look like 'ToolVersionNode D' belongs to 'HeaderNode B' instead of 'HeaderNode A'.
$this.Children | Where-Object { $this.IsNodeHasMarkdownHeader($_) } | ForEach-Object {
throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node"
}
}
$this.Children.Add($node)
}
[void] AddNodes([BaseNode[]] $nodes) {
$nodes | ForEach-Object {
$this.AddNode($_)
}
}
[HeaderNode] AddHeader([String] $Title) {
$node = [HeaderNode]::new($Title)
$this.AddNode($node)
return $node
}
[void] AddToolVersion([String] $ToolName, [String] $Version) {
$this.AddNode([ToolVersionNode]::new($ToolName, $Version))
}
[void] AddToolVersionsList([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) {
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "List"))
}
[void] AddToolVersionsListInline([String] $ToolName, [String[]] $Version, [String] $MajorVersionRegex) {
$this.AddNode([ToolVersionsListNode]::new($ToolName, $Version, $MajorVersionRegex, "Inline"))
}
[void] AddTable([PSCustomObject[]] $Table) {
$this.AddNode([TableNode]::FromObjectsArray($Table))
}
[void] AddNote([String] $Content) {
$this.AddNode([NoteNode]::new($Content))
}
[String] ToMarkdown([Int32] $Level) {
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine()
$sb.AppendLine("$("#" * $Level) $($this.Title)")
$this.Children | ForEach-Object {
$sb.AppendLine($_.ToMarkdown($Level + 1))
}
return $sb.ToString().TrimEnd()
}
[PSCustomObject] ToJsonObject() {
return [PSCustomObject]@{
NodeType = $this.GetType().Name
Title = $this.Title
Children = $this.Children | ForEach-Object { $_.ToJsonObject() }
}
}
static [HeaderNode] FromJsonObject([Object] $JsonObj) {
$node = [HeaderNode]::new($JsonObj.Title)
$JsonObj.Children | Where-Object { $_ } | ForEach-Object { $node.AddNode([NodesFactory]::ParseNodeFromObject($_)) }
return $node
}
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
if ($OtherNode.GetType() -ne [HeaderNode]) {
return $false
}
return $this.Title -eq $OtherNode.Title
}
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
return $this.IsSimilarTo($OtherNode)
}
[BaseNode] FindSimilarChildNode([BaseNode] $Find) {
foreach ($childNode in $this.Children) {
if ($childNode.IsSimilarTo($Find)) {
return $childNode
}
}
return $null
}
hidden [Boolean] IsNodeHasMarkdownHeader([BaseNode] $node) {
if ($node -is [HeaderNode]) {
return $true
}
if (($node -is [ToolVersionsListNode]) -and ($node.ListType -eq "List")) {
return $true
}
return $false
}
}
class ToolVersionNode: BaseToolNode {
[ValidateNotNullOrEmpty()]
[String] $Version
ToolVersionNode([String] $ToolName, [String] $Version): base($ToolName) {
if ([String]::IsNullOrEmpty($Version)) {
throw "ToolVersionNode '$($this.ToolName)' has empty version"
}
$this.Version = $Version
}
[String] ToMarkdown([Int32] $Level) {
return "- $($this.ToolName) $($this.Version)"
}
[String] GetValue() {
return $this.Version
}
[PSCustomObject] ToJsonObject() {
return [PSCustomObject]@{
NodeType = $this.GetType().Name
ToolName = $this.ToolName
Version = $this.Version
}
}
static [BaseNode] FromJsonObject([Object] $JsonObj) {
return [ToolVersionNode]::new($JsonObj.ToolName, $JsonObj.Version)
}
}
class ToolVersionsListNode: BaseToolNode {
[ValidateNotNullOrEmpty()]
[String[]] $Versions
[Regex] $MajorVersionRegex
[ValidateSet("List", "Inline")]
[String] $ListType
ToolVersionsListNode([String] $ToolName, [String[]] $Versions, [String] $MajorVersionRegex, [String] $ListType): base($ToolName) {
$this.Versions = $Versions
if ([String]::IsNullOrEmpty($Versions)) {
throw "ToolVersionsListNode '$($this.ToolName)' has empty versions list"
}
$this.MajorVersionRegex = [Regex]::new($MajorVersionRegex)
$this.ListType = $ListType
$this.ValidateMajorVersionRegex()
}
[String] ToMarkdown([Int32] $Level) {
if ($this.ListType -eq "Inline") {
return "- $($this.ToolName): $($this.Versions -join ', ')"
}
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine()
$sb.AppendLine("$("#" * $Level) $($this.ToolName)")
$this.Versions | ForEach-Object {
$sb.AppendLine("- $_")
}
return $sb.ToString().TrimEnd()
}
[String] GetValue() {
return $this.Versions -join ', '
}
[String] ExtractMajorVersion([String] $Version) {
$match = $this.MajorVersionRegex.Match($Version)
if (($match.Success -ne $true) -or [String]::IsNullOrEmpty($match.Groups[0].Value)) {
throw "Version '$Version' doesn't match regex '$($this.PrimaryVersionRegex)'"
}
return $match.Groups[0].Value
}
[PSCustomObject] ToJsonObject() {
return [PSCustomObject]@{
NodeType = $this.GetType().Name
ToolName = $this.ToolName
Versions = $this.Versions
MajorVersionRegex = $this.MajorVersionRegex.ToString()
ListType = $this.ListType
}
}
static [ToolVersionsListNode] FromJsonObject([Object] $JsonObj) {
return [ToolVersionsListNode]::new($JsonObj.ToolName, $JsonObj.Versions, $JsonObj.MajorVersionRegex, $JsonObj.ListType)
}
hidden [void] ValidateMajorVersionRegex() {
$this.Versions | Group-Object { $this.ExtractMajorVersion($_) } | ForEach-Object {
if ($_.Count -gt 1) {
throw "Multiple versions from list '$($this.GetValue())' return the same result from regex '$($this.MajorVersionRegex)': $($_.Name)"
}
}
}
}
class TableNode: BaseNode {
# It is easier to store the table as rendered lines because it will simplify finding differences in rows later
[ValidateNotNullOrEmpty()]
[String] $Headers
[ValidateNotNullOrEmpty()]
[String[]] $Rows
TableNode([String] $Headers, [String[]] $Rows) {
$this.Headers = $Headers
$this.Rows = $Rows
$columnsCount = $this.Headers.Split("|").Count
$this.Rows | ForEach-Object {
if ($_.Split("|").Count -ne $columnsCount) {
throw "Table has different number of columns in different rows"
}
}
}
[Boolean] ShouldBeIncludedToDiff() {
return $true
}
[String] ToMarkdown([Int32] $Level) {
$maxColumnWidths = $this.CalculateColumnsWidth()
$columnsCount = $maxColumnWidths.Count
$delimiterLine = [String]::Join("|", @("-") * $columnsCount)
$sb = [System.Text.StringBuilder]::new()
@($this.Headers) + @($delimiterLine) + $this.Rows | ForEach-Object {
$sb.Append("|")
$row = $_.Split("|")
for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) {
$padSymbol = $row[$colIndex] -eq "-" ? "-" : " "
$cellContent = $row[$colIndex].PadRight($maxColumnWidths[$colIndex], $padSymbol)
$sb.Append(" $($cellContent) |")
}
$sb.AppendLine()
}
return $sb.ToString().TrimEnd()
}
hidden [Int32[]] CalculateColumnsWidth() {
$maxColumnWidths = $this.Headers.Split("|") | ForEach-Object { $_.Length }
$columnsCount = $maxColumnWidths.Count
$this.Rows | ForEach-Object {
$columnWidths = $_.Split("|") | ForEach-Object { $_.Length }
for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) {
$maxColumnWidths[$colIndex] = [Math]::Max($maxColumnWidths[$colIndex], $columnWidths[$colIndex])
}
}
return $maxColumnWidths
}
[PSCustomObject] ToJsonObject() {
return [PSCustomObject]@{
NodeType = $this.GetType().Name
Headers = $this.Headers
Rows = $this.Rows
}
}
static [TableNode] FromJsonObject([Object] $JsonObj) {
return [TableNode]::new($JsonObj.Headers, $JsonObj.Rows)
}
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
if ($OtherNode.GetType() -ne [TableNode]) {
return $false
}
# We don't support having multiple TableNode instances on the same header level so such check is fine
return $true
}
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
if (-not $this.IsSimilarTo($OtherNode)) {
return $false
}
# We don't compare $this.Headers intentionally
# It is fine to ignore the tables where headers are changed but rows are not changed
if ($this.Rows.Count -ne $OtherNode.Rows.Count) {
return $false
}
for ($rowIndex = 0; $rowIndex -lt $this.Rows.Count; $rowIndex++) {
if ($this.Rows[$rowIndex] -ne $OtherNode.Rows[$rowIndex]) {
return $false
}
}
return $true
}
static [TableNode] FromObjectsArray([PSCustomObject[]] $Table) {
if ($Table.Count -eq 0) {
throw "Failed to create TableNode from empty objects array"
}
[String] $tableHeaders = [TableNode]::ArrayToTableRow($Table[0].PSObject.Properties.Name)
[Collections.Generic.List[String]] $tableRows = @()
$Table | ForEach-Object {
$rowHeaders = [TableNode]::ArrayToTableRow($_.PSObject.Properties.Name)
if (($rowHeaders -ne $tableHeaders)) {
throw "Failed to create TableNode from objects array because objects have different properties"
}
$tableRows.Add([TableNode]::ArrayToTableRow($_.PSObject.Properties.Value))
}
return [TableNode]::new($tableHeaders, $tableRows)
}
hidden static [String] ArrayToTableRow([String[]] $Values) {
if ($Values.Count -eq 0) {
throw "Failed to create TableNode because some objects are empty"
}
$Values | ForEach-Object {
if ($_.Contains("|")) {
throw "Failed to create TableNode because some cells '$_' contains forbidden symbol '|'"
}
}
return [String]::Join("|", $Values)
}
}
class NoteNode: BaseNode {
[ValidateNotNullOrEmpty()]
[String] $Content
NoteNode([String] $Content) {
$this.Content = $Content
}
[String] ToMarkdown([Int32] $Level) {
return @(
'```',
$this.Content,
'```'
) -join "`n"
}
[PSCustomObject] ToJsonObject() {
return [PSCustomObject]@{
NodeType = $this.GetType().Name
Content = $this.Content
}
}
static [NoteNode] FromJsonObject([Object] $JsonObj) {
return [NoteNode]::new($JsonObj.Content)
}
[Boolean] IsSimilarTo([BaseNode] $OtherNode) {
if ($OtherNode.GetType() -ne [NoteNode]) {
return $false
}
return $this.Content -eq $OtherNode.Content
}
[Boolean] IsIdenticalTo([BaseNode] $OtherNode) {
return $this.IsSimilarTo($OtherNode)
}
}
@@ -0,0 +1,34 @@
using module ./SoftwareReport.BaseNodes.psm1
using module ./SoftwareReport.Nodes.psm1
class SoftwareReport {
[ValidateNotNullOrEmpty()]
[HeaderNode] $Root
SoftwareReport([String] $Title) {
$this.Root = [HeaderNode]::new($Title)
}
SoftwareReport([HeaderNode] $Root) {
$this.Root = $Root
}
[String] ToJson() {
return $this.Root.ToJsonObject() | ConvertTo-Json -Depth 10
}
static [SoftwareReport] FromJson([String] $JsonString) {
$jsonObj = $JsonString | ConvertFrom-Json
$rootNode = [NodesFactory]::ParseNodeFromObject($jsonObj)
return [SoftwareReport]::new($rootNode)
}
[String] ToMarkdown() {
return $this.Root.ToMarkdown().Trim()
}
[String] GetImageVersion() {
$imageVersionNode = $this.Root.Children ?? @() | Where-Object { ($_ -is [ToolVersionNode]) -and ($_.ToolName -eq "Image Version:") } | Select-Object -First 1
return $imageVersionNode.Version ?? "Unknown version"
}
}
@@ -0,0 +1,558 @@
using module ../SoftwareReport.psm1
using module ../SoftwareReport.DifferenceCalculator.psm1
Describe "Comparer.E2E" {
It "Some tools are updated" {
# Previous report
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
$prevTools.AddToolVersion("ToolWillBeUpdated1", "1.0.0")
$prevTools.AddToolVersion("ToolWillBeUpdated2", "3.0.1")
$prevTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.0.0", "15.5.1"), "^\d+")
# Next report
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
$nextTools.AddToolVersion("ToolWillBeUpdated1", "2.5.0")
$nextTools.AddToolVersion("ToolWillBeUpdated2", "3.0.2")
$nextTools.AddToolVersionsList("ToolWillBeUpdated3", @("14.2.0", "15.5.1"), "^\d+")
# Compare reports
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
$comparer.CompareReports()
$comparer.GetMarkdownReport() | Should -BeExactly @'
# :desktop_computer: Actions Runner Image: macOS 11
- OS Version: macOS 11.7.1 (20G817)
- Image Version: 20220922.1
## :mega: What's changed?
### Updated
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Previous (20220918.1)</th>
<th>Current (20220922.1)</th>
</thead>
<tbody>
<tr>
<td rowspan="3">Tools</td>
<td>ToolWillBeUpdated1</td>
<td>1.0.0</td>
<td>2.5.0</td>
</tr>
<tr>
<td>ToolWillBeUpdated2</td>
<td>3.0.1</td>
<td>3.0.2</td>
</tr>
<tr>
<td>ToolWillBeUpdated3</td>
<td>14.0.0</td>
<td>14.2.0</td>
</tr>
</tbody>
</table>
'@
}
It "Some tools are updated, added and removed" {
# Previous report
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
$prevLanguagesAndRuntimes = $prevInstalledSoftware.AddHeader("Language and Runtime")
$prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeRemoved", "5.1.16(1)-release")
$prevLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.100", "1.2.200", "1.3.500", "1.4.100", "1.4.200"), "^\d+\.\d+\.\d")
$prevLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0")
$prevLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.1.0")
$prevCachedTools = $prevInstalledSoftware.AddHeader("Cached Tools")
$prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
$prevCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("14.8.0", "15.1.0", "16.4.2"), "^\d+")
$prevSQLSection = $prevInstalledSoftware.AddHeader("Databases")
$prevSQLSection.AddToolVersion("MineSQL", "6.1.0")
$prevSQLSection.AddNote("First Note")
# Next report
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.2 (20G922)")
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0")
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
$nextLanguagesAndRuntimes = $nextInstalledSoftware.AddHeader("Language and Runtime")
$nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeAdded", "16.18.0")
$nextLanguagesAndRuntimes.AddToolVersionsListInline("ToolWithMultipleVersions3", @("1.2.200", "1.3.515", "1.4.100", "1.4.200", "1.5.800"), "^\d+\.\d+\.\d")
$nextLanguagesAndRuntimes.AddToolVersion("ToolWithoutChanges", "5.34.0")
$nextLanguagesAndRuntimes.AddToolVersion("ToolWillBeUpdated", "8.3.0")
$nextCachedTools = $nextInstalledSoftware.AddHeader("Cached Tools")
$nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions1", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
$nextCachedTools.AddToolVersionsList("ToolWithMultipleVersions2", @("15.1.0", "16.4.2", "17.0.1"), "^\d+")
$nextSQLSection = $nextInstalledSoftware.AddHeader("Databases")
$nextSQLSection.AddToolVersion("MineSQL", "6.1.1")
$nextSQLSection.AddNote("Second Note")
# Compare reports
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
$comparer.CompareReports()
$comparer.GetMarkdownReport() | Should -BeExactly @'
# :desktop_computer: Actions Runner Image: macOS 11
- OS Version: macOS 11.7.2 (20G922)
- Image Version: 20220922.0
## :mega: What's changed?
### Added :heavy_plus_sign:
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Current (20220922.0)</th>
</thead>
<tbody>
<tr>
<td rowspan="2">Language and Runtime</td>
<td>ToolWillBeAdded</td>
<td>16.18.0</td>
</tr>
<tr>
<td>ToolWithMultipleVersions3</td>
<td>1.5.800</td>
</tr>
<tr>
<td rowspan="1">Cached Tools</td>
<td>ToolWithMultipleVersions2</td>
<td>17.0.1</td>
</tr>
</tbody>
</table>
### Deleted :heavy_minus_sign:
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Previous (20220918.1)</th>
</thead>
<tbody>
<tr>
<td rowspan="2">Language and Runtime</td>
<td>ToolWithMultipleVersions3</td>
<td>1.2.100</td>
</tr>
<tr>
<td>ToolWillBeRemoved</td>
<td>5.1.16(1)-release</td>
</tr>
<tr>
<td rowspan="1">Cached Tools</td>
<td>ToolWithMultipleVersions2</td>
<td>14.8.0</td>
</tr>
</tbody>
</table>
### Updated
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Previous (20220918.1)</th>
<th>Current (20220922.0)</th>
</thead>
<tbody>
<tr>
<td rowspan="1"></td>
<td>OS Version</td>
<td>macOS 11.7.1 (20G817)</td>
<td>macOS 11.7.2 (20G922)</td>
</tr>
<tr>
<td rowspan="2">Language and Runtime</td>
<td>ToolWithMultipleVersions3</td>
<td>1.3.500</td>
<td>1.3.515</td>
</tr>
<tr>
<td>ToolWillBeUpdated</td>
<td>8.1.0</td>
<td>8.3.0</td>
</tr>
<tr>
<td rowspan="1">Databases</td>
<td>MineSQL</td>
<td>6.1.0</td>
<td>6.1.1</td>
</tr>
</tbody>
</table>
'@
}
It "Header tree changes" {
# Previous report
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
$prevInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0")
$prevInstalledSoftware.AddHeader("HeaderWillBeRemoved").AddHeader("SubheaderWillBeRemoved").AddToolVersion("ToolWillBeRemoved", "1.0.0")
$prevInstalledSoftware.AddHeader("Header1").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0")
# Next report
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.0")
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
$nextInstalledSoftware.AddToolVersion("ToolWithoutChanges", "5.34.0")
$nextInstalledSoftware.AddHeader("HeaderWillBeAdded").AddHeader("SubheaderWillBeAdded").AddToolVersion("ToolWillBeAdded", "5.0.0")
$nextInstalledSoftware.AddHeader("Header2").AddToolVersion("ToolWillBeMovedToAnotherHeader", "3.0.0")
# Compare reports
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
$comparer.CompareReports()
$comparer.GetMarkdownReport() | Should -BeExactly @'
# :desktop_computer: Actions Runner Image: macOS 11
- Image Version: 20220922.0
## :mega: What's changed?
### Added :heavy_plus_sign:
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Current (20220922.0)</th>
</thead>
<tbody>
<tr>
<td rowspan="1">HeaderWillBeAdded ><br> SubheaderWillBeAdded</td>
<td>ToolWillBeAdded</td>
<td>5.0.0</td>
</tr>
<tr>
<td rowspan="1">Header2</td>
<td>ToolWillBeMovedToAnotherHeader</td>
<td>3.0.0</td>
</tr>
</tbody>
</table>
### Deleted :heavy_minus_sign:
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Previous (20220918.1)</th>
</thead>
<tbody>
<tr>
<td rowspan="1">HeaderWillBeRemoved ><br> SubheaderWillBeRemoved</td>
<td>ToolWillBeRemoved</td>
<td>1.0.0</td>
</tr>
<tr>
<td rowspan="1">Header1</td>
<td>ToolWillBeMovedToAnotherHeader</td>
<td>3.0.0</td>
</tr>
</tbody>
</table>
'@
}
It "Tables are added and removed" {
# Previous report
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
$prevInstalledSoftware.AddHeader("HeaderWillExist").AddTable(@(
[PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "Q"; Value = "25"},
[PSCustomObject]@{TableInExistingHeaderWillBeRemoved = "O"; Value = "24"}
))
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
$prevTools.AddHeader("HeaderWillBeRemoved").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "Z"; Value = "30"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "W"; Value = "29"}
))
# Next report
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
$nextInstalledSoftware.AddHeader("HeaderWillExist")
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
$nextTools.AddToolVersion("ToolWillBeAdded", "3.0.1")
$nextTools.AddTable(@(
[PSCustomObject]@{NewTableInExistingHeader = "A"; Value = "1"},
[PSCustomObject]@{NewTableInExistingHeader = "B"; Value = "2"}
))
$nextTools.AddHeader("NewHeaderWithTable").AddTable(@(
[PSCustomObject]@{NewTableInNewHeader = "C"; Value = "3"},
[PSCustomObject]@{NewTableInNewHeader = "D"; Value = "4"}
))
# Compare reports
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
$comparer.CompareReports()
$comparer.GetMarkdownReport() | Should -BeExactly @'
# :desktop_computer: Actions Runner Image: macOS 11
- Image Version: 20220922.1
## :mega: What's changed?
### Added :heavy_plus_sign:
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Current (20220922.1)</th>
</thead>
<tbody>
<tr>
<td rowspan="1">Tools</td>
<td>ToolWillBeAdded</td>
<td>3.0.1</td>
</tr>
</tbody>
</table>
#### Tools
| NewTableInExistingHeader | Value |
| ------------------------ | ----- |
| A | 1 |
| B | 2 |
#### Tools > NewHeaderWithTable
| NewTableInNewHeader | Value |
| ------------------- | ----- |
| C | 3 |
| D | 4 |
### Deleted :heavy_minus_sign:
#### HeaderWillExist
| TableInExistingHeaderWillBeRemoved | Value |
| ---------------------------------- | ------ |
| ~~Q~~ | ~~25~~ |
| ~~O~~ | ~~24~~ |
#### Tools > HeaderWillBeRemoved
| TableWillBeRemovedWithHeader | Value |
| ---------------------------- | ------ |
| ~~Z~~ | ~~30~~ |
| ~~W~~ | ~~29~~ |
'@
}
It "Tables are changed" {
# Previous report
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
$prevTools.AddHeader("TableWithAddedRows").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"}
))
$prevTools.AddHeader("TableWithRemovedRows").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BA"; Value = "32"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"}
))
$prevTools.AddHeader("TableWithUpdatedRow").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "43"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"}
))
$prevTools.AddHeader("TableWithUpdatedRows").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "51"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "52"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"}
))
$prevTools.AddHeader("TableWithComplexChanges").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EA"; Value = "62"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "64"}
[PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"}
))
$prevTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@(
[PSCustomObject]@{TableWithOnlyHeaderChanged = "FA"; Value = "72"},
[PSCustomObject]@{TableWithOnlyHeaderChanged = "FB"; Value = "73"}
))
$prevTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@(
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GA"; Value = "82"},
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GB"; Value = "83"},
[PSCustomObject]@{TableWithHeaderAndRowsChanges = "GC"; Value = "84"}
))
# Next report
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
$nextTools.AddHeader("TableWithAddedRows").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AA"; Value = "10"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AB"; Value = "11"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "AC"; Value = "12"}
))
$nextTools.AddHeader("TableWithRemovedRows").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BB"; Value = "33"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "BC"; Value = "34"}
))
$nextTools.AddHeader("TableWithUpdatedRow").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CA"; Value = "42"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CB"; Value = "500"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "CC"; Value = "44"}
))
$nextTools.AddHeader("TableWithUpdatedRows").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DA"; Value = "50"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DB"; Value = "5100"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DC"; Value = "5200"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "DD"; Value = "53"}
))
$nextTools.AddHeader("TableWithComplexChanges").AddTable(@(
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EB"; Value = "63"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EC"; Value = "640"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "ED"; Value = "65"},
[PSCustomObject]@{TableWillBeRemovedWithHeader = "EE"; Value = "66"}
))
$nextTools.AddHeader("TableWithOnlyHeaderChanged").AddTable(@(
[PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FA"; Value = "72"},
[PSCustomObject]@{TableWithOnlyHeaderChanged2 = "FB"; Value = "73"}
))
$nextTools.AddHeader("TableWithHeaderAndRowsChanges").AddTable(@(
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GA"; Value = "82"},
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GE"; Value = "850"},
[PSCustomObject]@{TableWithHeaderAndRowsChanges2 = "GC"; Value = "840"}
))
# Compare reports
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
$comparer.CompareReports()
$comparer.GetMarkdownReport() | Should -BeExactly @'
# :desktop_computer: Actions Runner Image: macOS 11
- Image Version: 20220922.1
## :mega: What's changed?
### Added :heavy_plus_sign:
#### Tools > TableWithAddedRows
| TableWillBeRemovedWithHeader | Value |
| ---------------------------- | ----- |
| AC | 12 |
#### Tools > TableWithHeaderAndRowsChanges
| TableWithHeaderAndRowsChanges2 | Value |
| ------------------------------ | ----- |
| GA | 82 |
| GE | 850 |
| GC | 840 |
### Deleted :heavy_minus_sign:
#### Tools > TableWithRemovedRows
| TableWillBeRemovedWithHeader | Value |
| ---------------------------- | ------ |
| ~~BA~~ | ~~32~~ |
#### Tools > TableWithHeaderAndRowsChanges
| TableWithHeaderAndRowsChanges | Value |
| ----------------------------- | ------ |
| ~~GA~~ | ~~82~~ |
| ~~GB~~ | ~~83~~ |
| ~~GC~~ | ~~84~~ |
### Updated
#### Tools > TableWithUpdatedRow
| TableWillBeRemovedWithHeader | Value |
| ---------------------------- | ------ |
| ~~CB~~ | ~~43~~ |
| CB | 500 |
#### Tools > TableWithUpdatedRows
| TableWillBeRemovedWithHeader | Value |
| ---------------------------- | ------ |
| ~~DB~~ | ~~51~~ |
| ~~DC~~ | ~~52~~ |
| DB | 5100 |
| DC | 5200 |
#### Tools > TableWithComplexChanges
| TableWillBeRemovedWithHeader | Value |
| ---------------------------- | ------ |
| ~~EA~~ | ~~62~~ |
| ~~EC~~ | ~~64~~ |
| EC | 640 |
| EE | 66 |
'@
}
It "Reports are identical" {
# Previous report
$prevSoftwareReport = [SoftwareReport]::new("macOS 11")
$prevSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
$prevSoftwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$prevInstalledSoftware = $prevSoftwareReport.Root.AddHeader("Installed Software")
$prevTools = $prevInstalledSoftware.AddHeader("Tools")
$prevTools.AddToolVersion("ToolA", "1.0.0")
$prevTools.AddToolVersion("ToolB", "3.0.1")
# Next report
$nextSoftwareReport = [SoftwareReport]::new("macOS 11")
$nextSoftwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7.1 (20G817)")
$nextSoftwareReport.Root.AddToolVersion("Image Version:", "20220922.1")
$nextInstalledSoftware = $nextSoftwareReport.Root.AddHeader("Installed Software")
$nextTools = $nextInstalledSoftware.AddHeader("Tools")
$nextTools.AddToolVersion("ToolA", "1.0.0")
$nextTools.AddToolVersion("ToolB", "3.0.1")
# Compare reports
$comparer = [SoftwareReportDifferenceCalculator]::new($prevSoftwareReport, $nextSoftwareReport)
$comparer.CompareReports()
$comparer.GetMarkdownReport() | Should -BeExactly @'
# :desktop_computer: Actions Runner Image: macOS 11
- OS Version: macOS 11.7.1 (20G817)
- Image Version: 20220922.1
## :mega: What's changed?
'@
}
}
@@ -0,0 +1,603 @@
using module ../SoftwareReport.Nodes.psm1
using module ../SoftwareReport.DifferenceCalculator.psm1
BeforeDiscovery {
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
}
Describe "Comparer.UnitTests" {
Describe "Headers Tree" {
It "Add Node to existing header" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader")
}
It "Add new header with Node" {
$prevReport = [HeaderNode]::new("Version 1")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddHeader("MySubHeader").AddToolVersion("MyTool1", "2.1.3")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader")
}
It "Remove Node from existing header" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader")
}
It "Remove header with Node" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
$nextReport = [HeaderNode]::new("Version 2")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
}
It "Node with minor changes" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.4")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubHeader")
}
It "Node without changes" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
}
It "Node is moved to different header" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddHeader("MySubheader").AddToolVersion("MyTool1", "2.1.3")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddHeader("MySubheader2").AddToolVersion("MyTool1", "2.1.3")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.1.3"
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader2")
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
}
It "Complex structure" {
$prevReport = [HeaderNode]::new("Version 1")
$prevSubHeader = $prevReport.AddHeader("MyHeader").AddHeader("MySubheader")
$prevSubHeader.AddToolVersion("MyTool1", "2.1.3")
$prevSubHeader.AddHeader("MySubSubheader").AddToolVersion("MyTool2", "2.9.1")
$prevReport.AddHeader("MyHeader2")
$prevReport.AddHeader("MyHeader3").AddHeader("MySubheader3").AddToolVersion("MyTool3", "14.2.1")
$nextReport = [HeaderNode]::new("Version 2")
$nextSubHeader = $nextReport.AddHeader("MyHeader").AddHeader("MySubheader")
$nextSubHeader.AddToolVersion("MyTool1", "2.1.4")
$nextSubSubHeader = $nextSubHeader.AddHeader("MySubSubheader")
$nextSubSubHeader.AddToolVersion("MyTool2", "2.9.1")
$nextSubSubHeader.AddToolVersion("MyTool4", "2.7.6")
$nextReport.AddHeader("MyHeader2")
$nextReport.AddHeader("MyHeader3")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 1
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool4"
$comparer.AddedItems[0].CurrentReportNode.Version | Should -Be "2.7.6"
$comparer.AddedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader", "MySubSubheader")
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader", "MySubheader")
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool3"
$comparer.DeletedItems[0].PreviousReportNode.Version | Should -Be "14.2.1"
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
$comparer.DeletedItems[0].Headers | Should -BeArray @("MyHeader3", "MySubheader3")
}
}
Describe "ToolVersionNode" {
It "ToolVersionNode is updated" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.3")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersion("MyTool1", "2.1.4")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Version | Should -Be "2.1.3"
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Version | Should -Be "2.1.4"
$comparer.ChangedItems[0].Headers | Should -BeArray @("MyHeader")
}
}
Describe "ToolVersionsListNode" {
It "Single version is not changed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^.+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
}
It "Single version is changed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.4"), "^\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.1.4")
}
It "Major version is added" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3"), "^\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.1.4")
}
It "Major version is removed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.1.4"), "^\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.1.3")
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
}
It "Major version is changed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.1.4"), "^\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("3.2.0"), "^\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0")
}
It "Major version is added, removed and updated at the same time" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("1.0.0", "2.1.3", "3.1.4", "4.0.2"), "^\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.1.3", "3.2.0", "4.0.2", "5.1.0"), "^\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 1
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("5.1.0")
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("3.1.4")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("3.2.0")
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("1.0.0")
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
}
It "Minor version is added, removed and updated at the same time" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4", "2.8.0"), "^\d+\.\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.5.3", "2.6.2", "2.7.5", "2.8.0", "2.9.2", "2.10.3"), "^\d+\.\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 1
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.9.2", "2.10.3")
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.6.0", "2.7.4")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.ChangedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.ChangedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.6.2", "2.7.5")
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.4.9")
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
}
It "Patch version is added, removed and updated at the same time" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.3.8", "2.4.9", "2.5.3", "2.6.0", "2.7.4"), "^\d+\.\d+\.\d+")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddToolVersionsList("MyTool1", @("2.4.9", "2.5.4", "2.6.0", "2.7.5", "2.8.2"), "^\d+\.\d+\.\d+")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.AddedItems[0].CurrentReportNode.ToolName | Should -Be "MyTool1"
$comparer.AddedItems[0].CurrentReportNode.Versions | Should -BeArray @("2.5.4", "2.7.5", "2.8.2")
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([ToolVersionsListNode])
$comparer.DeletedItems[0].PreviousReportNode.ToolName | Should -Be "MyTool1"
$comparer.DeletedItems[0].PreviousReportNode.Versions | Should -BeArray @("2.3.8", "2.5.3", "2.7.4")
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
}
}
Describe "TableNode" {
It "Rows are added" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
$comparer.AddedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
$comparer.AddedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
$comparer.AddedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
$comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
$comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2")
}
It "Rows are deleted" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2", "D1|D2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("C1|C2", "D1|D2")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
$comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
$comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2", "D1|D2")
$comparer.DeletedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
$comparer.DeletedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
$comparer.DeletedItems[0].CurrentReportNode.Rows | Should -BeArray @("C1|C2", "D1|D2")
}
It "Rows are changed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4")
}
It "Rows are changed and updated at the same time" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4", "C1|C2")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4", "C1|C2")
}
It "Rows are changed and removed at the same time" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2", "C1|C2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B3|B4")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 1
$comparer.DeletedItems | Should -HaveCount 0
$comparer.ChangedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
$comparer.ChangedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
$comparer.ChangedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2")
$comparer.ChangedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
$comparer.ChangedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value"
$comparer.ChangedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B3|B4")
}
It "Rows are not changed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
}
It "Rows are not changed but header is changed" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
}
It "Rows are changed and header is changed at the same time" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value", @("A1|A2", "B1|B2")))
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddHeader("MyHeader").AddNode([TableNode]::new("Name|Value2", @("A1|A2", "B1|B2", "C1|C2")))
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 1
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 1
$comparer.AddedItems[0].PreviousReportNode | Should -BeNullOrEmpty
$comparer.AddedItems[0].CurrentReportNode | Should -BeOfType ([TableNode])
$comparer.AddedItems[0].CurrentReportNode.Headers | Should -Be "Name|Value2"
$comparer.AddedItems[0].CurrentReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2", "C1|C2")
$comparer.DeletedItems[0].PreviousReportNode | Should -BeOfType ([TableNode])
$comparer.DeletedItems[0].PreviousReportNode.Headers | Should -Be "Name|Value"
$comparer.DeletedItems[0].PreviousReportNode.Rows | Should -BeArray @("A1|A2", "B1|B2")
$comparer.DeletedItems[0].CurrentReportNode | Should -BeNullOrEmpty
}
}
Describe "NoteNode" {
It "NoteNode is ignored from report" {
$prevReport = [HeaderNode]::new("Version 1")
$prevReport.AddNote("MyFirstNote")
$prevReport.AddHeader("MyFirstHeader").AddNote("MyFirstSubNote")
$nextReport = [HeaderNode]::new("Version 2")
$nextReport.AddNote("MySecondNote")
$nextReport.AddHeader("MySecondHeader").AddNote("MySecondSubNote")
$comparer = [SoftwareReportDifferenceCalculator]::new($prevReport, $nextReport)
$comparer.CompareReports()
$comparer.AddedItems | Should -HaveCount 0
$comparer.ChangedItems | Should -HaveCount 0
$comparer.DeletedItems | Should -HaveCount 0
}
}
}
@@ -0,0 +1,291 @@
using module ../SoftwareReport.Nodes.psm1
using module ../SoftwareReport.DifferenceRender.psm1
BeforeDiscovery {
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
}
Describe "ComparerReport.UnitTests" {
BeforeAll {
$script:DifferenceRender = [SoftwareReportDifferenceRender]::new()
}
Context "CalculateHtmlTableRowSpan" {
It "Without the equal cells" {
$table = @(
[PSCustomObject]@{ Key = "A"; Value = "1" }
[PSCustomObject]@{ Key = "B"; Value = "2" }
[PSCustomObject]@{ Key = "C"; Value = "3" }
)
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
$actual | Should -BeArray @(1, 1, 1)
}
It "Only equal cells" {
$table = @(
[PSCustomObject]@{ Key = "A"; Value = "D" }
[PSCustomObject]@{ Key = "B"; Value = "D" }
[PSCustomObject]@{ Key = "C"; Value = "D" }
)
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Value")
$actual | Should -BeArray @(3, 0, 0)
}
It "Single row" {
$table = @(
[PSCustomObject]@{ Key = "A"; Value = "1" }
)
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
$actual | Should -BeArray @(1)
}
It "Different cells" {
$table = @(
[PSCustomObject]@{ Key = "A"; Value = "1" }
[PSCustomObject]@{ Key = "B"; Value = "2" }
[PSCustomObject]@{ Key = "B"; Value = "3" }
[PSCustomObject]@{ Key = "C"; Value = "4" }
[PSCustomObject]@{ Key = "C"; Value = "5" }
[PSCustomObject]@{ Key = "C"; Value = "6" }
[PSCustomObject]@{ Key = "D"; Value = "7" }
[PSCustomObject]@{ Key = "E"; Value = "8" }
[PSCustomObject]@{ Key = "E"; Value = "9" }
[PSCustomObject]@{ Key = "F"; Value = "10" }
)
$actual = $DifferenceRender.CalculateHtmlTableRowSpan($table, "Key")
$actual | Should -BeArray @(1, 2, 0, 3, 0, 0, 1, 2, 0, 1)
}
}
Context "RenderCategory" {
It "With line separator" {
$actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $true)
$actual | Should -Be "Header 2 ><br> Header 3"
}
It "Without line separator" {
$actual = $DifferenceRender.RenderCategory(@("Header 1", "Header 2", "Header 3"), $false)
$actual | Should -Be "Header 2 > Header 3"
}
It "One header" {
$actual = $DifferenceRender.RenderCategory(@("Header 1"), $false)
$actual | Should -Be ""
}
It "Empty headers" {
$actual = $DifferenceRender.RenderCategory(@(), $false)
$actual | Should -Be ""
}
}
Context "RenderToolName" {
It "Clear tool name" {
$actual = $DifferenceRender.RenderToolName("My Tool 1")
$actual | Should -Be "My Tool 1"
}
It "Name with colon symbol" {
$actual = $DifferenceRender.RenderToolName("My Tool 1:")
$actual | Should -Be "My Tool 1"
}
}
Context "StrikeTableRow" {
It "Simple row" {
$actual = $DifferenceRender.StrikeTableRow("Test1|Test2|Test3")
$actual | Should -Be "~~Test1~~|~~Test2~~|~~Test3~~"
}
It "Row with spaces" {
$actual = $DifferenceRender.StrikeTableRow("Test 1|Test 2|Test 3")
$actual | Should -Be "~~Test 1~~|~~Test 2~~|~~Test 3~~"
}
}
Context "RenderHtmlTable" {
It "Simple table" {
$table = @(
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" },
[PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 2"; "Version" = "2.0" },
[PSCustomObject]@{ "Category" = "C"; "Tool name" = "My Tool 3"; "Version" = "3.0" }
)
$renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category")
$renderedTable | Should -Be @'
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Version</th>
</thead>
<tbody>
<tr>
<td rowspan="1">A</td>
<td>My Tool 1</td>
<td>1.0</td>
</tr>
<tr>
<td rowspan="1">B</td>
<td>My Tool 2</td>
<td>2.0</td>
</tr>
<tr>
<td rowspan="1">C</td>
<td>My Tool 3</td>
<td>3.0</td>
</tr>
</tbody>
</table>
'@
}
It "Table with the same category" {
$table = @(
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 1"; "Version" = "1.0" },
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 2"; "Version" = "2.0" },
[PSCustomObject]@{ "Category" = "A"; "Tool name" = "My Tool 3"; "Version" = "3.0" },
[PSCustomObject]@{ "Category" = "B"; "Tool name" = "My Tool 4"; "Version" = "4.0" }
)
$renderedTable = $DifferenceRender.RenderHtmlTable($table, "Category")
$renderedTable | Should -Be @'
<table>
<thead>
<th>Category</th>
<th>Tool name</th>
<th>Version</th>
</thead>
<tbody>
<tr>
<td rowspan="3">A</td>
<td>My Tool 1</td>
<td>1.0</td>
</tr>
<tr>
<td>My Tool 2</td>
<td>2.0</td>
</tr>
<tr>
<td>My Tool 3</td>
<td>3.0</td>
</tr>
<tr>
<td rowspan="1">B</td>
<td>My Tool 4</td>
<td>4.0</td>
</tr>
</tbody>
</table>
'@
}
}
Context "RenderTableNodesDiff" {
It "Add new table" {
$previousNode = $null
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
$actual | Should -Be @'
#### Header 2 > Header 3
| Name | Value |
| ---- | ----- |
| A | 1 |
| B | 2 |
'@
}
It "Remove existing table" {
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
$currentNode = $null
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
$actual | Should -Be @'
#### Header 2 > Header 3
| Name | Value |
| ----- | ----- |
| ~~A~~ | ~~1~~ |
| ~~B~~ | ~~2~~ |
'@
}
It "Add new rows to existing table" {
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
$actual | Should -Be @'
#### Header 2 > Header 3
| Name | Value |
| ---- | ----- |
| C | 3 |
| D | 4 |
'@
}
It "Remove rows from existing table" {
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
$currentNode = [TableNode]::new("Name|Value", @("C|3", "D|4"))
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
$actual | Should -Be @'
#### Header 2 > Header 3
| Name | Value |
| ----- | ----- |
| ~~A~~ | ~~1~~ |
| ~~B~~ | ~~2~~ |
'@
}
It "Row is changed in existing table" {
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2"))
$currentNode = [TableNode]::new("Name|Value", @("A|1", "B|3"))
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
$actual | Should -Be @'
#### Header 2 > Header 3
| Name | Value |
| ----- | ----- |
| ~~B~~ | ~~2~~ |
| B | 3 |
'@
}
It "Row is changed, added and removed at the same time in existing table" {
$previousNode = [TableNode]::new("Name|Value", @("A|1", "B|2", "C|3", "D|4"))
$currentNode = [TableNode]::new("Name|Value", @("B|2", "C|4", "D|4", "E|5"))
$reportItem = [ReportDifferenceItem]::new($previousNode, $currentNode, @("Header 1", "Header 2", "Header 3"))
$actual = $DifferenceRender.RenderTableNodesDiff($reportItem)
$actual | Should -Be @'
#### Header 2 > Header 3
| Name | Value |
| ----- | ----- |
| ~~A~~ | ~~1~~ |
| ~~C~~ | ~~3~~ |
| C | 4 |
| E | 5 |
'@
}
}
}
@@ -0,0 +1,93 @@
using module ../SoftwareReport.psm1
using module ../SoftwareReport.Nodes.psm1
Describe "SoftwareReport.E2E" {
Context "Report example 1" {
BeforeEach {
$softwareReport = [SoftwareReport]::new("macOS 11")
$softwareReport.Root.AddToolVersion("OS Version:", "macOS 11.7 (20G817)")
$softwareReport.Root.AddToolVersion("Image Version:", "20220918.1")
$installedSoftware = $softwareReport.Root.AddHeader("Installed Software")
$languagesAndRuntimes = $installedSoftware.AddHeader("Language and Runtime")
$languagesAndRuntimes.AddToolVersion("Bash", "5.1.16(1)-release")
$languagesAndRuntimes.AddToolVersionsListInline(".NET Core SDK", @("1.2.100", "1.2.200", "3.1.414"), "^\d+\.\d+\.\d")
$languagesAndRuntimes.AddNode([ToolVersionNode]::new("Perl", "5.34.0"))
$cachedTools = $installedSoftware.AddHeader("Cached Tools")
$cachedTools.AddToolVersionsList("Ruby", @("2.7.3", "2.8.1", "3.1.2"), "^\d+\.\d+")
$cachedTools.AddToolVersionsList("Node.js", @("14.8.0", "15.1.0", "16.4.2"), "^\d+")
$javaSection = $installedSoftware.AddHeader("Java")
$javaSection.AddTable(@(
[PSCustomObject] @{ Version = "8.0.125"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_8_X64" },
[PSCustomObject] @{ Version = "11.3.103"; Vendor = "My Vendor"; "Environment Variable" = "JAVA_HOME_11_X64" }
))
$sqlSection = $installedSoftware.AddHeader("MySQL")
$sqlSection.AddToolVersion("MySQL", "6.1.0")
$sqlSection.AddNote("MySQL service is disabled by default.`nUse the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'")
$expectedMarkdown = @'
# macOS 11
- OS Version: macOS 11.7 (20G817)
- Image Version: 20220918.1
## Installed Software
### Language and Runtime
- Bash 5.1.16(1)-release
- .NET Core SDK: 1.2.100, 1.2.200, 3.1.414
- Perl 5.34.0
### Cached Tools
#### Ruby
- 2.7.3
- 2.8.1
- 3.1.2
#### Node.js
- 14.8.0
- 15.1.0
- 16.4.2
### Java
| Version | Vendor | Environment Variable |
| -------- | --------- | -------------------- |
| 8.0.125 | My Vendor | JAVA_HOME_8_X64 |
| 11.3.103 | My Vendor | JAVA_HOME_11_X64 |
### MySQL
- MySQL 6.1.0
```
MySQL service is disabled by default.
Use the following command as a part of your job to start the service: 'sudo systemctl start mysql.service'
```
'@
}
It "ToMarkdown" {
$softwareReport.ToMarkdown() | Should -Be $expectedMarkdown
}
It "Serialization + Deserialization" {
$json = $softwareReport.ToJson()
$deserializedReport = [SoftwareReport]::FromJson($json)
$deserializedReport.ToMarkdown() | Should -Be $expectedMarkdown
}
}
Context "GetImageVersion" {
It "Image version exists" {
$softwareReport = [SoftwareReport]::new("MyReport")
$softwareReport.Root.AddToolVersion("Image Version:", "123.4")
$softwareReport.GetImageVersion() | Should -Be "123.4"
}
It "Empty report" {
$softwareReport = [SoftwareReport]::new("MyReport")
$softwareReport.GetImageVersion() | Should -Be "Unknown version"
}
}
}
@@ -0,0 +1,511 @@
using module ../SoftwareReport.Nodes.psm1
BeforeDiscovery {
Import-Module $(Join-Path $PSScriptRoot "TestHelpers.psm1") -DisableNameChecking
}
Describe "Nodes.UnitTests" {
Context "ToolVersionNode" {
It "ToMarkdown" {
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
$node.ToMarkdown() | Should -Be "- MyTool 2.1.3"
}
It "GetValue" {
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
$node.GetValue() | Should -Be "2.1.3"
}
It "Serialization" {
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
$json = $node.ToJsonObject()
$json.NodeType | Should -Be "ToolVersionNode"
$json.ToolName | Should -Be "MyTool"
$json.Version | Should -Be "2.1.3"
}
It "Deserialization" {
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = ""; Version = "2.1.3" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*'
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "" }) } | Should -Throw 'ToolVersionNode ''MyTool'' has empty version'
{ [ToolVersionNode]::FromJsonObject(@{ NodeType = "ToolVersionNode"; ToolName = "MyTool"; Version = "2.1.3" }) } | Should -Not -Throw
}
It "Serialization + Deserialization" {
$node = [ToolVersionNode]::new("MyTool", "2.1.3")
$json = $node.ToJsonObject()
$node2 = [ToolVersionNode]::FromJsonObject($json)
$json2 = $node2.ToJsonObject()
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
}
It "IsSimilarTo" {
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeTrue
[ToolVersionNode]::new("MyTool", "2.1.3").IsSimilarTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse
}
It "IsIdenticalTo" {
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "2.1.3")) | Should -BeTrue
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -BeFalse
[ToolVersionNode]::new("MyTool", "2.1.3").IsIdenticalTo([ToolVersionNode]::new("MyTool2", "2.1.3")) | Should -BeFalse
}
}
Context "ToolVersionsListNode" {
It "ToMarkdown - List" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
$expected = @(
"",
"# MyTool"
"- 2.7.7"
"- 3.0.5"
"- 3.1.3"
) -join "`n"
$node.ToMarkdown() | Should -Be $expected
}
It "ToMarkdown - Inline" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline")
$node.ToMarkdown() | Should -Be "- MyTool: 2.7.7, 3.0.5, 3.1.3"
}
It "GetValue" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
$node.GetValue() | Should -Be "2.7.7, 3.0.5, 3.1.3"
}
It "Serialization - List" {
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
$json = $node.ToJsonObject()
$json.NodeType | Should -Be "ToolVersionsListNode"
$json.ToolName | Should -Be "Ruby"
$json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3")
$json.MajorVersionRegex | Should -Be "^.+"
$json.ListType | Should -Be "List"
}
It "Serialization - Inline" {
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "Inline")
$json = $node.ToJsonObject()
$json.NodeType | Should -Be "ToolVersionsListNode"
$json.ToolName | Should -Be "Ruby"
$json.Versions | Should -BeArray @("2.7.7", "3.0.5", "3.1.3")
$json.MajorVersionRegex | Should -Be "^.+"
$json.ListType | Should -Be "Inline"
}
It "Deserialization" {
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = ""; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "ToolName": "The argument is null or empty.*'
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null or empty.*'
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @(); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw '*Exception setting "Versions": "The argument is null, empty,*'
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", '2.2.4'); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Throw 'Multiple versions from list * return the same result from regex *'
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = ""; ListType = "List" }) } | Should -Throw 'Version * doesn''t match regex *'
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Fake" }) } | Should -Throw '*Exception setting "ListType": "The argument * does not belong to the set*'
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "List" }) } | Should -Not -Throw
{ [ToolVersionsListNode]::FromJsonObject(@{ NodeType = "ToolVersionsListNode"; ToolName = "MyTool"; Versions = @("2.1.3", "3.1.4"); MajorVersionRegex = "^\d+"; ListType = "Inline" }) } | Should -Not -Throw
}
It "Serialization + Deserialization" {
$node = [ToolVersionsListNode]::new("Ruby", @("2.7.7", "3.0.5", "3.1.3"), "^.+", "List")
$json = $node.ToJsonObject()
$node2 = [ToolVersionsListNode]::FromJsonObject($json)
$json2 = $node2.ToJsonObject()
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
}
It "IsSimilarTo" {
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
) | Should -BeTrue
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
[ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List")
) | Should -BeTrue
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsSimilarTo(
[ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
) | Should -BeFalse
}
It "IsIdenticalTo" {
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
) | Should -BeTrue
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
[ToolVersionsListNode]::new("MyTool", @("2.1.5", "5.0.0"), "^.+", "List")
) | Should -BeFalse
[ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List").IsIdenticalTo(
[ToolVersionsListNode]::new("MyTool2", @("2.1.3", "3.1.5", "4.0.0"), "^.+", "List")
) | Should -BeFalse
}
It "ExtractMajorVersion" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+\.\d+", "List")
$node.ExtractMajorVersion("2.1.3") | Should -Be "2.1"
$node.ExtractMajorVersion("3.1.5") | Should -Be "3.1"
$node.ExtractMajorVersion("4.0.0") | Should -Be "4.0"
}
Context "ValidateMajorVersionRegex" {
It "Major version regex - unique versions" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "4.0.0"), "^\d+", "List")
$node.Versions | Should -BeArray @("2.1.3", "3.1.5", "4.0.0")
}
It "Major version regex - non-unique versions" {
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "3.1.5", "3.2.0", "4.0.0"), "^\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
}
It "Minor version regex - unique versions" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.4.0", "3.1.2"), "^\d+\.\d+", "List")
$node.Versions | Should -BeArray @("2.1.3", "2.4.0", "3.1.2")
}
It "Minor version regex - non-unique versions" {
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "3.1.2"), "^\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
}
It "Patch version regex - unique versions" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.5"), "^\d+\.\d+\.\d+", "List")
$node.Versions | Should -BeArray @("2.1.3", "2.1.4", "2.1.5")
}
It "Patch version regex - non-unique versions" {
{ [ToolVersionsListNode]::new("MyTool", @("2.1.3", "2.1.4", "2.1.4"), "^\d+\.\d+\.\d+", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
}
It ".NET Core version regex - unique versions" {
$node = [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.303"), "^\d+\.\d+\.\d", "List")
$node.Versions | Should -BeArray @("2.1.100", "2.1.205", "2.1.303")
}
It ".NET Core version regex - non-unique versions" {
{ [ToolVersionsListNode]::new("MyTool", @("2.1.100", "2.1.205", "2.1.230", "3.1.0"), "^\d+\.\d+\.\d", "List") } | Should -Throw "Multiple versions from list * return the same result from regex *"
}
}
}
Context "TableNode" {
It "ToMarkdown (Simple table)" {
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
$node.ToMarkdown() | Should -Be @'
| Name | Value |
| ---- | ----- |
| A | B |
| C | D |
'@
}
It "ToMarkdown (Wide cells)" {
$node = [TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too"))
$node.ToMarkdown() | Should -Be @'
| Name | Value |
| -------------------- | ---------------------------- |
| Very long value here | B |
| C | And very long value here too |
'@
}
It "CalculateColumnsWidth" {
[TableNode]::new("Name|Value", @("A|B", "C|D")).CalculateColumnsWidth() | Should -BeArray @(4, 5)
[TableNode]::new("Name|Value", @("Very long value here|B", "C|And very long value here too")).CalculateColumnsWidth() | Should -BeArray @(20, 28)
}
It "Serialization" {
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
$json = $node.ToJsonObject()
$json.NodeType | Should -Be "TableNode"
$json.Headers | Should -Be "Name|Value"
$json.Rows | Should -BeArray @("A|B", "C|D")
}
It "Deserialization" {
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = ""; Rows = @("A|1", "B|2") }) } | Should -Throw 'Exception setting "Headers": "The argument is null or empty. *'
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @() }) } | Should -Throw 'Exception setting "Rows": "The argument is null, empty, *'
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2|T", "C|3") }) } | Should -Throw 'Table has different number of columns in different rows'
{ [TableNode]::FromJsonObject(@{ NodeType = "TableNode"; Headers = "Name|Value"; Rows = @("A|1", "B|2") }) } | Should -Not -Throw
}
It "Serialization + Deserialization" {
$node = [TableNode]::new("Name|Value", @("A|B", "C|D"))
$json = $node.ToJsonObject()
$node2 = [TableNode]::FromJsonObject($json)
$json2 = $node2.ToJsonObject()
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
}
It "IsSimilarTo" {
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeTrue
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeTrue
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsSimilarTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue
}
It "IsIdenticalTo" {
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D"))) | Should -BeTrue
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Key", @("A|B", "C|D"))) | Should -BeTrue
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|D", "F|W"))) | Should -BeFalse
[TableNode]::new("Name|Value", @("A|B", "C|D")).IsIdenticalTo([TableNode]::new("Name|Value", @("A|B", "C|E"))) | Should -BeFalse
}
Context "FromObjectsArray" {
It "Correct table" {
$table = @(
[PSCustomObject]@{Name = "A"; Value = "B"}
[PSCustomObject]@{Name = "C"; Value = "D"}
)
$tableNode = [TableNode]::FromObjectsArray($table)
$tableNode.Headers | Should -Be "Name|Value"
$tableNode.Rows | Should -BeArray @("A|B", "C|D")
}
It "Correct table with spaces" {
$table = @(
[PSCustomObject]@{Name = "A B"; "My Value" = "1 2"}
[PSCustomObject]@{Name = "C D"; "My Value" = "3 4"}
)
$tableNode = [TableNode]::FromObjectsArray($table)
$tableNode.Headers | Should -Be "Name|My Value"
$tableNode.Rows | Should -BeArray @("A B|1 2", "C D|3 4")
}
It "Throw on empty table" {
{ [TableNode]::FromObjectsArray(@()) } | Should -Throw "Failed to create TableNode from empty objects array"
}
It "Throw on table with different columns" {
$table = @(
[PSCustomObject]@{Name = "A"; Value = "B"}
[PSCustomObject]@{Name = "C"; Value2 = "D"}
)
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode from objects array because objects have different properties"
}
It "Throw on empty row" {
$table = @(
[PSCustomObject]@{Name = "A"; Value = "B"},
[PSCustomObject]@{},
[PSCustomObject]@{Name = "C"; Value2 = "D"}
)
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some objects are empty"
}
It "Throw on incorrect symbols in table column names" {
$table = @(
[PSCustomObject]@{"Name|War" = "A"; Value = "B"}
[PSCustomObject]@{"Name|War" = "C"; Value = "D"}
)
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*"
}
It "Throw on incorrect symbols in table rows" {
$table = @(
[PSCustomObject]@{Name = "A"; Value = "B|AA"}
[PSCustomObject]@{Name = "C"; Value = "D"}
)
{ [TableNode]::FromObjectsArray($table) } | Should -Throw "Failed to create TableNode because some cells * contains forbidden symbol*"
}
}
}
Context "NoteNode" {
It "ToMarkdown" {
$node = [NoteNode]::new("Hello world`nGood Bye world")
$node.ToMarkdown() | Should -Be @'
```
hello world
Good Bye world
```
'@
}
It "Serialization" {
$node = [NoteNode]::new("MyContent`nMyContent2")
$json = $node.ToJsonObject()
$json.NodeType | Should -Be "NoteNode"
$json.Content | Should -Be "MyContent`nMyContent2"
}
It "Deserialization" {
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*'
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "" }) } | Should -Throw '*Exception setting "Content": "The argument is null or empty.*'
{ [NoteNode]::FromJsonObject(@{ NodeType = "NoteNode"; Content = "MyTool" }) } | Should -Not -Throw
}
It "Serialization + Deserialization" {
$node = [NoteNode]::new("MyContent`nMyContent2")
$json = $node.ToJsonObject()
$node2 = [NoteNode]::FromJsonObject($json)
$json2 = $node2.ToJsonObject()
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
}
It "IsSimilarTo" {
[NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent")) | Should -BeTrue
[NoteNode]::new("MyContent").IsSimilarTo([NoteNode]::new("MyContent2")) | Should -BeFalse
}
It "IsIdenticalTo" {
[NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent")) | Should -BeTrue
[NoteNode]::new("MyContent").IsIdenticalTo([NoteNode]::new("MyContent2")) | Should -BeFalse
}
}
Context "HeaderNode" {
It "ToMarkdown" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersion("MyTool", "2.1.3")
$node.ToMarkdown(1) | Should -Be @'
# MyHeader
- MyTool 2.1.3
'@
}
It "ToMarkdown (level 3)" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersion("MyTool", "2.1.3")
$node.ToMarkdown(3) | Should -Be @'
### MyHeader
- MyTool 2.1.3
'@
}
It "ToMarkdown (multiple levels)" {
$node = [HeaderNode]::new("MyHeader")
$node.AddHeader("MyHeader 2").AddHeader("MyHeader 3").AddHeader("MyHeader 4").AddToolVersion("MyTool", "2.1.3")
$node.ToMarkdown(1) | Should -Be @'
# MyHeader
## MyHeader 2
### MyHeader 3
#### MyHeader 4
- MyTool 2.1.3
'@
}
It "Serialization" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersion("MyTool", "2.1.3")
$json = $node.ToJsonObject()
$json.NodeType | Should -Be "HeaderNode"
$json.Title | Should -Be "MyHeader"
$json.Children | Should -HaveCount 1
}
It "Deserialization" {
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*'
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "" }) } | Should -Throw '*Exception setting "Title": "The argument is null or empty.*'
{ [HeaderNode]::FromJsonObject(@{ NodeType = "HeaderNode"; Title = "MyHeader" }) } | Should -Not -Throw
}
It "Serialization + Deserialization" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersion("MyTool", "2.1.3")
$json = $node.ToJsonObject()
$node2 = [HeaderNode]::FromJsonObject($json)
$json2 = $node2.ToJsonObject()
$($json | ConvertTo-Json) | Should -Be $($json2 | ConvertTo-Json)
}
It "IsSimilarTo" {
[HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader")) | Should -BeTrue
[HeaderNode]::new("MyHeader").IsSimilarTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse
}
It "IsIdenticalTo" {
[HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader")) | Should -BeTrue
[HeaderNode]::new("MyHeader").IsIdenticalTo([HeaderNode]::new("MyHeader2")) | Should -BeFalse
}
It "FindSimilarChildNode" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersion("MyTool", "2.1.3")
$node.FindSimilarChildNode([ToolVersionNode]::new("MyTool", "1.0.0")) | Should -Not -BeNullOrEmpty
$node.FindSimilarChildNode([ToolVersionNode]::New("MyTool2", "1.0.0")) | Should -BeNullOrEmpty
}
Context "Detect node duplicates" {
It "Similar HeaderNode on the same header" {
$node = [HeaderNode]::new("MyHeader")
$node.AddHeader("MySubHeader1")
$node.AddHeader("MySubHeader2")
{ $node.AddHeader("MySubHeader1") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "Similar ToolVersionNode on the same header" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersion("MyTool", "2.1.3")
$node.AddToolVersion("MyTool2", "2.1.3")
{ $node.AddToolVersion("MyTool", "2.1.3") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "Similar ToolVersionsListNode on the same header" {
$node = [HeaderNode]::new("MyHeader")
$node.AddToolVersionsListInline("MyTool", @("2.1.3", "3.0.0"), "^\d+")
$node.AddToolVersionsListInline("MyTool2", @("2.1.3", "3.0.0"), "^\d+")
{ $node.AddToolVersionsList("MyTool", @("2.1.3", "3.0.0"), "^\d+") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "Similar TableNode on the same header" {
$node = [HeaderNode]::new("MyHeader")
$node.AddTable(@(
[PSCustomObject]@{Name = "Value1"},
[PSCustomObject]@{Name = "Value2"}
))
{
$node.AddTable(@(
[PSCustomObject]@{Name = "Value1"},
[PSCustomObject]@{Name = "Value2"}
))
} | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "Similar NoteNode on the same header" {
$node = [HeaderNode]::new("MyHeader")
$node.AddNote("MyContent")
$node.AddNote("MyContent2")
{ $node.AddNote("MyContent") } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "AddNode detects duplicates" {
$node = [HeaderNode]::new("MyHeader")
$node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3"))
{ $node.AddNode([ToolVersionNode]::new("MyTool", "2.1.3")) } | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "AddNodes detects duplicates" {
$node = [HeaderNode]::new("MyHeader")
$node.AddNodes(@(
[ToolVersionNode]::new("MyTool", "2.1.3"),
[ToolVersionNode]::new("MyTool2", "2.1.4")
))
{
$node.AddNodes(@(
[ToolVersionNode]::new("MyTool3", "2.1.5"),
[ToolVersionNode]::new("MyTool", "2.1.3")
))
} | Should -Throw "This HeaderNode already contains the similar child node. It is not allowed to add the same node twice.*"
}
It "Doesn't allow adding non-header nodes after header node" {
$node = [HeaderNode]::new("MyHeader")
{ $node.AddToolVersion("MyTool", "2.1.3") } | Should -Not -Throw
{ $node.AddHeader("MySubHeader") } | Should -Not -Throw
{ $node.AddToolVersion("MyTool2", "2.1.4") } | Should -Throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node"
{ $node.AddHeader("MySubHeader2") } | Should -Not -Throw
{ $node.AddToolVersionsListInline("MyTool3", @("2.1.4", "2.1.5"), "^.+") } | Should -Throw "It is not allowed to add the non-header node after the header node. Consider adding the separate HeaderNode for this node"
{ $node.AddToolVersionsList("MyTool4", @("2.1.4", "2.1.5"), "^.+") } | Should -Not -Throw
}
}
}
}
@@ -0,0 +1,34 @@
function ShouldBeArray([Array] $ActualValue, [Array]$ExpectedValue, [Switch] $Negate, [String] $Because) {
if ($Negate) {
throw "Negation is not supported for Should-BeArray"
}
if ($ExpectedValue.Count -eq 0) {
throw "Expected array cannot be empty. Use Should-BeNullOrEmpty instead."
}
$ExpectedValue | ForEach-Object {
if ($_.GetType() -notin @([String], [Int32])) {
throw "Only string or int arrays are supported in Should-BeArray"
}
}
$actualValueJson = $ActualValue | ConvertTo-Json
$expectedValueJson = $ExpectedValue | ConvertTo-Json
$succeeded = ($ActualValue.Count -eq $ExpectedValue.Count) -and ($actualValueJson -eq $expectedValueJson)
if (-not $succeeded) {
$failureMessage = "Expected array '$actualValueJson' to be equal to '$expectedValueJson'"
}
return [PSCustomObject]@{
Succeeded = $succeeded
FailureMessage = $failureMessage
}
}
Add-ShouldOperator -Name BeArray `
-InternalName 'ShouldBeArray' `
-Test ${function:ShouldBeArray} `
-SupportsArrayInput
+9
View File
@@ -0,0 +1,9 @@
{
"tool": "Credential Scanner",
"suppressions": [
{
"placeholder": "P@ssword!!",
"_justification": "Password used by SQL Express. It is required to interact with database."
}
]
}
+14
View File
@@ -0,0 +1,14 @@
param(
[String] [Parameter (Mandatory=$true)] $RepoUrl,
[String] [Parameter (Mandatory=$true)] $RepoBranch
)
Write-Host "Clean up default repository"
Remove-Item -path './*' -Recurse -Force
Write-Host "Download ${RepoBranch} branch from ${RepoUrl}"
$env:GIT_REDIRECT_STDERR = '2>&1'
git clone $RepoUrl . -b $RepoBranch --single-branch --depth 1
Write-Host "Latest commit:"
git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1
@@ -0,0 +1,181 @@
# Ideally we would use GitHub Actions for this, but since we use self-hosted machines to run image builds
# we need the following features to use GitHub Actions for Images CI:
# - https://github.community/t5/GitHub-Actions/Make-secrets-available-to-builds-of-forks/m-p/30678#M508
# - https://github.community/t5/GitHub-Actions/GitHub-Actions-Manual-Trigger-Approvals/td-p/31504
# - https://github.community/t5/GitHub-Actions/Protecting-github-workflows/td-p/30290
parameters:
- name: job_id
type: string
default: 'generate_image'
- name: image_type
type: string
- name: image_template_name
type: string
- name: image_readme_name
type: string
- name: agent_pool
type: object
default:
name: 'ci-agent-pool'
- name: variable_group_name
type: string
default: 'Image Generation Variables'
- name: create_release
type: boolean
default: true
- name: repository_ref
type: string
default: 'self'
jobs:
- job: ${{ parameters.job_id }}
displayName: Image Generation (${{ parameters.image_type }})
timeoutInMinutes: 600
cancelTimeoutInMinutes: 30
pool: ${{ parameters.agent_pool }}
variables:
- group: ${{ parameters.variable_group_name }}
steps:
- checkout: ${{ parameters.repository_ref }}
clean: true
fetchDepth: 0
fetchTags: false
- task: PowerShell@2
displayName: 'Download custom repository'
condition: and(ne(variables['CUSTOM_REPOSITORY_URL'], ''), ne(variables['CUSTOM_REPOSITORY_BRANCH'], ''))
inputs:
targetType: 'filePath'
filePath: ./images.CI/download-repo.ps1
arguments: -RepoUrl $(CUSTOM_REPOSITORY_URL) `
-RepoBranch $(CUSTOM_REPOSITORY_BRANCH)
- task: AzureCLI@2
displayName: 'Set variables'
inputs:
azureSubscription: 'spn-hosted-runners'
scriptType: 'pscore'
scriptLocation: 'inlineScript'
inlineScript: |
$ImageType = "${{ parameters.image_type }}"
$TemplateDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "ubuntu/templates" } else { "windows/templates" }
$TemplateDirectoryPath = Join-Path "images" $TemplateDirectoryName | Resolve-Path
$TemplateFileName = "${{ parameters.image_template_name }}"
$TemplatePath = Join-Path $TemplateDirectoryPath $TemplateFileName
Write-Host "##vso[task.setvariable variable=TemplateDirectoryPath;]$TemplateDirectoryPath"
Write-Host "##vso[task.setvariable variable=TemplatePath;]$TemplatePath"
$ManagedImageName = "${{ parameters.image_type }}-$(Build.BuildId)"
Write-Host "##vso[task.setvariable variable=ManagedImageName;]$ManagedImageName"
$TempResourceGroupName = "packer-temp-$ManagedImageName"
Write-Host "##vso[task.setvariable variable=TempResourceGroupName;]$TempResourceGroupName"
$clientSecret = $(az keyvault secret show --name "spnhostedrunners" --vault-name "gh-imagegeneration" --query value -o tsv)
Write-Host "##vso[task.setvariable variable=ClientSecret;issecret=true]$clientSecret"
- task: PowerShell@2
displayName: 'Build VM'
inputs:
targetType: filePath
filePath: ./images.CI/linux-and-win/build-image.ps1
arguments: -ClientId $(CLIENT_ID) `
-ClientSecret "$(ClientSecret)" `
-TemplatePath $(TemplatePath) `
-ImageName "$(ManagedImageName)" `
-ImageResourceGroupName $(AZURE_RESOURCE_GROUP) `
-TempResourceGroupName "$(TempResourceGroupName)" `
-SubscriptionId $(AZURE_SUBSCRIPTION) `
-TenantId $(AZURE_TENANT) `
-Location $(AZURE_LOCATION) `
-VirtualNetworkName $(BUILD_AGENT_VNET_NAME) `
-VirtualNetworkRG $(BUILD_AGENT_VNET_RESOURCE_GROUP) `
-VirtualNetworkSubnet $(BUILD_AGENT_SUBNET_NAME)
env:
PACKER_LOG: 1
PACKER_LOG_PATH: "$(Agent.TempDirectory)/packer-log.txt"
- task: PowerShell@2
displayName: 'Copy image artifacts to the separate directory'
inputs:
targetType: 'inline'
script: |
$ImageType = "${{ parameters.image_type }}"
$rootDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "ubuntu" } else { "windows" }
$rootDirectoryPath = Join-Path "images" $rootDirectoryName | Resolve-Path
$readmePath = Join-Path $rootDirectoryPath "${{ parameters.image_readme_name }}"
$softwareReportPath = Join-Path $rootDirectoryPath "software-report.json"
Copy-Item -Path $readmePath -Destination "$(Build.ArtifactStagingDirectory)/"
if (Test-Path $softwareReportPath) {
Copy-Item -Path $softwareReportPath -Destination "$(Build.ArtifactStagingDirectory)/"
}
- task: PowerShell@2
displayName: 'Print markdown software report'
inputs:
targetType: 'inline'
script: |
Get-Content -Path "$(Build.ArtifactStagingDirectory)/${{ parameters.image_readme_name }}"
- task: PowerShell@2
displayName: 'Print json software report'
inputs:
targetType: 'inline'
script: |
$softwareReportPath = "$(Build.ArtifactStagingDirectory)/software-report.json"
if (Test-Path $softwareReportPath) {
Get-Content -Path $softwareReportPath
}
- task: PublishBuildArtifacts@1
inputs:
ArtifactName: 'Built_VM_Artifacts'
displayName: Publish Artifacts
- task: PowerShell@2
displayName: 'Print provisioners duration'
inputs:
targetType: 'filePath'
filePath: ./images.CI/measure-provisioners-duration.ps1
arguments: -PackerLogPath "$(Agent.TempDirectory)/packer-log.txt" `
-PrefixToPathTrim "$(TemplateDirectoryPath)" `
-PrintTopNLongest 25
- ${{ if eq(parameters.create_release, true) }}:
- task: PowerShell@2
displayName: 'Create release for VM deployment'
inputs:
targetType: filePath
filePath: ./images.CI/linux-and-win/create-release.ps1
arguments: -BuildId $(Build.BuildId) `
-Organization $(RELEASE_TARGET_ORGANIZATION) `
-DefinitionId $(RELEASE_TARGET_DEFINITION_ID) `
-Project $(RELEASE_TARGET_PROJECT) `
-ImageType "${{ parameters.image_type }}" `
-ManagedImageName "$(ManagedImageName)" `
-AccessToken $(RELEASE_TARGET_TOKEN)
- task: PowerShell@2
displayName: 'Clean up resources'
condition: always()
inputs:
targetType: filePath
filePath: ./images.CI/linux-and-win/cleanup.ps1
arguments: -TempResourceGroupName "$(TempResourceGroupName)" `
-SubscriptionId $(AZURE_SUBSCRIPTION) `
-ClientId $(CLIENT_ID) `
-ClientSecret "$(ClientSecret)" `
-TenantId $(AZURE_TENANT)
@@ -0,0 +1,21 @@
schedules:
- cron: "0 0 * * *"
displayName: Daily
branches:
include:
- main
always: true
trigger: none
pr:
autoCancel: true
branches:
include:
- main
jobs:
- template: image-generation.yml
parameters:
image_type: ubuntu2004
image_readme_name: Ubuntu2004-Readme.md
image_template_name: ubuntu-20.04.pkr.hcl
@@ -0,0 +1,21 @@
schedules:
- cron: "0 0 * * *"
displayName: Daily
branches:
include:
- main
always: true
trigger: none
pr:
autoCancel: true
branches:
include:
- main
jobs:
- template: image-generation.yml
parameters:
image_type: ubuntu2204
image_readme_name: Ubuntu2204-Readme.md
image_template_name: ubuntu-22.04.pkr.hcl
@@ -0,0 +1,21 @@
schedules:
- cron: "0 0 * * *"
displayName: Daily
branches:
include:
- main
always: true
trigger: none
pr:
autoCancel: true
branches:
include:
- main
jobs:
- template: image-generation.yml
parameters:
image_type: ubuntu2404
image_readme_name: Ubuntu2404-Readme.md
image_template_name: ubuntu-24.04.pkr.hcl
@@ -0,0 +1,21 @@
schedules:
- cron: "0 0 * * *"
displayName: Daily
branches:
include:
- main
always: true
trigger: none
pr:
autoCancel: true
branches:
include:
- main
jobs:
- template: image-generation.yml
parameters:
image_type: windows2019
image_readme_name: Windows2019-Readme.md
image_template_name: windows-2019.pkr.hcl
@@ -0,0 +1,21 @@
schedules:
- cron: "0 0 * * *"
displayName: Daily
branches:
include:
- main
always: true
trigger: none
pr:
autoCancel: true
branches:
include:
- main
jobs:
- template: image-generation.yml
parameters:
image_type: windows2022
image_readme_name: Windows2022-Readme.md
image_template_name: windows-2022.pkr.hcl
+66
View File
@@ -0,0 +1,66 @@
param(
[String] [Parameter (Mandatory=$true)] $TemplatePath,
[String] [Parameter (Mandatory=$true)] $ClientId,
[String] [Parameter (Mandatory=$true)] $ClientSecret,
[String] [Parameter (Mandatory=$true)] $Location,
[String] [Parameter (Mandatory=$true)] $ImageName,
[String] [Parameter (Mandatory=$true)] $ImageResourceGroupName,
[String] [Parameter (Mandatory=$true)] $TempResourceGroupName,
[String] [Parameter (Mandatory=$true)] $SubscriptionId,
[String] [Parameter (Mandatory=$true)] $TenantId,
[String] [Parameter (Mandatory=$false)] $VirtualNetworkName,
[String] [Parameter (Mandatory=$false)] $VirtualNetworkRG,
[String] [Parameter (Mandatory=$false)] $VirtualNetworkSubnet,
[String] [Parameter (Mandatory=$false)] $AllowedInboundIpAddresses = "[]"
)
if (-not (Test-Path $TemplatePath))
{
Write-Error "'-TemplatePath' parameter is not valid. You have to specify correct Template Path"
exit 1
}
$ImageTemplateName = [io.path]::GetFileName($TemplatePath).Split(".")[0]
$InstallPassword = [System.GUID]::NewGuid().ToString().ToUpper()
$SensitiveData = @(
'OSType',
'StorageAccountLocation',
'OSDiskUri',
'OSDiskUriReadOnlySas',
'TemplateUri',
'TemplateUriReadOnlySas',
': ->'
)
Write-Host "Show Packer Version"
packer --version
Write-Host "Download packer plugins"
packer init $TemplatePath
Write-Host "Validate packer template"
packer validate -syntax-only $TemplatePath
Write-Host "Build $ImageTemplateName VM"
packer build -var "client_id=$ClientId" `
-var "client_secret=$ClientSecret" `
-var "install_password=$InstallPassword" `
-var "location=$Location" `
-var "managed_image_name=$ImageName" `
-var "managed_image_resource_group_name=$ImageResourceGroupName" `
-var "subscription_id=$SubscriptionId" `
-var "temp_resource_group_name=$TempResourceGroupName" `
-var "tenant_id=$TenantId" `
-var "virtual_network_name=$VirtualNetworkName" `
-var "virtual_network_resource_group_name=$VirtualNetworkRG" `
-var "virtual_network_subnet_name=$VirtualNetworkSubnet" `
-var "allowed_inbound_ip_addresses=$($AllowedInboundIpAddresses)" `
-color=false `
$TemplatePath `
| Where-Object {
#Filter sensitive data from Packer logs
$currentString = $_
$sensitiveString = $SensitiveData | Where-Object { $currentString -match $_ }
$sensitiveString -eq $null
}
+19
View File
@@ -0,0 +1,19 @@
param(
[Parameter (Mandatory=$true)] [string] $TempResourceGroupName,
[Parameter (Mandatory=$true)] [string] $SubscriptionId,
[Parameter (Mandatory=$true)] [string] $ClientId,
[Parameter (Mandatory=$true)] [string] $ClientSecret,
[Parameter (Mandatory=$true)] [string] $TenantId
)
az login --service-principal --username $ClientId --password=$ClientSecret --tenant $TenantId | Out-Null
az account set --subscription $SubscriptionId | Out-Null
$groupExist = az group exists --name $TempResourceGroupName
if ($groupExist -eq "true") {
Write-Host "Found a match, deleting temporary files"
az group delete --name $TempResourceGroupName --yes | Out-Null
Write-Host "Temporary group was deleted successfully"
} else {
Write-Host "No temporary groups found"
}
@@ -0,0 +1,36 @@
param(
[Parameter (Mandatory)] [UInt32] $BuildId,
[Parameter (Mandatory)] [string] $Organization,
[Parameter (Mandatory)] [string] $Project,
[Parameter (Mandatory)] [string] $ImageType,
[Parameter (Mandatory)] [string] $ManagedImageName,
[Parameter (Mandatory)] [string] $DefinitionId,
[Parameter (Mandatory)] [string] $AccessToken
)
$Body = @{
definitionId = $DefinitionId
variables = @{
ImageBuildId = @{
value = $BuildId
}
ImageType = @{
value = $ImageType
}
ManagedImageName = @{
value = $ManagedImageName
}
}
isDraft = "false"
} | ConvertTo-Json -Depth 3
$URL = "https://vsrm.dev.azure.com/$Organization/$Project/_apis/release/releases?api-version=5.1"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("'':${AccessToken}"))
$headers = @{
Authorization = "Basic ${base64AuthInfo}"
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
$NewRelease = Invoke-RestMethod $URL -Body $Body -Method "POST" -Headers $headers -ContentType "application/json"
Write-Host "Created release: $($NewRelease._links.web.href)"
+248
View File
@@ -0,0 +1,248 @@
function Push-AnkaTemplateToRegistry {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $RegistryUrl,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $TagName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $TemplateName
)
# if registry uuid doesn't match then delete an image in registry
$AnkaCaCrtPath="$HOME/.config/anka/certs/anka-ca-crt.pem"
$images = anka --machine-readable registry --cacert $AnkaCaCrtPath --registry-path $RegistryUrl list | ConvertFrom-Json | ForEach-Object body
$images | Where-Object name -eq $TemplateName | ForEach-Object {
$id = $_.uuid
Show-StringWithFormat "Deleting '$TemplateName[$id]' VM and '$TagName' tag"
$curlCommand='curl -s -X DELETE -k "{0}/registry/vm?id={1}"' -f $RegistryUrl, $id
Invoke-AnkaCommand -Command $curlCommand
}
$command = "anka registry --cacert $AnkaCaCrtPath --registry-path $RegistryUrl push --force --tag $TagName $TemplateName"
Invoke-AnkaCommand -Command $command
}
function Get-AnkaVM {
param(
[string] $VMName
)
$command = "anka --machine-readable list"
if (-not [string]::IsNullOrEmpty($VMName)) {
$command = "anka --machine-readable show $VMName"
}
Invoke-AnkaCommand -Command $command | ConvertFrom-Json | Foreach-Object body
}
function Get-AnkaVMStatus {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName
)
$command = "anka --machine-readable list $VMName"
Invoke-AnkaCommand -Command $command | ConvertFrom-Json | Foreach-Object { $_.body.status }
}
function Get-AnkaVMIPAddress {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName
)
Get-AnkaVM -VMName $VMName | Foreach-Object ip
}
function Invoke-AnkaCommand {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Command
)
$result = bash -c "$Command 2>&1"
if ($LASTEXITCODE -ne 0) {
Write-Error "There is an error during command execution:`n$result"
exit 1
}
$result
}
function New-AnkaVMTemplate {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $InstallerPath,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $TemplateName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $TemplateUsername,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $TemplatePassword,
[Parameter(Mandatory)]
[int] $CPUCount,
[Parameter(Mandatory)]
[int] $RamSizeGb,
[Parameter(Mandatory)]
[int] $DiskSizeGb
)
$env:ANKA_DEFAULT_USER = $TemplateUsername
$env:ANKA_DEFAULT_PASSWD = $TemplatePassword
$env:ANKA_CREATE_SUSPEND = 0
$command = "anka create --cpu-count '$CPUCount' --ram-size '${RamSizeGb}G' --disk-size '${DiskSizeGb}G' --app '$InstallerPath' $TemplateName"
Invoke-AnkaCommand -Command $command
}
function Remove-AnkaVM {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName
)
$command = "anka delete $VMName --yes"
$isTemplateExists = Get-AnkaVM | Where-Object name -eq $VMName
if ($isTemplateExists) {
$null = Invoke-AnkaCommand -Command $command
}
}
function Set-AnkaVMVideoController {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $ShortMacOSVersion,
[ValidateSet("fbuf", "pg")]
[string] $Controller = "pg"
)
$command = "anka modify $VMName set display -c $Controller"
$null = Invoke-AnkaCommand -Command $command
}
function Set-AnkaVMDisplayResolution {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $DisplayResolution
)
$command = "anka modify $VMName set display -r $DisplayResolution"
$null = Invoke-AnkaCommand -Command $command
}
function Start-AnkaVM {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName
)
$command = "anka start $VMName"
$vmStatus = Get-AnkaVMStatus -VMName $VMName
if ($vmStatus -eq "stopped") {
$null = Invoke-AnkaCommand -Command $command
}
}
function Stop-AnkaVM {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName
)
$command = "anka stop $VMName"
$vmStatus = Get-AnkaVMStatus -VMName $VMName
if ($vmStatus -eq "running") {
$null = Invoke-AnkaCommand -Command $command
}
}
function Wait-AnkaVMIPAddress {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName,
[int] $RetryCount = 20,
[int] $Seconds = 60
)
$condition = {
$vmStatus = Get-AnkaVMStatus -VMName $VMName
if ($vmStatus -eq "failed") {
Write-Host "`t [-] $VMName is in failed status"
exit 1
}
Get-AnkaVMIPAddress -VMName $VMName
}
$null = Invoke-WithRetry -BreakCondition $condition -RetryCount $RetryCount -Seconds $Seconds
}
function Wait-AnkaVMSSHService {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName,
[int] $RetryCount = 20,
[int] $Seconds = 60
)
Start-Sleep -Seconds $Seconds
Write-Host "`t[*] Waiting for '$VMName' VM to get an IP address"
Wait-AnkaVMIPAddress -VMName $VMName -RetryCount $RetryCount -Seconds $Seconds
$ipAddress = Get-AnkaVMIPAddress -VMName $VMName
Write-Host "`t[*] The '$ipAddress' IP address for '$VMName' VM"
Write-Host "`t[*] Checking if SSH on a port is open"
$isSSHPortOpen = Test-SSHPort -IPAddress $ipAddress
if (-not $isSSHPortOpen) {
Write-Host "`t[x] SSH port is closed"
exit 1
}
}
function Set-AnkaVMUuid {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Uuid
)
$command = "anka modify $VMName set custom-variable hw.uuid $Uuid"
Write-Host "`t[*] Setting $VMName uuid to $Uuid"
Invoke-AnkaCommand -Command $command
}
@@ -0,0 +1,227 @@
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[version] $MacOSVersion,
[ValidateNotNullOrEmpty()]
[string] $TemplateUsername,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $TemplatePassword,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $RegistryUrl,
[ValidateNotNullOrEmpty()]
[string] $TemplateName,
[bool] $DownloadLatestVersion = $true,
[bool] $PushToRegistry = $true,
[bool] $BetaSearch = $false,
[bool] $InstallSoftwareUpdate = $true,
[bool] $EnableAutoLogon = $true,
[int] $CPUCount = 6,
[int] $RamSizeGb = 7,
[int] $DiskSizeGb = 325,
[string] $DisplayResolution = "1920x1080",
[string] $TagName = [DateTimeOffset]::Now.ToUnixTimeSeconds(),
[string] $Uuid = "4203018E-580F-C1B5-9525-B745CECA79EB"
)
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
# Import helper modules
Import-Module "$PSScriptRoot/Anka.Helpers.psm1"
Import-Module "$PSScriptRoot/Service.Helpers.psm1"
# Helper functions
function Invoke-EnableAutoLogon {
if (-not $EnableAutoLogon) {
Write-Host "`t[*] Skip configuring AutoLogon"
return
}
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30
Write-Host "`t[*] Enable AutoLogon"
Enable-AutoLogon -HostName $ipAddress -UserName $TemplateUsername -Password $TemplatePassword
Write-Host "`t[*] Reboot '$TemplateName' VM to enable AutoLogon"
Restart-VMSSH -HostName $ipAddress | Show-StringWithFormat
Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30
Write-Host "`t[*] Checking if AutoLogon is enabled"
Test-AutoLogon -VMName $TemplateName -UserName $TemplateUsername
}
function Invoke-SoftwareUpdate {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Password
)
if (-not $InstallSoftwareUpdate) {
Write-Host "`t[*] Skip installing software updates"
return
}
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
# Unenroll Seed
Write-Host "`t[*] Resetting the seed before requesting stable versions"
Remove-CurrentBetaSeed -HostName $ipAddress | Show-StringWithFormat
# Install Software Updates
# Security updates may not be able to install(hang, freeze) when AutoLogon is turned off
Write-Host "`t[*] Finding available software"
$newUpdates = Get-SoftwareUpdate -HostName $ipAddress
if (-not $newUpdates) {
Write-Host "`t[*] No Updates Available"
return
}
# Define the next macOS version
$command = "sw_vers"
$guestMacosVersion = Invoke-SSHPassCommand -HostName $ipAddress -Command $command
switch -regex ($guestMacosVersion[1]) {
'12.\d' { $nextOSVersion = 'macOS Ventura|macOS Sonoma|macOS Sequoia' }
'13.\d' { $nextOSVersion = 'macOS Sonoma|macOS Sequoia' }
'14.\d' { $nextOSVersion = 'macOS Sequoia' }
}
Write-Host "`t[*] Fetching Software Updates ready to install on '$TemplateName' VM:"
Show-StringWithFormat $newUpdates
$listOfNewUpdates = $($($newUpdates.Split("*")).Split("Title").where({$_ -match "Label:"}).Replace("Label: ", '').where({$_ -notmatch $nextOSVersion}))
Write-Host "`t[*] Installing Software Updates on '$TemplateName' VM:"
Install-SoftwareUpdate -HostName $ipAddress -listOfUpdates $listOfNewUpdates -Password $Password | Show-StringWithFormat
Write-Host "`t[*] Sleep 60 seconds before the software updates have been installed"
Start-Sleep -Seconds 60
Write-Host "`t[*] Waiting for loginwindow process"
Wait-LoginWindow -HostName $ipAddress | Show-StringWithFormat
# Re-enable AutoLogon after installing a new security software update
Invoke-EnableAutoLogon
foreach ($newupdate in $listOfNewUpdates) {
# Check software updates have been installed
$updates = Get-SoftwareUpdate -HostName $ipAddress
if ($updates.Contains("Action: restart") -and !($updates -match $nextOSVersion)) {
Write-Host "`t[x] Software updates failed to install: "
Show-StringWithFormat $updates
exit 1
}
}
Write-Host "`t[*] Show the install history:"
$hUpdates = Get-SoftwareUpdateHistory -HostName $ipAddress
Show-StringWithFormat $hUpdates
Write-Host "`t[*] The current macOS version:"
$command = "sw_vers"
Invoke-SSHPassCommand -HostName $ipAddress -Command $command | Show-StringWithFormat
}
function Invoke-UpdateSettings {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Password
)
$isConfRequired = $InstallSoftwareUpdate -or $EnableAutoLogon
if (-not $isConfRequired) {
Write-Host "`t[*] Skip additional configuration"
return
}
Write-Host "`t[*] Starting '$TemplateName' VM"
Start-AnkaVM -VMName $TemplateName
Write-Host "`t[*] Waiting for SSH service on '$TemplateName' VM"
Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30
# Configure AutoLogon
Invoke-EnableAutoLogon
# Install software updates
Invoke-SoftwareUpdate -Password $Password
Write-Host "`t[*] Stopping '$TemplateName' VM"
Stop-AnkaVM -VMName $TemplateName
}
function Test-VMStopped {
$vmStatus = Get-AnkaVMStatus -VMName $TemplateName
if ($vmStatus -ne "stopped") {
Write-Host "`t[x] VM '$TemplateName' state is not stopped. The current state is '$vmStatus'"
exit 1
}
}
# Password is passed as env-var "SSHPASS"
$env:SSHUSER = $TemplateUsername
$env:SSHPASS = $TemplatePassword
Write-Host "`n[#1] Download macOS application installer:"
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
if ([string]::IsNullOrEmpty($TemplateName)) {
$osArch = $(arch)
if ($osArch -eq "arm64") {
$macOSInstaller = Get-MacOSIPSWInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
$TemplateName = "clean_macos_${shortMacOSVersion}_${osArch}"
} else {
$macOSInstaller = Get-MacOSInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
$TemplateName = "clean_macos_${shortMacOSVersion}"
}
}
Write-Host "`n[#2] Create a VM template:"
Write-Host "`t[*] Deleting existed template with name '$TemplateName' before creating a new one"
Remove-AnkaVM -VMName $TemplateName
# Temporary disable VNC for macOS 14
# It's probably Anka's bug fixed in 3.3.2
if ($shortMacOSVersion -eq "14") {
$env:ANKA_CREATE_VNC = 0
}
Write-Host "`t[*] Creating Anka VM template with name '$TemplateName' and '$TemplateUsername' user"
Write-Host "`t[*] CPU Count: $CPUCount, RamSize: ${RamSizeGb}G, DiskSizeGb: ${DiskSizeGb}G, InstallerPath: $macOSInstaller, TemplateName: $TemplateName"
New-AnkaVMTemplate -InstallerPath "$macOSInstaller" `
-TemplateName $TemplateName `
-TemplateUsername $TemplateUsername `
-TemplatePassword $TemplatePassword `
-CPUCount $CPUCount `
-RamSizeGb $RamSizeGb `
-DiskSizeGb $DiskSizeGb | Show-StringWithFormat
Write-Host "`n[#3] Configure AutoLogon and/or install software updates:"
Invoke-UpdateSettings -Password $TemplatePassword
Write-Host "`n[#4] Finalization '$TemplateName' configuration and push to the registry:"
Write-Host "`t[*] The '$TemplateName' VM status is stopped"
Test-VMStopped
# Configure graphics settings
Write-Host "`t[*] Enabling Graphics Acceleration with Apple Metal for '$TemplateName' VM"
Set-AnkaVMVideoController -VMName $TemplateName -ShortMacOSVersion $ShortMacOSVersion
Write-Host "`t[*] Setting screen resolution to $DisplayResolution for $TemplateName"
Set-AnkaVMDisplayResolution -VMName $TemplateName -DisplayResolution $DisplayResolution
# Set static UUID
Set-AnkaVMUuid -VMName $TemplateName -Uuid $Uuid
if ($PushToRegistry) {
# Push a VM template (and tag) to the Cloud
Write-Host "`t[*] Pushing '$TemplateName' image with '$TagName' tag to the '$RegistryUrl' registry..."
Push-AnkaTemplateToRegistry -RegistryUrl $registryUrl -TagName $TagName -TemplateName $TemplateName
}
+478
View File
@@ -0,0 +1,478 @@
function Enable-AutoLogon {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $UserName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Password
)
$url = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/assets/bootstrap-provisioner/setAutoLogin.sh"
$script = Invoke-RestMethod -Uri $url
$base64 = [Convert]::ToBase64String($script.ToCharArray())
$command = "echo $base64 | base64 --decode > ./setAutoLogin.sh;sudo bash ./setAutoLogin.sh '${UserName}' '${Password}';rm ./setAutoLogin.sh"
Invoke-SSHPassCommand -HostName $HostName -Command $command
}
function Invoke-SoftwareUpdateArm64 {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Password,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[array] $ListOfUpdates
)
# Define the next macOS version
$command = "sw_vers"
$guestMacosVersion = Invoke-SSHPassCommand -HostName $HostName -Command $command
switch -regex ($guestMacosVersion[1]) {
'12.\d' { $nextOSVersion = 'macOS Ventura|macOS Sonoma|macOS Sequoia' }
'13.\d' { $nextOSVersion = 'macOS Sonoma|macOS Sequoia' }
'14.\d' { $nextOSVersion = 'macOS Sequoia' }
}
$url = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/assets/auto-software-update-arm64.exp"
$script = Invoke-RestMethod -Uri $url
foreach ($update in $ListOfUpdates) {
if ($update -notmatch $nextOSVersion) {
$updatedScript = $script.Replace("MACOSUPDATE", $($($update.trim()).Replace(" ","\ ")))
$base64 = [Convert]::ToBase64String($updatedScript.ToCharArray())
$command = "echo $base64 | base64 --decode > ./auto-software-update-arm64.exp;chmod +x ./auto-software-update-arm64.exp; ./auto-software-update-arm64.exp ${Password};rm ./auto-software-update-arm64.exp"
Invoke-SSHPassCommand -HostName $HostName -Command $command
}
}
}
function Get-AvailableVersions {
param (
[bool] $IsBeta = $false
)
if ($IsBeta) {
$searchPostfix = " beta"
}
$command = { /usr/sbin/softwareupdate --list-full-installers | grep "macOS" }
$condition = { $LASTEXITCODE -eq 0 }
$softwareUpdates = Invoke-WithRetry -Command $command -BreakCondition $condition | Where-Object { $_.Contains("Title: macOS") -and $_ -match $searchPostfix }
$allVersions = $softwareUpdates -replace "(\* )?(Title|Version|Size):" | ConvertFrom-Csv -Header OSName, OSVersion | Select-Object OSName, OSVersion -Unique
$allVersions
}
function Get-AvailableIPSWVersions {
param (
[bool] $IsBeta = $false,
[bool] $IsLatest = $true,
[string] $MacOSCodeNameOrVersion
)
if ($IsBeta) {
$command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --include-betas --latest --export "/Applications/export.json" }
} elseif ($IsLatest) {
$command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --latest --export "/Applications/export.json" }
} else {
$command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --export "/Applications/export.json" }
}
$condition = { $LASTEXITCODE -eq 0 }
Invoke-WithRetry -Command $command -BreakCondition $condition | Out-Null
$softwareList = get-content -Path "/Applications/export.json"
$availableBuilds = ($softwareList | ConvertFrom-Json).build
if ($null -eq $availableBuilds) {
Write-Host "Requested macOS '$MacOSCodeNameOrVersion' version not found in the list of available installers."
$command = { mist list firmware "$($MacOSCodeNameOrVersion.split('.')[0])" }
Invoke-WithRetry -Command $command -BreakCondition $condition
exit 1
}
return $availableBuilds
}
function Get-MacOSIPSWInstaller {
param (
[Parameter(Mandatory)]
[version] $MacOSVersion,
[bool] $DownloadLatestVersion = $false,
[bool] $BetaSearch = $false
)
if ($MacOSVersion -eq [version] "12.0") {
$MacOSName = "macOS Monterey"
} elseif ($MacOSVersion -eq [version] "13.0") {
$MacOSName = "macOS Ventura"
} elseif ($MacOSVersion -eq [version] "14.0") {
$MacOSName = "macOS Sonoma"
} else {
$MacOSName = $MacOSVersion.ToString()
}
Write-Host "`t[*] Finding available full installers"
if ($DownloadLatestVersion -eq $true) {
$targetBuild = Get-AvailableIPSWVersions -IsLatest $true -MacOSCodeNameOrVersion $MacOSName
Write-Host "`t[*] The 'DownloadLatestVersion' flag is set to true. Latest compatible macOS build of '$MacOSName' is '$targetBuild'"
} elseif ($BetaSearch -eq $true) {
$targetBuild = Get-AvailableIPSWVersions -IsBeta $true -MacOSCodeNameOrVersion $MacOSName
Write-Host "`t[*] The 'BetaSearch' flag is set to true. Latest compatible beta macOS build of '$MacOSName' is '$targetBuild'"
} else {
$targetBuild = Get-AvailableIPSWVersions -MacOSCodeNameOrVersion $MacOSName -IsLatest $false
Write-Host "`t[*] Available compatible macOS builds of '$MacOSName' are: $($targetBuild -join ', ')"
if ($targetBuild.Count -gt 1) {
Write-Error "`t[*] Please specify the exact build number of macOS you want to install"
exit 1
}
}
$installerPathPattern = "/Applications/Install ${macOSName}*.ipsw"
if (Test-Path $installerPathPattern) {
$previousInstallerPath = Get-Item -Path $installerPathPattern
Write-Host "`t[*] Removing '$previousInstallerPath' installation app before downloading the new one"
sudo rm -rf "$previousInstallerPath"
}
# Download macOS installer
$installerDir = "/Applications/"
$installerName = "Install ${macOSName}.ipsw"
Write-Host "`t[*] Requested macOS '$targetBuild' version installer found, fetching it from mist database"
Invoke-WithRetry { mist download firmware "$targetBuild" --output-directory $installerDir --firmware-name "$installerName" } { $LASTEXITCODE -eq 0 } | Out-Null
if (Test-Path "$installerDir$installerName") {
$result = "$installerDir$installerName"
} else {
Write-Error "`t[*] Requested macOS '$targetBuild' version installer failed to download"
exit 1
}
return $result
}
function Get-MacOSInstaller {
param (
[Parameter(Mandatory)]
[version] $MacOSVersion,
[bool] $DownloadLatestVersion = $false,
[bool] $BetaSearch = $false
)
# Enroll machine to DeveloperSeed if we need beta and unenroll otherwise
$seedutil = "/System/Library/PrivateFrameworks/Seeding.framework/Versions/Current/Resources/seedutil"
if ($BetaSearch) {
Write-Host "`t[*] Beta Version requested. Enrolling machine to DeveloperSeed"
sudo $seedutil enroll DeveloperSeed | Out-Null
} else {
Write-Host "`t[*] Resetting the seed before requesting stable versions"
sudo $seedutil unenroll | Out-Null
}
# Validate there is no software update at the moment
Test-SoftwareUpdate
# Validate availability OSVersion
Write-Host "`t[*] Finding available full installers"
$availableVersions = Get-AvailableVersions -IsBeta $BetaSearch
if ($DownloadLatestVersion) {
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
$filterSearch = "${shortMacOSVersion}."
$filteredVersions = $availableVersions.Where{ $_.OSVersion.StartsWith($filterSearch) }
if (-not $filteredVersions) {
Write-Host "`t[x] Failed to find any macOS versions using '$filterSearch' search condition"
Show-StringWithFormat $availableVersions
exit 1
}
Show-StringWithFormat $filteredVersions
$osVersions = $filteredVersions.OSVersion | Sort-Object { [version]$_ }
$MacOSVersion = $osVersions | Select-Object -Last 1
Write-Host "`t[*] The 'DownloadLatestVersion' flag is set. Latest macOS version is '$MacOSVersion' now"
}
$macOSName = $availableVersions.Where{ $MacOSVersion -eq $_.OSVersion }.OSName.Split(" ")[1]
if (-not $macOSName) {
Write-Host "`t[x] Requested macOS '$MacOSVersion' version not found in the list of available installers. Available versions are:`n$($availableVersions.OSVersion)"
Write-Host "`t[x] Make sure to pass '-BetaSearch `$true' if you need a beta version installer"
exit 1
}
# Clear LastRecommendedMajorOSBundleIdentifier to prevent error during fetching updates
# Install failed with error: Update not found
Update-SoftwareBundle
# Download macOS installer
Write-Host "`t[*] Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update"
Invoke-WithRetry -Command { sudo /usr/local/bin/mist download installer $MacOSVersion application --force --export installer.json --output-directory /Applications } -BreakCondition { $LASTEXITCODE -eq 0 } | Out-Null
if (-not(Test-Path installer.json -PathType leaf)) {
Write-Host "`t[x] Failed to fetch $MacOSVersion macOS"
exit 1
}
$installerPath = (Get-Content installer.json | Out-String | ConvertFrom-Json).options.applicationPath
if (-not $installerPath) {
Write-Host "`t[x] Path not found using '$installerPathPattern'"
exit 1
}
Write-Host "`t[*] Installer successfully downloaded to '$installerPath'"
$installerPath
}
function Get-ShortMacOSVersion {
param (
[Parameter(Mandatory)]
[version] $MacOSVersion
)
# Take Major.Minor version for macOS 10 (10.14 or 10.15) and Major for all further versions
$MacOSVersion.Major -eq 10 ? $MacOSVersion.ToString(2) : $MacOSVersion.ToString(1)
}
function Get-SoftwareUpdate {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName
)
$command = "/usr/sbin/softwareupdate --list"
$result = Invoke-SSHPassCommand -HostName $HostName -Command $command
$result | Where-Object { $_ -match "(Label|Title):" } | Out-String
}
function Get-SoftwareUpdateHistory {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName
)
$command = "/usr/sbin/softwareupdate --history"
Invoke-SSHPassCommand -HostName $HostName -Command $command | Out-String
}
function Install-SoftwareUpdate {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName,
[array] $listOfUpdates,
[string] $Password
)
# If an update is happening on macOS arm64 we will use the additional tool to install updates.
$osArch = $(arch)
if ($osArch -eq "arm64") {
Invoke-SoftwareUpdateArm64 -HostName $HostName -Password $Password -ListOfUpdates $listOfUpdates
} else {
foreach ($update in $listOfUpdates) {
$command = "sudo /usr/sbin/softwareupdate --restart --verbose --install '$($update.trim())'"
Invoke-SSHPassCommand -HostName $HostName -Command $command
}
}
}
function Invoke-SSHPassCommand {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Command,
[int] $ConnectTimeout = 10,
[int] $ConnectionAttempts = 10,
[int] $ServerAliveInterval = 30
)
$sshArg = @(
"sshpass"
"-e"
"ssh"
"-o UserKnownHostsFile=/dev/null"
"-o StrictHostKeyChecking=no"
"-o ConnectTimeout=$ConnectTimeout"
"-o ConnectionAttempts=$ConnectionAttempts"
"-o LogLevel=ERROR"
"-o ServerAliveInterval=$ServerAliveInterval"
"${env:SSHUSER}@${HostName}"
)
$sshPassOptions = $sshArg -join " "
if ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) {
$result = bash -c "$sshPassOptions \""$Command\"" 2>&1"
} else {
$result = bash -c "$sshPassOptions `"$Command`" 2>&1"
}
if ($LASTEXITCODE -ne 0) {
Write-Error "There is an error during command execution:`n$result"
exit 1
}
$result
}
function Invoke-WithRetry {
param(
[scriptblock] $Command,
[scriptblock] $BreakCondition,
[int] $RetryCount = 20,
[int] $Seconds = 60
)
while ($RetryCount -gt 0) {
try {
if ($Command) {
$result = & $Command
}
if (& $BreakCondition) {
return $result
}
} catch {
Write-Host "`t [!] Error during command execution: $_"
}
$RetryCount--
if ($RetryCount -eq 0) {
Write-Error "No more attempts left: $BreakCondition"
}
Write-Host "`t [/] Waiting $Seconds seconds before retrying. Retries left: $RetryCount"
Start-Sleep -Seconds $Seconds
}
$result
}
function Restart-VMSSH {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName
)
#
# https://unix.stackexchange.com/questions/58271/closing-connection-after-executing-reboot-using-ssh-command
#
$command = '(sleep 1 && sudo reboot &) && exit'
Invoke-SSHPassCommand -HostName $HostName -Command $command
}
function Show-StringWithFormat {
param(
[Parameter(ValuefromPipeline)]
[object] $string
)
process {
($string | Out-String).Trim().split("`n") | ForEach-Object { Write-Host "`t $_" }
}
}
function Remove-CurrentBetaSeed {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName
)
$command = "sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/Current/Resources/seedutil unenroll"
Invoke-SSHPassCommand -HostName $HostName -Command $command | Out-String
}
function Test-AutoLogon {
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $VMName,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $UserName
)
Invoke-WithRetry -BreakCondition {
# pwsh crashes if it invokes directly
# https://github.com/dotnet/runtime/issues/59059
$ankaUser = "" | bash -c "anka run $VMName /usr/bin/id -nu"
$UserName -eq $ankaUser
}
}
function Test-SoftwareUpdate {
param (
[string] $UpdateProcessName = "softwareupdate"
)
$command = {
$updateProcess = (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue).id
if ($updateProcess) {
# Workaround to get commandline param as it doesn't work for macOS atm https://github.com/PowerShell/PowerShell/issues/13943
$processName = /bin/ps -o command= $updateProcess
Write-Host "`t[*] Another software update process with '$updateProcess' id is in place with the following arguments '$processName'"
}
}
$condition = {
$null -eq (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue)
}
Invoke-WithRetry -Command $command -BreakCondition $condition
}
function Test-SSHPort {
param(
[Parameter(Mandatory)]
[ipaddress] $IPAddress,
[int] $Port = 22,
[int] $Timeout = 2000
)
Invoke-WithRetry -Command {$true} -BreakCondition {
try {
$client = [System.Net.Sockets.TcpClient]::new()
$client.ConnectAsync($IPAddress, $Port).Wait($Timeout)
}
catch {
$false
}
finally {
$client.Close()
}
}
}
function Wait-LoginWindow {
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $HostName,
[int] $RetryCount = 60,
[int] $Seconds = 60
)
$condition = {
$psCommand = "/bin/ps auxww"
$lw = "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow"
$ctk = "/System/Library/Frameworks/CryptoTokenKit.framework/ctkahp.bundle/Contents/MacOS/ctkahp"
$proc = Invoke-SSHPassCommand -HostName $HostName -Command $psCommand | Out-String
$proc.Contains($lw) -and $proc.Contains($ctk)
}
Invoke-WithRetry -RetryCount $RetryCount -Seconds $Seconds -BreakCondition $condition
}
function Update-SoftwareBundle {
$productVersion = sw_vers -productVersion
if ( $productVersion.StartsWith('11.') ) {
sudo rm -rf /Library/Preferences/com.apple.commerce.plist
sudo /usr/bin/defaults delete /Library/Preferences/com.apple.SoftwareUpdate.plist LastRecommendedMajorOSBundleIdentifier | Out-Null
}
}
+122
View File
@@ -0,0 +1,122 @@
param(
[Parameter(Mandatory=$true)]
[string]$PackerLogPath,
[string]$PrefixToPathTrim,
[int]$PrintTopNLongest = 25
)
$DateTimeRegex = "(\d+\/\d+\/\d+ \d+:\d+:\d+)"
$TelemetryLineRegex = "\[INFO\] \(telemetry\)"
$StartProvisionerRegex = "^${DateTimeRegex} ${TelemetryLineRegex} Starting provisioner (.+)$"
$EndProvisionerRegex = "^${DateTimeRegex} ${TelemetryLineRegex} ending (.+)$"
$ShellScriptSubItemRegex = "${DateTimeRegex} ui: ==> .+: Provisioning with \w+ script: (.+)"
$DownloadUploadSubItemRegex = "${DateTimeRegex} ui: ==> .+: (Downloading .+|Uploading .+)"
function Start-ProvisionerItem {
param([string]$ProvisionerType, [string]$StartTime)
return @{
ProvisionerType = $ProvisionerType
StartTime = [DateTime]::Parse($StartTime)
SubItems = @()
}
}
function End-ProvisionerItem {
param([object]$Provisioner, [string]$EndTime)
$Provisioner.EndTime = [DateTime]::Parse($EndTime)
$Provisioner.Duration = New-TimeSpan -Start $Provisioner.StartTime -End $Provisioner.EndTime
}
function Add-ProvisionerSubItem {
param([object]$Provisioner, [string]$Command, [string]$DateTime)
$lastItem = $Provisioner.SubItems | Select-Object -Last 1
if ($lastItem) {
$lastItem.EndTime = [DateTime]::Parse($DateTime)
$lastItem.Duration = New-TimeSpan -Start $lastItem.StartTime -End $lastItem.EndTime
}
if ($Command) {
if ($PrefixToPathTrim) { $Command = $Command.Replace($PrefixToPathTrim, ".") }
$Provisioner.SubItems += @{
Command = $Command
StartTime = [DateTime]::Parse($DateTime)
}
}
}
function Invoke-TryFindProvisionerSubItem {
param([object]$Provisioner, [string] $Line)
if ($Provisioner.ProvisionerType -in "powershell", "shell", "windows-shell") {
if ($Line -match $ShellScriptSubItemRegex) {
Add-ProvisionerSubItem -Provisioner $Provisioner -Command $Matches[2] -DateTime $Matches[1]
}
} elseif ($Provisioner.ProvisionerType -eq "file") {
if ($Line -match $DownloadUploadSubItemRegex) {
Add-ProvisionerSubItem -Provisioner $Provisioner -Command $Matches[2] -DateTime $Matches[1]
}
}
}
function Assert-StartProvisioner {
param([object]$Provisioner, [string]$ProvisionerType)
if ($null -ne $Provisioner) {
throw "New provisioner '$ProvisionerType' has been started but previous '$($Provisioner.ProvisionerType)' was not finished yet"
}
}
function Assert-EndProvisioner {
param([object]$Provisioner, [string]$ProvisionerType)
if (($null -ne $Provisioner) -and ($Provisioner.ProvisionerType -ne $ProvisionerType)) {
throw "Expected end of '$($Provisioner.ProvisionerType)' provisioner but found end of '$ProvisionerType'"
}
}
$provisionersList = @()
$currentProvisioner = $null
if ((Get-Content $PackerLogPath -Raw) -notmatch $TelemetryLineRegex) {
throw "Packer log doesn't contain diagnostic information. Env PACKER_LOG must be set to 1"
}
Get-Content $PackerLogPath | ForEach-Object {
if ($_ -match $StartProvisionerRegex) {
Assert-StartProvisioner -Provisioner $currentProvisioner -ProvisionerType $Matches[2]
$currentProvisioner = Start-ProvisionerItem -ProvisionerType $Matches[2] -StartTime $Matches[1]
} elseif (($_ -match $EndProvisionerRegex) -and $currentProvisioner) {
Assert-EndProvisioner -Provisioner $currentProvisioner -ProvisionerType $Matches[2]
End-ProvisionerItem -Provisioner $currentProvisioner -EndTime $Matches[1]
Add-ProvisionerSubItem -Provisioner $currentProvisioner -Command $null -DateTime $Matches[1]
$provisionersList += $currentProvisioner
$currentProvisioner = $null
} elseif ($currentProvisioner) {
Invoke-TryFindProvisionerSubItem -Provisioner $currentProvisioner -Line $_
}
}
$totalProvisionersTime = New-TimeSpan
$provisionersList | ForEach-Object { $totalProvisionersTime = $totalProvisionersTime.Add($_.Duration) }
# Print information about provisioners in order of execution
Write-Host "Build timeline:"
$provisionersList | ForEach-Object {
Write-Host "- $($_.Duration) | $($_.ProvisionerType)"
$_.SubItems | ForEach-Object {
Write-Host " $($_.Duration) | $($_.Command)"
}
Write-Host ""
}
Write-Host "Total provisioners time: $totalProvisionersTime"
if ($PrintTopNLongest -gt 0) {
Write-Host "`n`nTop longest provisioners:"
$provisionersList | ForEach-Object {
if ($_.SubItems.Length -gt 0) { $_.SubItems } else { @{ Command = $_.ProvisionerType; Duration = $_.Duration } }
} | Sort-Object { $_.Duration } | Select-Object -Last $PrintTopNLongest | ForEach-Object {
Write-Host "- $($_.Duration) | $($_.Command)"
}
}
+43
View File
@@ -0,0 +1,43 @@
$ErrorActionPreference = "Stop"
function Validate-Scripts {
Param (
[Parameter(Mandatory=$true)]
[string[]]$Path,
[Parameter(Mandatory=$true)]
[string]$ExpectedShebang
)
$ScriptWithoutShebangLine = @()
Get-ChildItem $path -Recurse -File -Filter "*.sh" | ForEach-Object {
$relativePath = Resolve-Path $_.FullName -Relative
$shebangLine = Get-Content -Path $_.FullName | Select-Object -First 1
if ($shebangLine -eq $ExpectedShebang) {
Write-Host "[+] '$relativePath'"
}
else {
Write-Host "[-] '$relativePath'"
$ScriptWithoutShebangLine += $relativePath
}
}
return $ScriptWithoutShebangLine
}
$PathUbuntu = "./images/ubuntu/scripts"
$PathMacOS = "./images/macos"
$PatternUbuntu = "#!/bin/bash -e"
$PatternMacOS = "#!/bin/bash -e -o pipefail"
$ScriptsWithBrokenShebang = @()
$ScriptsWithBrokenShebang += Validate-Scripts -Path $PathUbuntu -ExpectedShebang $PatternUbuntu
$ScriptsWithBrokenShebang += Validate-Scripts -Path $PathMacOS -ExpectedShebang $PatternMacOS
if ($ScriptsWithBrokenShebang.Length -gt 0) {
Write-Host "`n`n`n##[error] The following scripts have incorrect shebang:"
$ScriptsWithBrokenShebang | ForEach-Object {
Write-Host "##[error] '$_'"
}
Write-Host "`n`n##[error] Expected shebang for scripts in 'images/ubuntu' folder is '$PatternUbuntu'"
Write-Host "##[error] Expected shebang for scripts in 'images/macos' folder is '$PatternMacOS'"
exit 1
else {
Write-Host "All scripts have correct shebang."
}
}
+64
View File
@@ -0,0 +1,64 @@
import Foundation
import Security
let certInfo: CFDictionary
enum SecurityError: Error {
case generalError
}
func deleteCertificateFromKeyChain(_ certificateLabel: String) -> Bool {
let delQuery: [NSString: Any] = [
kSecClass: kSecClassCertificate,
kSecAttrLabel: certificateLabel,
]
let delStatus: OSStatus = SecItemDelete(delQuery as CFDictionary)
return delStatus == errSecSuccess
}
func saveCertificateToKeyChain(_ certificate: SecCertificate, certificateLabel: String) throws {
SecKeychainSetPreferenceDomain(SecPreferencesDomain.system)
deleteCertificateFromKeyChain(certificateLabel)
let setQuery: [NSString: AnyObject] = [
kSecClass: kSecClassCertificate,
kSecValueRef: certificate,
kSecAttrLabel: certificateLabel as AnyObject,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
]
let addStatus: OSStatus = SecItemAdd(setQuery as CFDictionary, nil)
guard addStatus == errSecSuccess else {
throw SecurityError.generalError
}
var status = SecTrustSettingsSetTrustSettings(certificate, SecTrustSettingsDomain.admin, nil)
}
func getCertificateFromString(stringData: String) throws -> SecCertificate {
if let data = NSData(base64Encoded: stringData, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) {
if let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, data) {
return certificate
}
}
throw SecurityError.generalError
}
if CommandLine.arguments.count > 1 {
let fileURL = URL(fileURLWithPath: CommandLine.arguments[1])
do {
let certData = try Data(contentsOf: fileURL)
let certificate = SecCertificateCreateWithData(nil, certData as CFData)
if certificate != nil {
print("Saving certificate")
try? saveCertificateToKeyChain(certificate!, certificateLabel: "Test")
} else {
print("Certificate can't be read")
}
} catch {
print("Unable to read the file \(CommandLine.arguments[1])")
}
} else {
print("Usage: \(CommandLine.arguments[0]) [cert.file]")
}
@@ -0,0 +1,7 @@
#! /usr/bin/expect -f
set timeout -1
spawn sudo /usr/sbin/softwareupdate --restart --verbose --install "MACOSUPDATE"
expect "Password*"
send "[lindex $argv 0]\r"
expect eof
+1
View File
@@ -0,0 +1 @@
[ -f $HOME/.bashrc ] && source $HOME/.bashrc
+33
View File
@@ -0,0 +1,33 @@
export LC_CTYPE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export ANDROID_HOME=${HOME}/Library/Android/sdk
export ANDROID_SDK_ROOT=${HOME}/Library/Android/sdk
export VM_ASSETS=/usr/local/opt/$USER/scripts
export NUNIT_BASE_PATH=/Library/Developer/nunit
export NUNIT3_PATH=/Library/Developer/nunit/3.6.0
export AGENT_TOOLSDIRECTORY=$HOME/hostedtoolcache
export RUNNER_TOOL_CACHE=$HOME/hostedtoolcache
export ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE=$HOME/actionarchivecache
export PATH=/Library/Frameworks/Mono.framework/Versions/Current/Commands:$PATH
export PATH=/Library/Frameworks/Python.framework/Versions/Current/bin:$PATH
export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH
export PATH=/usr/local/bin:/usr/local/sbin:~/bin:~/.yarn/bin:$PATH
export PATH="/usr/local/opt/curl/bin:$PATH"
export PATH=$HOME/.cargo/bin:$PATH
export RCT_NO_LAUNCH_PACKAGER=1
export DOTNET_ROOT=$HOME/.dotnet
export DOTNET_MULTILEVEL_LOOKUP=0
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_CASK_OPTS="--no-quarantine"
export BOOTSTRAP_HASKELL_NONINTERACTIVE=1
export BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=1
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
USERNAME="$1"
OLD_PASSWD="$2"
NEW_PASSWD="$3"
UPDATE_LOGIN_KEYCHAIN="${4:-true}"
sudo /usr/sbin/sysadminctl -resetPasswordFor $USERNAME -newPassword "$NEW_PASSWD" -adminUser $USERNAME -adminPassword "$OLD_PASSWD"
sudo /usr/bin/python3 /Users/$USERNAME/bootstrap/kcpassword.py "$NEW_PASSWD"
sudo /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "$USERNAME"
if [[ $UPDATE_LOGIN_KEYCHAIN == "true" ]]; then
/usr/bin/security set-keychain-password -o "$OLD_PASSWD" -p "$NEW_PASSWD" /Users/$USERNAME/Library/Keychains/login.keychain
fi
@@ -0,0 +1,45 @@
#!/bin/bash -e -o pipefail
BOOTSTRAP_PATH="$1"
ProvisionerPackageUri="$2"
ProvisionerScriptUri="$3"
ScriptName="$4"
ScriptParam="$5"
Username="$6"
arch=$(arch)
if [[ $arch == "arm64" ]]; then
export PATH=/usr/bin:/usr/sbin:/usr/local/bin:/bin:/sbin:/opt/homebrew/bin
else
export PATH=/usr/bin:/usr/sbin:/usr/local/bin:/bin:/sbin
fi
PROVISIONER_ROOT=/usr/local/opt/${Username}
sudo mkdir -p ${PROVISIONER_ROOT}
sudo chown ${Username} ${PROVISIONER_ROOT}
tee -a ${PROVISIONER_ROOT}/runprovisioner.sh > /dev/null <<\EOF
#!/bin/bash
. ${HOME}/.bashrc
/usr/local/opt/$USER/provisioner/provisioner
EOF
chmod +x $PROVISIONER_ROOT/runprovisioner.sh
aria2c \
--enable-color=false \
--file-allocation=none \
-d ${BOOTSTRAP_PATH} "${ProvisionerPackageUri}" >> ${BOOTSTRAP_PATH}/download.log
aria2c \
--enable-color=false \
--file-allocation=none \
-d ${BOOTSTRAP_PATH} "${ProvisionerScriptUri}" >> ${BOOTSTRAP_PATH}/download.log
chmod +x ${BOOTSTRAP_PATH}/${ScriptName}
# Install Provisioner with provided scripts
eval "$BOOTSTRAP_PATH/$ScriptName $BOOTSTRAP_PATH/$ScriptParam $Username" 2>&1 | tee "$BOOTSTRAP_PATH/install.log"
# State File
touch $BOOTSTRAP_PATH/provisionerDone
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
# Port of Gavin Brock's Perl kcpassword generator to Python, by Tom Taylor
# <tom@tomtaylor.co.uk>.
# Perl version: http://www.brock-family.org/gavin/perl/kcpassword.html
# This script was taken from https://github.com/timsutton/osx-vm-templates/blob/master/scripts/support/set_kcpassword.py
# Distributed by MIT license, license can be found at the bottom of this script
import sys
import os
def encode_data(passwd):
# The magic 11 bytes - these are just repeated
# 0x7D 0x89 0x52 0x23 0xD2 0xBC 0xDD 0xEA 0xA3 0xB9 0x1F
key = [125,137,82,35,210,188,221,234,163,185,31]
key_len = len(key)
passwd = [ord(x) for x in list(passwd)]
# pad passwd length out to an even multiple of key length
r = len(passwd) % key_len
if len(passwd) == 11:
passwd += [0]
elif (r > 0):
passwd = passwd + [0] * (key_len - r)
for n in range(0, len(passwd), len(key)):
ki = 0
for j in range(n, min(n+len(key), len(passwd))):
passwd[j] = passwd[j] ^ key[ki]
ki += 1
return bytearray(passwd)
if __name__ == "__main__":
runner_pwd_encoded = encode_data(sys.argv[1])
fd = os.open('/etc/kcpassword', os.O_WRONLY | os.O_CREAT, 0o600)
file = os.fdopen(fd, 'wb')
file.truncate(0)
file.write(runner_pwd_encoded)
file.close()
"""
The MIT License (MIT)
Copyright (c) 2013-2017 Timothy Sutton
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
@@ -0,0 +1,62 @@
#!/bin/bash -e -o pipefail
: <<-LICENSE_BLOCK
setAutoLogin (20210911) - Copyright (c) 2021 Joel Bruner (https://github.com/brunerd)
Licensed under the MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LICENSE_BLOCK
USERNAME="${1}"
PW="${2}"
kcpasswordEncode() {
#ascii string
local thisString="${1}"
local i
#macOS cipher hex ascii representation array
local cipherHex_array=( 7D 89 52 23 D2 BC DD EA A3 B9 1F )
#converted to hex representation with spaces
local thisStringHex_array=( $(echo -n "${thisString}" | xxd -p -u | sed 's/../& /g') )
#get padding by subtraction if under 11
local r=$(( ${#thisStringHex_array[@]} % 11 ))
local padding=0
if [ ${#thisStringHex_array[@]} -eq 11 ]; then
local padding=1
elif [ $r -gt 0 ]; then
local padding=$(( 11 - $r ))
fi
#cycle through each element of the array + padding
for ((i=0; i < $(( ${#thisStringHex_array[@]} + ${padding})); i++)); do
#use modulus to loop through the cipher array elements
local charHex_cipher=${cipherHex_array[$(( $i % 11 ))]}
#get the current hex representation element
local charHex=${thisStringHex_array[$i]}
#use $(( shell Aritmethic )) to ^ XOR the two 0x## values (extra padding is 0x00)
#take decimal value and printf convert to two char hex value
#use xxd to convert hex to actual value and append to the encodedString variable
local encodedString+=$(printf "%02X" "$(( 0x${charHex_cipher} ^ 0x${charHex:-00} ))")
done
#return the string without a newline
echo -n "${encodedString}"
}
#encode password and write file
kcpasswordEncode "${PW}" | xxd -r -p > /etc/kcpassword
#ensure ownership and permissions (600)
chown root:wheel /etc/kcpassword
chmod u=rw,go= /etc/kcpassword
#turn on auto login
/usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string "${USERNAME}"
/usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUserScreenLocked -bool false
echo "Auto login enabled for '${USERNAME}'"
@@ -0,0 +1,53 @@
#!/bin/bash -e -o pipefail
# Select any Xamarin SDK versions as the default ones independently by specifying only those versions that need to be changed.
# Examples:
# 1. Change all versions: $VM_ASSETS/select-xamarin-sdkv-v2.sh --mono=6.12 --ios=14.8 --android=10.2 --mac=7.8
# 2. Change default Mono and iOS only: $VM_ASSETS/select-xamarin-sdkv-v2.sh --mono=6.12 --ios=14.8
# 3. Change default Android only: $VM_ASSETS/select-xamarin-sdkv-v2.sh --android=11.1
get_framework_path() {
case $1 in
--mono) echo '/Library/Frameworks/Mono.framework/Versions' ;;
--ios) echo '/Library/Frameworks/Xamarin.iOS.framework/Versions' ;;
--android) echo '/Library/Frameworks/Xamarin.Android.framework/Versions' ;;
--mac) echo '/Library/Frameworks/Xamarin.Mac.framework/Versions' ;;
*) ;;
esac
}
change_framework_version() {
local framework=$1
local version=$2
echo "Select $framework $version"
local countDigit=$(echo "${version}" | grep -o "\." | grep -c "\.")
if [[ countDigit -gt 1 ]]; then
echo "[WARNING] It is not recommended to specify the exact framework version because your build can be broken with the next patch update. Consider using "major.minor" only format."
fi
local framework_path=$(get_framework_path "$framework")
if [ -d "${framework_path}/${version}" ]; then
sudo rm -f ${framework_path}/Current
sudo ln -s "${framework_path}/${version}" "${framework_path}/Current"
else
echo "Invalid framework version ${framework_path}/${version}"
exit 1
fi
}
for arg in "$@"; do
key=$(echo $arg | cut -f1 -d=)
value=$(echo $arg | cut -f2 -d=)
case $key in
--mono | --ios | --android | --mac) change_framework_version $key $value ;;
*)
echo "Invalid parameter <${key}>"
exit 1
;;
esac
done
@@ -0,0 +1,22 @@
#!/bin/bash -e -o pipefail
if [ -z "$1" ]; then
echo "No Xamarin SDK specified."
exit 0
fi
XAMARIN_SDK=$1
echo "Set Xamarin SDK to ${XAMARIN_SDK}"
FOLDERS_LIST=(
'/Library/Frameworks/Mono.framework/Versions'
'/Library/Frameworks/Xamarin.iOS.framework/Versions'
'/Library/Frameworks/Xamarin.Android.framework/Versions'
'/Library/Frameworks/Xamarin.Mac.framework/Versions'
)
for FOLDER in "${FOLDERS_LIST[@]}"
do
echo "Set Current folder for ${FOLDER}"
sudo rm -f ${FOLDER}/Current
sudo ln -s ${FOLDER}/${XAMARIN_SDK} ${FOLDER}/Current
done
+378
View File
@@ -0,0 +1,378 @@
| Announcements |
|-|
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 12
- OS Version: macOS 12.7.6 (21H1320)
- Kernel Version: Darwin 21.6.0
- Image Version: 20240926.174
## Installed Software
### Language and Runtime
- .NET Core SDK: 6.0.425, 7.0.102, 7.0.202, 7.0.306, 7.0.410, 8.0.101, 8.0.204, 8.0.303, 8.0.402
- Bash 3.2.57(1)-release
- Clang/LLVM 14.0.0
- Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Julia 1.10.5
- Kotlin 2.0.20-release-360
- Go 1.21.13
- Mono 6.12.0.188
- Node.js 18.20.4
- MSBuild 16.10.1.51301 (Mono 6.12.0.188)
- NVM 0.39.7
- NVM - Cached node versions: 16.20.2, 18.20.4, 20.17.0
- Perl 5.38.2
- PHP 8.3.11
- Python 2.7.18
- Python3 3.12.6
- R 4.4.1
- Ruby 3.0.7p220
### Package Management
- Bundler 2.5.20
- Carthage 0.40.0
- CocoaPods 1.15.2
- Composer 2.7.9
- Homebrew 4.3.24
- Miniconda 24.7.1
- NPM 10.7.0
- NuGet 6.3.1.1
- Pip 20.3.4 (python 2.7)
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.20
- Vcpkg 2024 (build from commit 3d8959985)
- Yarn 1.22.22
#### Environment variables
| Name | Value |
| ----------------------- | ---------------------- |
| CONDA | /usr/local/miniconda |
| VCPKG_INSTALLATION_ROOT | /usr/local/share/vcpkg |
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
- Sbt 1.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.1
- bazelisk 1.21.0
- bsdtar 3.5.1 - available by 'tar' alias
- Curl 8.10.1
- Git 2.46.2
- Git LFS 3.5.1
- GitHub CLI 2.57.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- ImageMagick 7.1.1-38
- jq 1.7.1
- mongo 5.0.29
- mongod 5.0.29
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.9.4
- pkg-config 0.29.2
- PostgreSQL 14.13 (Homebrew)
- psql (PostgreSQL) 14.13 (Homebrew)
- Sox 14.4.2
- Subversion (SVN) 1.14.3
- Switchaudio-osx 1.2.2
- Vagrant 2.4.1
- VirtualBox 6.1.38r153438
- yq 4.44.3
- zstd 1.5.6
### Tools
- App Center CLI 3.0.1
- AWS CLI 2.17.59
- AWS SAM CLI 1.124.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.64.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cabal 3.10.3.0
- Cmake 3.30.3
- CodeQL Action Bundle 2.19.0
- Colima 0.7.5
- Fastlane 2.222.0
- GHC 9.10.1
- GHCup 0.1.30.0
- Jazzy 0.15.2
- Stack 3.1.1
- SwiftFormat 0.54.5
- Swig 4.2.1
- Xcbeautify 1.6.0
- Xcode Command Line Tools 14.2.0.0.1.1668646533
- Xcodes 1.5.0
### Linters
- SwiftLint 0.53.0
- Yamllint 1.35.1
### Browsers
- Safari 17.6 (17618.3.11.11.7)
- SafariDriver 17.6 (17618.3.11.11.7)
- Google Chrome 129.0.6668.71
- Google Chrome for Testing 129.0.6668.70
- ChromeDriver 129.0.6668.70
- Microsoft Edge 129.0.2792.52
- Microsoft Edge WebDriver 129.0.2792.46
- Mozilla Firefox 130.0.1
- geckodriver 0.35.0
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | ------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 |
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
| GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin |
### Java
| Version | Environment Variable |
| --------------------- | -------------------- |
| 8.0.422+5.1 (default) | JAVA_HOME_8_X64 |
| 11.0.24+8 | JAVA_HOME_11_X64 |
| 17.0.12+7 | JAVA_HOME_17_X64 |
| 21.0.4+7.0 | JAVA_HOME_21_X64 |
### Cached Tools
#### PyPy
- 2.7.18 [PyPy 7.3.17]
- 3.7.13 [PyPy 7.3.9]
- 3.8.16 [PyPy 7.3.11]
- 3.9.19 [PyPy 7.3.16]
- 3.10.14 [PyPy 7.3.17]
#### Ruby
- 3.0.7
- 3.1.6
- 3.2.5
- 3.3.5
#### Python
- 3.7.17
- 3.8.18
- 3.9.20
- 3.10.15
- 3.11.9
- 3.12.6
#### Node.js
- 16.20.2
- 18.20.4
- 20.17.0
#### Go
- 1.20.14
- 1.21.13
- 1.22.7
- 1.23.1
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Bindgen 0.70.1
- Cargo-audit 0.20.1
- Cargo-outdated 0.15.0
- Cbindgen 0.27.0
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.3.0
- MarkdownPS: 1.10
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Web Servers
| Name | Version | ConfigFile | ServiceStatus | ListenPort |
| ----- | ------- | ------------------------------- | ------------- | ---------- |
| httpd | 2.4.62 | /usr/local/etc/httpd/httpd.conf | none | 80 |
| nginx | 1.27.1 | /usr/local/etc/nginx/nginx.conf | none | 80 |
### Xamarin
#### Visual Studio for Mac
| Version | Build | Path |
| -------------- | ----------- | ------------------------------------ |
| 2019 | 8.10.25.2 | /Applications/Visual Studio 2019.app |
| 2022 (default) | 17.6.14.413 | /Applications/Visual Studio.app |
##### Notes
```
To use Visual Studio 2019 by default rename the app:
mv "/Applications/Visual Studio.app" "/Applications/Visual Studio 2022.app"
mv "/Applications/Visual Studio 2019.app" "/Applications/Visual Studio.app"
```
#### Xamarin bundles
| symlink | Xamarin.Mono | Xamarin.iOS | Xamarin.Mac | Xamarin.Android |
| ----------------- | ------------ | ----------- | ----------- | --------------- |
| 6_12_25 | 6.12 | 16.4 | 9.3 | 13.2 |
| 6_12_24 | 6.12 | 16.2 | 9.1 | 13.2 |
| 6_12_23 | 6.12 | 16.2 | 9.1 | 13.1 |
| 6_12_22 | 6.12 | 16.1 | 9.0 | 13.1 |
| 6_12_21 (default) | 6.12 | 16.0 | 8.12 | 13.1 |
| 6_12_20 | 6.12 | 16.0 | 8.12 | 13.0 |
| 6_12_19 | 6.12 | 15.12 | 8.12 | 13.0 |
| 6_12_18 | 6.12 | 15.10 | 8.10 | 12.3 |
| 6_12_17 | 6.12 | 15.10 | 8.10 | 12.2 |
| 6_12_16 | 6.12 | 15.8 | 8.8 | 12.2 |
| 6_12_15 | 6.12 | 15.8 | 8.8 | 12.1 |
| 6_12_14 | 6.12 | 15.8 | 8.8 | 12.0 |
| 6_12_13 | 6.12 | 15.6 | 8.6 | 12.0 |
| 6_12_12 | 6.12 | 15.4 | 8.4 | 12.0 |
| 6_12_11 | 6.12 | 15.2 | 8.2 | 12.0 |
| 6_12_10 | 6.12 | 15.0 | 7.14 | 11.3 |
#### Unit Test Framework
- NUnit 3.6.1
### Xcode
| Version | Build | Path |
| -------------- | -------- | ------------------------------ |
| 14.2 (default) | 14C18 | /Applications/Xcode_14.2.app |
| 14.1 | 14B47b | /Applications/Xcode_14.1.app |
| 14.0.1 | 14A400 | /Applications/Xcode_14.0.1.app |
| 13.4.1 | 13F100 | /Applications/Xcode_13.4.1.app |
| 13.3.1 | 13E500a | /Applications/Xcode_13.3.1.app |
| 13.2.1 | 13C100 | /Applications/Xcode_13.2.1.app |
| 13.1 | 13A1030d | /Applications/Xcode_13.1.app |
#### Xcode Support Tools
- xcpretty 0.3.0
- xcversion 2.8.1
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ----------------------- | -------------------- | ---------------------- |
| macOS 12.0 | macosx12.0 | 13.1 |
| macOS 12.1 | macosx12.1 | 13.2.1 |
| macOS 12.3 | macosx12.3 | 13.3.1, 13.4.1, 14.0.1 |
| macOS 13.0 | macosx13.0 | 14.1 |
| macOS 13.1 | macosx13.1 | 14.2 |
| iOS 15.0 | iphoneos15.0 | 13.1 |
| iOS 15.2 | iphoneos15.2 | 13.2.1 |
| iOS 15.4 | iphoneos15.4 | 13.3.1 |
| iOS 15.5 | iphoneos15.5 | 13.4.1 |
| iOS 16.0 | iphoneos16.0 | 14.0.1 |
| iOS 16.1 | iphoneos16.1 | 14.1 |
| iOS 16.2 | iphoneos16.2 | 14.2 |
| Simulator - iOS 15.0 | iphonesimulator15.0 | 13.1 |
| Simulator - iOS 15.2 | iphonesimulator15.2 | 13.2.1 |
| Simulator - iOS 15.4 | iphonesimulator15.4 | 13.3.1 |
| Simulator - iOS 15.5 | iphonesimulator15.5 | 13.4.1 |
| Simulator - iOS 16.0 | iphonesimulator16.0 | 14.0.1 |
| Simulator - iOS 16.1 | iphonesimulator16.1 | 14.1 |
| Simulator - iOS 16.2 | iphonesimulator16.2 | 14.2 |
| tvOS 15.0 | appletvos15.0 | 13.1 |
| tvOS 15.2 | appletvos15.2 | 13.2.1 |
| tvOS 15.4 | appletvos15.4 | 13.3.1, 13.4.1 |
| tvOS 16.0 | appletvos16.0 | 14.0.1 |
| tvOS 16.1 | appletvos16.1 | 14.1, 14.2 |
| Simulator - tvOS 15.0 | appletvsimulator15.0 | 13.1 |
| Simulator - tvOS 15.2 | appletvsimulator15.2 | 13.2.1 |
| Simulator - tvOS 15.4 | appletvsimulator15.4 | 13.3.1, 13.4.1 |
| Simulator - tvOS 16.0 | appletvsimulator16.0 | 14.0.1 |
| Simulator - tvOS 16.1 | appletvsimulator16.1 | 14.1, 14.2 |
| watchOS 8.0 | watchos8.0 | 13.1 |
| watchOS 8.3 | watchos8.3 | 13.2.1 |
| watchOS 8.5 | watchos8.5 | 13.3.1, 13.4.1 |
| watchOS 9.0 | watchos9.0 | 14.0.1 |
| watchOS 9.1 | watchos9.1 | 14.1, 14.2 |
| Simulator - watchOS 8.0 | watchsimulator8.0 | 13.1 |
| Simulator - watchOS 8.3 | watchsimulator8.3 | 13.2.1 |
| Simulator - watchOS 8.5 | watchsimulator8.5 | 13.3.1, 13.4.1 |
| Simulator - watchOS 9.0 | watchsimulator9.0 | 14.0.1 |
| Simulator - watchOS 9.1 | watchsimulator9.1 | 14.1, 14.2 |
| DriverKit 21.0.1 | driverkit21.0.1 | 13.1 |
| DriverKit 21.2 | driverkit21.2 | 13.2.1 |
| DriverKit 21.4 | driverkit21.4 | 13.3.1, 13.4.1, 14.0.1 |
| DriverKit 22.1 | driverkit22.1 | 14.1 |
| DriverKit 22.2 | driverkit22.2 | 14.2 |
#### Installed Simulators
| OS | Simulators |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| iOS 15.0 | iPod touch (7th generation)<br>iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (9.7-inch) |
| iOS 15.2 | iPod touch (7th generation)<br>iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (9.7-inch) |
| iOS 15.4 | iPod touch (7th generation)<br>iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPhone SE (3rd generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (9.7-inch) |
| iOS 15.5 | iPod touch (7th generation)<br>iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPhone SE (3rd generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (9.7-inch) |
| iOS 16.0 | iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPhone SE (3rd generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (9.7-inch) |
| iOS 16.1 | iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro (9.7-inch) |
| iOS 16.2 | iPod touch (7th generation)<br>iPhone 11<br>iPhone 11 Pro<br>iPhone 11 Pro Max<br>iPhone 12<br>iPhone 12 mini<br>iPhone 12 Pro<br>iPhone 12 Pro Max<br>iPhone 13<br>iPhone 13 mini<br>iPhone 13 Pro<br>iPhone 13 Pro Max<br>iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 8<br>iPhone 8 Plus<br>iPhone SE (2nd generation)<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad (9th generation)<br>iPad Air (4th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (3rd generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (5th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro (9.7-inch) |
| tvOS 15.0 | Apple TV<br>Apple TV 4K (2nd generation)<br>Apple TV 4K (at 1080p) (2nd generation) |
| tvOS 15.2 | Apple TV<br>Apple TV 4K (2nd generation)<br>Apple TV 4K (at 1080p) (2nd generation) |
| tvOS 15.4 | Apple TV<br>Apple TV 4K (2nd generation)<br>Apple TV 4K (at 1080p) (2nd generation) |
| tvOS 16.0 | Apple TV<br>Apple TV 4K (2nd generation)<br>Apple TV 4K (at 1080p) (2nd generation) |
| tvOS 16.1 | Apple TV<br>Apple TV 4K (2nd generation)<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p)<br>Apple TV 4K (at 1080p) (2nd generation) |
| watchOS 8.0 | Apple Watch Series 5 - 40mm<br>Apple Watch Series 5 - 44mm<br>Apple Watch Series 6 - 40mm<br>Apple Watch Series 6 - 44mm<br>Apple Watch Series 7 - 41mm<br>Apple Watch Series 7 - 45mm |
| watchOS 8.3 | Apple Watch Series 5 - 40mm<br>Apple Watch Series 5 - 44mm<br>Apple Watch Series 6 - 40mm<br>Apple Watch Series 6 - 44mm<br>Apple Watch Series 7 - 41mm<br>Apple Watch Series 7 - 45mm |
| watchOS 8.5 | Apple Watch Series 5 - 40mm<br>Apple Watch Series 5 - 44mm<br>Apple Watch Series 6 - 40mm<br>Apple Watch Series 6 - 44mm<br>Apple Watch Series 7 - 41mm<br>Apple Watch Series 7 - 45mm |
| watchOS 9.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 9.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
### Android
| Package Name | Version |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Command Line Tools | 8.0 |
| Android Emulator | 35.1.21 |
| Android SDK Build-tools | 35.0.0<br>34.0.0<br>33.0.0 33.0.1 33.0.2 33.0.3<br>32.0.0<br>31.0.0 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-34 (rev 3)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1)<br>android-33 (rev 3)<br>android-32 (rev 1)<br>android-31 (rev 1) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.18.1<br>3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 24.0.8215888<br>25.2.9519653 (default)<br>26.3.11579264<br>27.1.12297006 |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/25.2.9519653 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/25.2.9519653 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/25.2.9519653 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
### Miscellaneous
- libXext 1.3.6
- libXft 2.3.8
- Tcl/Tk 8.6.15
- Zlib 1.3.1
#### Environment variables
| Name | Value |
| ----------------- | ----------------------------------------------------------------------------------------- |
| PARALLELS_DMG_URL | https://download.parallels.com/desktop/v20/20.0.1-55659/ParallelsDesktop-20.0.1-55659.dmg |
##### Notes
```
If you want to use Parallels Desktop you should download a package from URL stored in
PARALLELS_DMG_URL environment variable. A system extension is allowed for this version.
```
+279
View File
@@ -0,0 +1,279 @@
| Announcements |
|-|
| [[macOS] The macOS 12 Actions runner image will begin deprecation on 10/7/24 and will be fully unsupported by 12/3/24 for GitHub and ADO](https://github.com/actions/runner-images/issues/10721) |
| [[macOS] Support policy changes; Xcode 14 and 16 will be removed from macOS 14 on October 28](https://github.com/actions/runner-images/issues/10703) |
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 13
- OS Version: macOS 13.7 (22H123)
- Kernel Version: Darwin 22.6.0
- Image Version: 20241008.186
## Installed Software
### Language and Runtime
- .NET Core SDK: 7.0.102, 7.0.202, 7.0.306, 7.0.410, 8.0.101, 8.0.204, 8.0.303, 8.0.403
- Bash 3.2.57(1)-release
- Clang/LLVM 14.0.0
- Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Kotlin 2.0.20-release-360
- Mono 6.12.0.188
- Node.js 20.18.0
- Perl 5.38.2
- PHP 8.3.12
- Python3 3.12.7
- Ruby 3.0.7p220
### Package Management
- Bundler 2.5.21
- Carthage 0.40.0
- CocoaPods 1.15.2
- Composer 2.8.1
- Homebrew 4.4.0
- NPM 10.8.2
- NuGet 6.3.1.1
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.21
- Vcpkg 2024 (build from commit 2ddc10c8a)
- Yarn 1.22.22
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.2
- bazelisk 1.22.0
- bsdtar 3.5.3 - available by 'tar' alias
- Curl 8.10.1
- Git 2.47.0
- Git LFS 3.5.1
- GitHub CLI 2.58.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- jq 1.7.1
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.11.2
- pkg-config 0.29.2
- yq 4.44.3
- zstd 1.5.6
### Tools
- AWS CLI 2.18.1
- AWS SAM CLI 1.125.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.65.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cmake 3.30.4
- CodeQL Action Bundle 2.19.1
- Fastlane 2.224.0
- SwiftFormat 0.54.5
- Xcbeautify 2.11.0
- Xcode Command Line Tools 14.3.1.0.1.1683849156
- Xcodes 1.5.0
### Linters
- SwiftLint 0.57.0
### Browsers
- Safari 18.0.1 (18619.1.26.111.11)
- SafariDriver 18.0.1 (18619.1.26.111.11)
- Google Chrome 129.0.6668.90
- Google Chrome for Testing 129.0.6668.89
- ChromeDriver 129.0.6668.89
- Microsoft Edge 129.0.2792.79
- Microsoft Edge WebDriver 129.0.2792.82
- Mozilla Firefox 131.0
- geckodriver 0.35.0
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | ------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 |
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
| GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin |
### Java
| Version | Environment Variable |
| ------------------- | -------------------- |
| 8.0.422+5.1 | JAVA_HOME_8_X64 |
| 11.0.24+8 | JAVA_HOME_11_X64 |
| 17.0.12+7 (default) | JAVA_HOME_17_X64 |
| 21.0.4+7.0 | JAVA_HOME_21_X64 |
### Cached Tools
#### PyPy
- 2.7.18 [PyPy 7.3.17]
- 3.7.13 [PyPy 7.3.9]
- 3.8.16 [PyPy 7.3.11]
- 3.9.19 [PyPy 7.3.16]
- 3.10.14 [PyPy 7.3.17]
#### Ruby
- 3.0.7
- 3.1.6
- 3.2.5
- 3.3.5
#### Python
- 3.8.18
- 3.9.20
- 3.10.15
- 3.11.9
- 3.12.7
#### Node.js
- 16.20.2
- 18.20.4
- 20.18.0
#### Go
- 1.21.13
- 1.22.8
- 1.23.2
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.4.0
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Xcode
| Version | Build | Path |
| -------------- | ------- | ------------------------------ |
| 15.2 (default) | 15C500b | /Applications/Xcode_15.2.app |
| 15.1 | 15C65 | /Applications/Xcode_15.1.app |
| 15.0.1 | 15A507 | /Applications/Xcode_15.0.1.app |
| 14.3.1 | 14E300c | /Applications/Xcode_14.3.1.app |
| 14.2 | 14C18 | /Applications/Xcode_14.2.app |
| 14.1 | 14B47b | /Applications/Xcode_14.1.app |
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ------------------------------------------------------- | --------------------------------------------- | ------------- |
| macOS 13.0 | macosx13.0 | 14.1 |
| macOS 13.1 | macosx13.1 | 14.2 |
| macOS 13.3 | macosx13.3 | 14.3.1 |
| macOS 14.0 | macosx14.0 | 15.0.1 |
| macOS 14.2 | macosx14.2 | 15.1, 15.2 |
| iOS 16.1 | iphoneos16.1 | 14.1 |
| iOS 16.2 | iphoneos16.2 | 14.2 |
| iOS 16.4 | iphoneos16.4 | 14.3.1 |
| iOS 17.0 | iphoneos17.0 | 15.0.1 |
| iOS 17.2 | iphoneos17.2 | 15.1, 15.2 |
| Simulator - iOS 16.1 | iphonesimulator16.1 | 14.1 |
| Simulator - iOS 16.2 | iphonesimulator16.2 | 14.2 |
| Simulator - iOS 16.4 | iphonesimulator16.4 | 14.3.1 |
| Simulator - iOS 17.0 | iphonesimulator17.0 | 15.0.1 |
| Simulator - iOS 17.2 | iphonesimulator17.2 | 15.1, 15.2 |
| tvOS 16.1 | appletvos16.1 | 14.1, 14.2 |
| tvOS 16.4 | appletvos16.4 | 14.3.1 |
| tvOS 17.0 | appletvos17.0 | 15.0.1 |
| tvOS 17.2 | appletvos17.2 | 15.1, 15.2 |
| Simulator - tvOS 16.1 | appletvsimulator16.1 | 14.1, 14.2 |
| Simulator - tvOS 16.4 | appletvsimulator16.4 | 14.3.1 |
| Simulator - tvOS 17.0 | appletvsimulator17.0 | 15.0.1 |
| Simulator - tvOS 17.2 | appletvsimulator17.2 | 15.1, 15.2 |
| watchOS 9.1 | watchos9.1 | 14.1, 14.2 |
| watchOS 9.4 | watchos9.4 | 14.3.1 |
| watchOS 10.0 | watchos10.0 | 15.0.1 |
| watchOS 10.2 | watchos10.2 | 15.1, 15.2 |
| Simulator - watchOS 9.1 | watchsimulator9.1 | 14.1, 14.2 |
| Simulator - watchOS 9.4 | watchsimulator9.4 | 14.3.1 |
| Simulator - watchOS 10.0 | watchsimulator10.0 | 15.0.1 |
| Simulator - watchOS 10.2 | watchsimulator10.2 | 15.1, 15.2 |
| visionOS 1.0 | xros1.0 | 15.2 |
| Simulator - visionOS 1.0 | xrsimulator1.0 | 15.2 |
| Asset Runtime SDK for macOS hosts targeting watchOS 9.4 | assetruntime.host.macosx.target.watchos9.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting tvOS 16.4 | assetruntime.host.macosx.target.appletvos16.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting iOS 16.4 | assetruntime.host.macosx.target.iphoneos16.4 | 14.3.1 |
| DriverKit 22.1 | driverkit22.1 | 14.1 |
| DriverKit 22.2 | driverkit22.2 | 14.2 |
| DriverKit 22.4 | driverkit22.4 | 14.3.1 |
| DriverKit 23.0 | driverkit23.0 | 15.0.1 |
| DriverKit 23.2 | driverkit23.2 | 15.1, 15.2 |
#### Installed Simulators
| OS | Simulators |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS 16.1 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 16.2 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 16.4 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.0 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.2 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| tvOS 16.1 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 16.4 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.2 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| watchOS 9.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 9.4 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 10.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.2 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
### Android
| Package Name | Version |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Command Line Tools | 11.0 |
| Android Emulator | 35.2.10 |
| Android SDK Build-tools | 35.0.0<br>34.0.0<br>33.0.2 33.0.3 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-34 (rev 3)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1)<br>android-33 (rev 3) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 26.3.11579264 (default)<br>27.1.12297006 |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
### Miscellaneous
- Tcl/Tk 8.6.15
#### Environment variables
| Name | Value |
| ----------------- | ----------------------------------------------------------------------------------------- |
| PARALLELS_DMG_URL | https://download.parallels.com/desktop/v20/20.1.0-55732/ParallelsDesktop-20.1.0-55732.dmg |
##### Notes
```
If you want to use Parallels Desktop you should download a package from URL stored in
PARALLELS_DMG_URL environment variable. A system extension is allowed for this version.
```
+246
View File
@@ -0,0 +1,246 @@
| Announcements |
|-|
| [[macOS] The macOS 12 Actions runner image will begin deprecation on 10/7/24 and will be fully unsupported by 12/3/24 for GitHub and ADO](https://github.com/actions/runner-images/issues/10721) |
| [[macOS] Support policy changes; Xcode 14 and 16 will be removed from macOS 14 on October 28](https://github.com/actions/runner-images/issues/10703) |
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 13
- OS Version: macOS 13.7 (22H123)
- Kernel Version: Darwin 22.6.0
- Image Version: 20241008.189
## Installed Software
### Language and Runtime
- .NET Core SDK: 7.0.102, 7.0.202, 7.0.306, 7.0.410, 8.0.101, 8.0.204, 8.0.303, 8.0.403
- Bash 3.2.57(1)-release
- Clang/LLVM 14.0.0
- Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Kotlin 2.0.20-release-360
- Mono 6.12.0.188
- Node.js 20.18.0
- Perl 5.38.2
- Python3 3.12.7
- Ruby 3.0.7p220
### Package Management
- Bundler 2.5.21
- Carthage 0.40.0
- CocoaPods 1.15.2
- Homebrew 4.4.0
- NPM 10.8.2
- NuGet 6.3.1.1
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.21
- Yarn 1.22.22
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.2
- bazelisk 1.22.0
- bsdtar 3.5.3 - available by 'tar' alias
- Curl 8.7.1
- Git 2.47.0
- Git LFS 3.5.1
- GitHub CLI 2.58.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- jq 1.7.1
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.11.2
- pkg-config 0.29.2
- yq 4.44.3
- zstd 1.5.6
### Tools
- AWS CLI 2.18.1
- AWS SAM CLI 1.125.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.65.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cmake 3.30.4
- CodeQL Action Bundle 2.19.1
- Fastlane 2.224.0
- SwiftFormat 0.54.5
- Xcbeautify 2.11.0
- Xcode Command Line Tools 14.3.1.0.1.1683849156
- Xcodes 1.5.0
### Linters
### Browsers
- Safari 18.0.1 (18619.1.26.111.11)
- SafariDriver 18.0.1 (18619.1.26.111.11)
- Google Chrome 129.0.6668.90
- Google Chrome for Testing 129.0.6668.89
- ChromeDriver 129.0.6668.89
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | --------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-arm64 |
| EDGEWEBDRIVER | |
| GECKOWEBDRIVER | |
### Java
| Version | Environment Variable |
| ------------------- | -------------------- |
| 11.0.24+8 | JAVA_HOME_11_arm64 |
| 17.0.12+7 (default) | JAVA_HOME_17_arm64 |
| 21.0.4+7.0 | JAVA_HOME_21_arm64 |
### Cached Tools
#### Python
- 3.9.13
- 3.10.11
- 3.11.9
- 3.12.7
#### Node.js
- 16.20.1
- 18.20.4
- 20.18.0
#### Go
- 1.21.13
- 1.22.8
- 1.23.2
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.4.0
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Xcode
| Version | Build | Path |
| -------------- | ------- | ------------------------------ |
| 15.2 (default) | 15C500b | /Applications/Xcode_15.2.app |
| 15.1 | 15C65 | /Applications/Xcode_15.1.app |
| 15.0.1 | 15A507 | /Applications/Xcode_15.0.1.app |
| 14.3.1 | 14E300c | /Applications/Xcode_14.3.1.app |
| 14.2 | 14C18 | /Applications/Xcode_14.2.app |
| 14.1 | 14B47b | /Applications/Xcode_14.1.app |
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ------------------------------------------------------- | --------------------------------------------- | ------------- |
| macOS 13.0 | macosx13.0 | 14.1 |
| macOS 13.1 | macosx13.1 | 14.2 |
| macOS 13.3 | macosx13.3 | 14.3.1 |
| macOS 14.0 | macosx14.0 | 15.0.1 |
| macOS 14.2 | macosx14.2 | 15.1, 15.2 |
| iOS 16.1 | iphoneos16.1 | 14.1 |
| iOS 16.2 | iphoneos16.2 | 14.2 |
| iOS 16.4 | iphoneos16.4 | 14.3.1 |
| iOS 17.0 | iphoneos17.0 | 15.0.1 |
| iOS 17.2 | iphoneos17.2 | 15.1, 15.2 |
| Simulator - iOS 16.1 | iphonesimulator16.1 | 14.1 |
| Simulator - iOS 16.2 | iphonesimulator16.2 | 14.2 |
| Simulator - iOS 16.4 | iphonesimulator16.4 | 14.3.1 |
| Simulator - iOS 17.0 | iphonesimulator17.0 | 15.0.1 |
| Simulator - iOS 17.2 | iphonesimulator17.2 | 15.1, 15.2 |
| tvOS 16.1 | appletvos16.1 | 14.1, 14.2 |
| tvOS 16.4 | appletvos16.4 | 14.3.1 |
| tvOS 17.0 | appletvos17.0 | 15.0.1 |
| tvOS 17.2 | appletvos17.2 | 15.1, 15.2 |
| Simulator - tvOS 16.1 | appletvsimulator16.1 | 14.1, 14.2 |
| Simulator - tvOS 16.4 | appletvsimulator16.4 | 14.3.1 |
| Simulator - tvOS 17.0 | appletvsimulator17.0 | 15.0.1 |
| Simulator - tvOS 17.2 | appletvsimulator17.2 | 15.1, 15.2 |
| watchOS 9.1 | watchos9.1 | 14.1, 14.2 |
| watchOS 9.4 | watchos9.4 | 14.3.1 |
| watchOS 10.0 | watchos10.0 | 15.0.1 |
| watchOS 10.2 | watchos10.2 | 15.1, 15.2 |
| Simulator - watchOS 9.1 | watchsimulator9.1 | 14.1, 14.2 |
| Simulator - watchOS 9.4 | watchsimulator9.4 | 14.3.1 |
| Simulator - watchOS 10.0 | watchsimulator10.0 | 15.0.1 |
| Simulator - watchOS 10.2 | watchsimulator10.2 | 15.1, 15.2 |
| Simulator - visionOS 1.0 | xrsimulator1.0 | 15.2 |
| visionOS 1.0 | xros1.0 | 15.2 |
| Asset Runtime SDK for macOS hosts targeting watchOS 9.4 | assetruntime.host.macosx.target.watchos9.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting tvOS 16.4 | assetruntime.host.macosx.target.appletvos16.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting iOS 16.4 | assetruntime.host.macosx.target.iphoneos16.4 | 14.3.1 |
| DriverKit 22.1 | driverkit22.1 | 14.1 |
| DriverKit 22.2 | driverkit22.2 | 14.2 |
| DriverKit 22.4 | driverkit22.4 | 14.3.1 |
| DriverKit 23.0 | driverkit23.0 | 15.0.1 |
| DriverKit 23.2 | driverkit23.2 | 15.1, 15.2 |
#### Installed Simulators
| OS | Simulators |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS 16.1 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 16.2 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 16.4 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.0 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.2 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| tvOS 16.1 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 16.4 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.2 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| watchOS 9.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 9.4 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 10.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.2 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| visionOS 1.0 | Apple Vision Pro |
### Android
| Package Name | Version |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Command Line Tools | 11.0 |
| Android Emulator | 35.2.10 |
| Android SDK Build-tools | 35.0.0<br>34.0.0<br>33.0.2 33.0.3 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-34 (rev 3)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1)<br>android-33 (rev 3) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 26.3.11579264 (default)<br>27.1.12297006 |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
### Miscellaneous
- Tcl/Tk 8.6.15
+306
View File
@@ -0,0 +1,306 @@
| Announcements |
|-|
| [[macOS] The macOS 12 Actions runner image will begin deprecation on 10/7/24 and will be fully unsupported by 12/3/24 for GitHub and ADO](https://github.com/actions/runner-images/issues/10721) |
| [[macOS] Support policy changes; Xcode 14 and 16 will be removed from macOS 14 on October 28](https://github.com/actions/runner-images/issues/10703) |
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 14
- OS Version: macOS 14.7 (23H124)
- Kernel Version: Darwin 23.6.0
- Image Version: 20241007.165
## Installed Software
### Language and Runtime
- .NET Core SDK: 7.0.102, 7.0.202, 7.0.306, 7.0.410, 8.0.101, 8.0.204, 8.0.303, 8.0.402
- Bash 3.2.57(1)-release
- Clang/LLVM 14.0.3
- Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Kotlin 2.0.20-release-360
- Mono 6.12.0.188
- Node.js 20.18.0
- Perl 5.38.2
- PHP 8.3.12
- Python3 3.12.7
- Ruby 3.0.7p220
### Package Management
- Bundler 2.5.21
- Carthage 0.40.0
- CocoaPods 1.15.2
- Composer 2.8.1
- Homebrew 4.4.0
- NPM 10.8.2
- NuGet 6.3.1.1
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.21
- Yarn 1.22.22
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.2
- bazelisk 1.22.0
- bsdtar 3.5.3 - available by 'tar' alias
- Curl 8.10.1
- Git 2.46.2
- Git LFS 3.5.1
- GitHub CLI 2.58.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- jq 1.7.1
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.11.2
- pkg-config 0.29.2
- yq 4.44.3
- zstd 1.5.6
### Tools
- AWS CLI 2.18.0
- AWS SAM CLI 1.125.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.64.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cmake 3.30.4
- CodeQL Action Bundle 2.19.0
- Fastlane 2.224.0
- SwiftFormat 0.54.5
- Xcbeautify 2.11.0
- Xcode Command Line Tools 16.0.0.0.1.1724870825
- Xcodes 1.5.0
### Linters
- SwiftLint 0.57.0
### Browsers
- Safari 18.0.1 (19619.1.26.111.11)
- SafariDriver 18.0.1 (19619.1.26.111.11)
- Google Chrome 129.0.6668.90
- Google Chrome for Testing 129.0.6668.89
- ChromeDriver 129.0.6668.89
- Microsoft Edge 129.0.2792.79
- Microsoft Edge WebDriver 129.0.2792.82
- Mozilla Firefox 131.0
- geckodriver 0.35.0
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | ------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 |
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
| GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin |
### Java
| Version | Environment Variable |
| -------------------- | -------------------- |
| 8.0.422+5.1 | JAVA_HOME_8_X64 |
| 11.0.24+8 | JAVA_HOME_11_X64 |
| 17.0.12+7 | JAVA_HOME_17_X64 |
| 21.0.4+7.0 (default) | JAVA_HOME_21_X64 |
### Cached Tools
#### Ruby
- 3.0.7
- 3.1.6
- 3.2.5
- 3.3.5
#### Python
- 3.9.20
- 3.10.15
- 3.11.9
- 3.12.7
#### Node.js
- 18.20.4
- 20.17.0
#### Go
- 1.21.13
- 1.22.8
- 1.23.2
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.3.0
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Xcode
| Version | Build | Path |
| -------------- | -------- | ----------------------------------- |
| 16.1 (beta) | 16B5014f | /Applications/Xcode_16.1_beta_2.app |
| 16.0 | 16A242d | /Applications/Xcode_16.app |
| 15.4 (default) | 15F31d | /Applications/Xcode_15.4.app |
| 15.3 | 15E204a | /Applications/Xcode_15.3.app |
| 15.2 | 15C500b | /Applications/Xcode_15.2.app |
| 15.1 | 15C65 | /Applications/Xcode_15.1.app |
| 15.0.1 | 15A507 | /Applications/Xcode_15.0.1.app |
| 14.3.1 | 14E300c | /Applications/Xcode_14.3.1.app |
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ------------------------------------------------------- | --------------------------------------------- | ------------- |
| macOS 13.3 | macosx13.3 | 14.3.1 |
| macOS 14.0 | macosx14.0 | 15.0.1 |
| macOS 14.2 | macosx14.2 | 15.1, 15.2 |
| macOS 14.4 | macosx14.4 | 15.3 |
| macOS 14.5 | macosx14.5 | 15.4 |
| macOS 15.0 | macosx15.0 | 16.0 |
| macOS 15.1 | macosx15.1 | 16.1 |
| iOS 16.4 | iphoneos16.4 | 14.3.1 |
| iOS 17.0 | iphoneos17.0 | 15.0.1 |
| iOS 17.2 | iphoneos17.2 | 15.1, 15.2 |
| iOS 17.4 | iphoneos17.4 | 15.3 |
| iOS 17.5 | iphoneos17.5 | 15.4 |
| iOS 18.0 | iphoneos18.0 | 16.0 |
| iOS 18.1 | iphoneos18.1 | 16.1 |
| Simulator - iOS 16.4 | iphonesimulator16.4 | 14.3.1 |
| Simulator - iOS 17.0 | iphonesimulator17.0 | 15.0.1 |
| Simulator - iOS 17.2 | iphonesimulator17.2 | 15.1, 15.2 |
| Simulator - iOS 17.4 | iphonesimulator17.4 | 15.3 |
| Simulator - iOS 17.5 | iphonesimulator17.5 | 15.4 |
| Simulator - iOS 18.0 | iphonesimulator18.0 | 16.0 |
| Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 |
| tvOS 16.4 | appletvos16.4 | 14.3.1 |
| tvOS 17.0 | appletvos17.0 | 15.0.1 |
| tvOS 17.2 | appletvos17.2 | 15.1, 15.2 |
| tvOS 17.4 | appletvos17.4 | 15.3 |
| tvOS 17.5 | appletvos17.5 | 15.4 |
| tvOS 18.0 | appletvos18.0 | 16.0 |
| tvOS 18.1 | appletvos18.1 | 16.1 |
| Simulator - tvOS 16.4 | appletvsimulator16.4 | 14.3.1 |
| Simulator - tvOS 17.0 | appletvsimulator17.0 | 15.0.1 |
| Simulator - tvOS 17.2 | appletvsimulator17.2 | 15.1, 15.2 |
| Simulator - tvOS 17.4 | appletvsimulator17.4 | 15.3 |
| Simulator - tvOS 17.5 | appletvsimulator17.5 | 15.4 |
| Simulator - tvOS 18.0 | appletvsimulator18.0 | 16.0 |
| Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 |
| watchOS 9.4 | watchos9.4 | 14.3.1 |
| watchOS 10.0 | watchos10.0 | 15.0.1 |
| watchOS 10.2 | watchos10.2 | 15.1, 15.2 |
| watchOS 10.4 | watchos10.4 | 15.3 |
| watchOS 10.5 | watchos10.5 | 15.4 |
| watchOS 11.0 | watchos11.0 | 16.0 |
| watchOS 11.1 | watchos11.1 | 16.1 |
| Simulator - watchOS 9.4 | watchsimulator9.4 | 14.3.1 |
| Simulator - watchOS 10.0 | watchsimulator10.0 | 15.0.1 |
| Simulator - watchOS 10.2 | watchsimulator10.2 | 15.1, 15.2 |
| Simulator - watchOS 10.4 | watchsimulator10.4 | 15.3 |
| Simulator - watchOS 10.5 | watchsimulator10.5 | 15.4 |
| Simulator - watchOS 11.0 | watchsimulator11.0 | 16.0 |
| Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 |
| Simulator - visionOS 1.0 | xrsimulator1.0 | 15.2 |
| visionOS 1.0 | xros1.0 | 15.2 |
| visionOS 1.1 | xros1.1 | 15.3 |
| Simulator - visionOS 1.1 | xrsimulator1.1 | 15.3 |
| visionOS 1.2 | xros1.2 | 15.4 |
| Simulator - visionOS 1.2 | xrsimulator1.2 | 15.4 |
| Simulator - visionOS 2.0 | xrsimulator2.0 | 16.0 |
| visionOS 2.0 | xros2.0 | 16.0 |
| Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 |
| visionOS 2.1 | xros2.1 | 16.1 |
| Asset Runtime SDK for macOS hosts targeting watchOS 9.4 | assetruntime.host.macosx.target.watchos9.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting tvOS 16.4 | assetruntime.host.macosx.target.appletvos16.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting iOS 16.4 | assetruntime.host.macosx.target.iphoneos16.4 | 14.3.1 |
| DriverKit 22.4 | driverkit22.4 | 14.3.1 |
| DriverKit 23.0 | driverkit23.0 | 15.0.1 |
| DriverKit 23.2 | driverkit23.2 | 15.1, 15.2 |
| DriverKit 23.4 | driverkit23.4 | 15.3 |
| DriverKit 23.5 | driverkit23.5 | 15.4 |
| DriverKit 24.0 | driverkit24.0 | 16.0 |
| DriverKit 24.1 | driverkit24.1 | 16.1 |
#### Installed Simulators
| OS | Simulators |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS 16.4 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.0 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.2 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.4 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 17.5 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 18.0 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 18.1 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| tvOS 16.4 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.2 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.4 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.5 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 18.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 18.1 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| watchOS 9.4 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 10.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.2 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.4 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.5 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 11.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 11.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
### Android
| Package Name | Version |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Command Line Tools | 11.0 |
| Android Emulator | 35.2.10 |
| Android SDK Build-tools | 35.0.0<br>34.0.0<br>33.0.2 33.0.3 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-34 (rev 3)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1)<br>android-33 (rev 3) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 26.3.11579264 (default)<br>27.1.12297006 |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
### Miscellaneous
#### Environment variables
| Name | Value |
| ----------------- | ----------------------------------------------------------------------------------------- |
| PARALLELS_DMG_URL | https://download.parallels.com/desktop/v20/20.0.1-55659/ParallelsDesktop-20.0.1-55659.dmg |
##### Notes
```
If you want to use Parallels Desktop you should download a package from URL stored in
PARALLELS_DMG_URL environment variable. A system extension is allowed for this version.
```
+279
View File
@@ -0,0 +1,279 @@
| Announcements |
|-|
| [[macOS] The macOS 12 Actions runner image will begin deprecation on 10/7/24 and will be fully unsupported by 12/3/24 for GitHub and ADO](https://github.com/actions/runner-images/issues/10721) |
| [[macOS] Support policy changes; Xcode 14 and 16 will be removed from macOS 14 on October 28](https://github.com/actions/runner-images/issues/10703) |
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 14
- OS Version: macOS 14.7 (23H124)
- Kernel Version: Darwin 23.6.0
- Image Version: 20241007.259
## Installed Software
### Language and Runtime
- .NET Core SDK: 7.0.102, 7.0.202, 7.0.306, 7.0.410, 8.0.101, 8.0.204, 8.0.303, 8.0.402
- Bash 3.2.57(1)-release
- Clang/LLVM 14.0.3
- Clang/LLVM (Homebrew) 15.0.7 - available on `$(brew --prefix llvm@15)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Kotlin 2.0.20-release-360
- Mono 6.12.0.188
- Node.js 20.18.0
- Perl 5.38.2
- Python3 3.12.7
- Ruby 3.0.7p220
### Package Management
- Bundler 2.5.21
- Carthage 0.40.0
- CocoaPods 1.15.2
- Homebrew 4.4.0
- NPM 10.8.2
- NuGet 6.3.1.1
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.21
- Yarn 1.22.22
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.2
- bazelisk 1.22.0
- bsdtar 3.5.3 - available by 'tar' alias
- Curl 8.7.1
- Git 2.46.2
- Git LFS 3.5.1
- GitHub CLI 2.58.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- jq 1.7.1
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.11.2
- pkg-config 0.29.2
- yq 4.44.3
- zstd 1.5.6
### Tools
- AWS CLI 2.18.0
- AWS SAM CLI 1.125.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.64.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cmake 3.30.4
- CodeQL Action Bundle 2.19.0
- Fastlane 2.224.0
- SwiftFormat 0.54.5
- Xcbeautify 2.11.0
- Xcode Command Line Tools 16.0.0.0.1.1724870825
- Xcodes 1.5.0
### Linters
### Browsers
- Safari 18.0.1 (19619.1.26.111.11)
- SafariDriver 18.0.1 (19619.1.26.111.11)
- Google Chrome 129.0.6668.90
- Google Chrome for Testing 129.0.6668.89
- ChromeDriver 129.0.6668.89
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | --------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-arm64 |
| EDGEWEBDRIVER | |
| GECKOWEBDRIVER | |
### Java
| Version | Environment Variable |
| -------------------- | -------------------- |
| 11.0.24+8 | JAVA_HOME_11_arm64 |
| 17.0.12+7 | JAVA_HOME_17_arm64 |
| 21.0.4+7.0 (default) | JAVA_HOME_21_arm64 |
### Cached Tools
#### Python
- 3.9.13
- 3.10.11
- 3.11.9
- 3.12.7
#### Node.js
- 18.20.4
- 20.17.0
#### Go
- 1.21.13
- 1.22.8
- 1.23.2
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.3.0
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Xcode
| Version | Build | Path |
| -------------- | -------- | ----------------------------------- |
| 16.1 (beta) | 16B5014f | /Applications/Xcode_16.1_beta_2.app |
| 16.0 | 16A242d | /Applications/Xcode_16.app |
| 15.4 (default) | 15F31d | /Applications/Xcode_15.4.app |
| 15.3 | 15E204a | /Applications/Xcode_15.3.app |
| 15.2 | 15C500b | /Applications/Xcode_15.2.app |
| 15.1 | 15C65 | /Applications/Xcode_15.1.app |
| 15.0.1 | 15A507 | /Applications/Xcode_15.0.1.app |
| 14.3.1 | 14E300c | /Applications/Xcode_14.3.1.app |
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ------------------------------------------------------- | --------------------------------------------- | ------------- |
| macOS 13.3 | macosx13.3 | 14.3.1 |
| macOS 14.0 | macosx14.0 | 15.0.1 |
| macOS 14.2 | macosx14.2 | 15.1, 15.2 |
| macOS 14.4 | macosx14.4 | 15.3 |
| macOS 14.5 | macosx14.5 | 15.4 |
| macOS 15.0 | macosx15.0 | 16.0 |
| macOS 15.1 | macosx15.1 | 16.1 |
| iOS 16.4 | iphoneos16.4 | 14.3.1 |
| iOS 17.0 | iphoneos17.0 | 15.0.1 |
| iOS 17.2 | iphoneos17.2 | 15.1, 15.2 |
| iOS 17.4 | iphoneos17.4 | 15.3 |
| iOS 17.5 | iphoneos17.5 | 15.4 |
| iOS 18.0 | iphoneos18.0 | 16.0 |
| iOS 18.1 | iphoneos18.1 | 16.1 |
| Simulator - iOS 16.4 | iphonesimulator16.4 | 14.3.1 |
| Simulator - iOS 17.0 | iphonesimulator17.0 | 15.0.1 |
| Simulator - iOS 17.2 | iphonesimulator17.2 | 15.1, 15.2 |
| Simulator - iOS 17.4 | iphonesimulator17.4 | 15.3 |
| Simulator - iOS 17.5 | iphonesimulator17.5 | 15.4 |
| Simulator - iOS 18.0 | iphonesimulator18.0 | 16.0 |
| Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 |
| tvOS 16.4 | appletvos16.4 | 14.3.1 |
| tvOS 17.0 | appletvos17.0 | 15.0.1 |
| tvOS 17.2 | appletvos17.2 | 15.1, 15.2 |
| tvOS 17.4 | appletvos17.4 | 15.3 |
| tvOS 17.5 | appletvos17.5 | 15.4 |
| tvOS 18.0 | appletvos18.0 | 16.0 |
| tvOS 18.1 | appletvos18.1 | 16.1 |
| Simulator - tvOS 16.4 | appletvsimulator16.4 | 14.3.1 |
| Simulator - tvOS 17.0 | appletvsimulator17.0 | 15.0.1 |
| Simulator - tvOS 17.2 | appletvsimulator17.2 | 15.1, 15.2 |
| Simulator - tvOS 17.4 | appletvsimulator17.4 | 15.3 |
| Simulator - tvOS 17.5 | appletvsimulator17.5 | 15.4 |
| Simulator - tvOS 18.0 | appletvsimulator18.0 | 16.0 |
| Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 |
| watchOS 9.4 | watchos9.4 | 14.3.1 |
| watchOS 10.0 | watchos10.0 | 15.0.1 |
| watchOS 10.2 | watchos10.2 | 15.1, 15.2 |
| watchOS 10.4 | watchos10.4 | 15.3 |
| watchOS 10.5 | watchos10.5 | 15.4 |
| watchOS 11.0 | watchos11.0 | 16.0 |
| watchOS 11.1 | watchos11.1 | 16.1 |
| Simulator - watchOS 9.4 | watchsimulator9.4 | 14.3.1 |
| Simulator - watchOS 10.0 | watchsimulator10.0 | 15.0.1 |
| Simulator - watchOS 10.2 | watchsimulator10.2 | 15.1, 15.2 |
| Simulator - watchOS 10.4 | watchsimulator10.4 | 15.3 |
| Simulator - watchOS 10.5 | watchsimulator10.5 | 15.4 |
| Simulator - watchOS 11.0 | watchsimulator11.0 | 16.0 |
| Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 |
| visionOS 1.0 | xros1.0 | 15.2 |
| Simulator - visionOS 1.0 | xrsimulator1.0 | 15.2 |
| visionOS 1.1 | xros1.1 | 15.3 |
| Simulator - visionOS 1.1 | xrsimulator1.1 | 15.3 |
| Simulator - visionOS 1.2 | xrsimulator1.2 | 15.4 |
| visionOS 1.2 | xros1.2 | 15.4 |
| Simulator - visionOS 2.0 | xrsimulator2.0 | 16.0 |
| visionOS 2.0 | xros2.0 | 16.0 |
| visionOS 2.1 | xros2.1 | 16.1 |
| Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 |
| Asset Runtime SDK for macOS hosts targeting watchOS 9.4 | assetruntime.host.macosx.target.watchos9.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting tvOS 16.4 | assetruntime.host.macosx.target.appletvos16.4 | 14.3.1 |
| Asset Runtime SDK for macOS hosts targeting iOS 16.4 | assetruntime.host.macosx.target.iphoneos16.4 | 14.3.1 |
| DriverKit 22.4 | driverkit22.4 | 14.3.1 |
| DriverKit 23.0 | driverkit23.0 | 15.0.1 |
| DriverKit 23.2 | driverkit23.2 | 15.1, 15.2 |
| DriverKit 23.4 | driverkit23.4 | 15.3 |
| DriverKit 23.5 | driverkit23.5 | 15.4 |
| DriverKit 24.0 | driverkit24.0 | 16.0 |
| DriverKit 24.1 | driverkit24.1 | 16.1 |
#### Installed Simulators
| OS | Simulators |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS 16.4 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.0 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.2 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation) |
| iOS 17.4 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 17.5 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 18.0 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 18.1 | iPhone 14<br>iPhone 14 Plus<br>iPhone 14 Pro<br>iPhone 14 Pro Max<br>iPhone 15<br>iPhone 15 Plus<br>iPhone 15 Pro<br>iPhone 15 Pro Max<br>iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air (5th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro (11-inch) (4th generation)<br>iPad Pro (12.9-inch) (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| tvOS 16.4 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.2 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.4 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 17.5 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 18.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 18.1 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| watchOS 9.4 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Ultra (49mm) |
| watchOS 10.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.2 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.4 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 10.5 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 11.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 11.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Series 5 (40mm)<br>Apple Watch Series 5 (44mm)<br>Apple Watch Series 6 (40mm)<br>Apple Watch Series 6 (44mm)<br>Apple Watch Series 7 (41mm)<br>Apple Watch Series 7 (45mm)<br>Apple Watch Series 8 (41mm)<br>Apple Watch Series 8 (45mm)<br>Apple Watch Series 9 (41mm)<br>Apple Watch Series 9 (45mm)<br>Apple Watch Ultra (49mm)<br>Apple Watch Ultra 2 (49mm) |
### Android
| Package Name | Version |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Command Line Tools | 11.0 |
| Android Emulator | 35.2.10 |
| Android SDK Build-tools | 35.0.0<br>34.0.0<br>33.0.2 33.0.3 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-34 (rev 3)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1)<br>android-33 (rev 3) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 26.3.11579264 (default)<br>27.1.12297006 |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/26.3.11579264 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
+218
View File
@@ -0,0 +1,218 @@
| Announcements |
|-|
| [[macOS] The macOS 12 Actions runner image will begin deprecation on 10/7/24 and will be fully unsupported by 12/3/24 for GitHub and ADO](https://github.com/actions/runner-images/issues/10721) |
| [[macOS] Support policy changes; Xcode 14 and 16 will be removed from macOS 14 on October 28](https://github.com/actions/runner-images/issues/10703) |
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 15
- OS Version: macOS 15.0 (24A335)
- Kernel Version: Darwin 24.0.0
- Image Version: 20241007.173
## Installed Software
### Language and Runtime
- .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.402
- Bash 3.2.57(1)-release
- Clang/LLVM 16.0.0
- Clang/LLVM (Homebrew) 18.1.8 - available on `$(brew --prefix llvm@18)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Kotlin 2.0.20-release-360
- Node.js 22.9.0
- Perl 5.38.2
- PHP 8.3.12
- Python3 3.12.7
- Ruby 3.3.5
### Package Management
- Bundler 2.5.21
- Carthage 0.40.0
- CocoaPods 1.15.2
- Composer 2.8.1
- Homebrew 4.4.0
- NPM 10.8.3
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.21
- Yarn 1.22.22
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.2
- bazelisk 1.22.0
- bsdtar 3.5.3 - available by 'tar' alias
- Curl 8.10.1
- Git 2.46.2
- Git LFS 3.5.1
- GitHub CLI 2.58.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- jq 1.7.1
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.11.2
- pkg-config 0.29.2
- yq 4.44.3
- zstd 1.5.6
### Tools
- AWS CLI 2.18.0
- AWS SAM CLI 1.125.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.64.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cmake 3.30.4
- CodeQL Action Bundle 2.19.0
- Fastlane 2.224.0
- SwiftFormat 0.54.5
- Xcbeautify 2.11.0
- Xcode Command Line Tools 16.0.0.0.1.1724870825
- Xcodes 1.5.0
### Linters
- SwiftLint 0.57.0
### Browsers
- Safari 18.0 (20619.1.26.31.6)
- SafariDriver 18.0 (20619.1.26.31.6)
- Google Chrome 129.0.6668.90
- Google Chrome for Testing 129.0.6668.89
- ChromeDriver 129.0.6668.89
- Microsoft Edge 129.0.2792.79
- Microsoft Edge WebDriver 129.0.2792.82
- Mozilla Firefox 131.0
- geckodriver 0.35.0
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | ------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-x64 |
| EDGEWEBDRIVER | /usr/local/share/edge_driver |
| GECKOWEBDRIVER | /usr/local/opt/geckodriver/bin |
### Java
| Version | Environment Variable |
| -------------------- | -------------------- |
| 11.0.24+8 | JAVA_HOME_11_X64 |
| 17.0.12+7 | JAVA_HOME_17_X64 |
| 21.0.4+7.0 (default) | JAVA_HOME_21_X64 |
### Cached Tools
#### Ruby
- 3.1.6
- 3.2.5
#### Python
- 3.9.20
- 3.10.15
- 3.11.9
- 3.12.7
#### Node.js
- 18.20.4
- 20.17.0
#### Go
- 1.21.13
- 1.22.8
- 1.23.2
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.3.0
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Xcode
| Version | Build | Path |
| -------------- | -------- | ----------------------------------- |
| 16.1 (beta) | 16B5014f | /Applications/Xcode_16.1_beta_2.app |
| 16.0 (default) | 16A242d | /Applications/Xcode_16.app |
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ------------------------ | -------------------- | ------------- |
| macOS 15.0 | macosx15.0 | 16.0 |
| macOS 15.1 | macosx15.1 | 16.1 |
| iOS 18.0 | iphoneos18.0 | 16.0 |
| iOS 18.1 | iphoneos18.1 | 16.1 |
| Simulator - iOS 18.0 | iphonesimulator18.0 | 16.0 |
| Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 |
| tvOS 18.0 | appletvos18.0 | 16.0 |
| tvOS 18.1 | appletvos18.1 | 16.1 |
| Simulator - tvOS 18.0 | appletvsimulator18.0 | 16.0 |
| Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 |
| watchOS 11.0 | watchos11.0 | 16.0 |
| watchOS 11.1 | watchos11.1 | 16.1 |
| Simulator - watchOS 11.0 | watchsimulator11.0 | 16.0 |
| Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 |
| visionOS 2.0 | xros2.0 | 16.0 |
| Simulator - visionOS 2.0 | xrsimulator2.0 | 16.0 |
| Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 |
| visionOS 2.1 | xros2.1 | 16.1 |
| DriverKit 24.0 | driverkit24.0 | 16.0 |
| DriverKit 24.1 | driverkit24.1 | 16.1 |
#### Installed Simulators
| OS | Simulators |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS 18.0 | iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 18.1 | iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| tvOS 18.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 18.1 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| watchOS 11.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 11.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Ultra 2 (49mm) |
### Android
| Package Name | Version |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Emulator | 35.2.10 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 26.3.11579264<br>27.1.12297006 (default) |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
### Miscellaneous
+205
View File
@@ -0,0 +1,205 @@
| Announcements |
|-|
| [[macOS] The macOS 12 Actions runner image will begin deprecation on 10/7/24 and will be fully unsupported by 12/3/24 for GitHub and ADO](https://github.com/actions/runner-images/issues/10721) |
| [[macOS] Support policy changes; Xcode 14 and 16 will be removed from macOS 14 on October 28](https://github.com/actions/runner-images/issues/10703) |
| [macOS 15 (Sequoia) is now available as a public beta in GitHub Actions](https://github.com/actions/runner-images/issues/10686) |
| [[Macos 13 and 14] Android NDK versions <=25 will be removed from images on October 07,2024](https://github.com/actions/runner-images/issues/10614) |
| [[Macos 13 and 14] Go version 1.20.0 will be removed on October 07,2024.](https://github.com/actions/runner-images/issues/10612) |
***
# macOS 15
- OS Version: macOS 15.0 (24A335)
- Kernel Version: Darwin 24.0.0
- Image Version: 20241007.159
## Installed Software
### Language and Runtime
- .NET Core SDK: 8.0.101, 8.0.204, 8.0.303, 8.0.402
- Bash 3.2.57(1)-release
- Clang/LLVM 16.0.0
- Clang/LLVM (Homebrew) 18.1.8 - available on `$(brew --prefix llvm@18)/bin/clang`
- GCC 12 (Homebrew GCC 12.4.0) - available by `gcc-12` alias
- GCC 13 (Homebrew GCC 13.3.0) - available by `gcc-13` alias
- GCC 14 (Homebrew GCC 14.2.0) - available by `gcc-14` alias
- GNU Fortran 12 (Homebrew GCC 12.4.0) - available by `gfortran-12` alias
- GNU Fortran 13 (Homebrew GCC 13.3.0) - available by `gfortran-13` alias
- GNU Fortran 14 (Homebrew GCC 14.2.0) - available by `gfortran-14` alias
- Kotlin 2.0.20-release-360
- Node.js 22.9.0
- Perl 5.38.2
- Python3 3.12.7
- Ruby 3.3.5
### Package Management
- Bundler 2.5.21
- Carthage 0.40.0
- CocoaPods 1.15.2
- Homebrew 4.4.0
- NPM 10.8.3
- Pip3 24.2 (python 3.12)
- Pipx 1.7.1
- RubyGems 3.5.21
- Yarn 1.22.22
### Project Management
- Apache Ant 1.10.15
- Apache Maven 3.9.9
- Gradle 8.10.2
### Utilities
- 7-Zip 17.05
- aria2 1.37.0
- azcopy 10.26.0
- bazel 7.3.2
- bazelisk 1.22.0
- bsdtar 3.5.3 - available by 'tar' alias
- Curl 8.7.1
- Git 2.46.2
- Git LFS 3.5.1
- GitHub CLI 2.58.0
- GNU Tar 1.35 - available by 'gtar' alias
- GNU Wget 1.24.5
- gpg (GnuPG) 2.4.5
- jq 1.7.1
- OpenSSL 1.1.1w 11 Sep 2023
- Packer 1.11.2
- pkg-config 0.29.2
- yq 4.44.3
- zstd 1.5.6
### Tools
- AWS CLI 2.18.0
- AWS SAM CLI 1.125.0
- AWS Session Manager CLI 1.2.650.0
- Azure CLI 2.64.0
- Azure CLI (azure-devops) 1.0.1
- Bicep CLI 0.30.23
- Cmake 3.30.4
- CodeQL Action Bundle 2.19.0
- Fastlane 2.224.0
- SwiftFormat 0.54.5
- Xcbeautify 2.11.0
- Xcode Command Line Tools 16.0.0.0.1.1724870825
- Xcodes 1.5.0
### Linters
### Browsers
- Safari 18.0 (20619.1.26.31.6)
- SafariDriver 18.0 (20619.1.26.31.6)
- Google Chrome 129.0.6668.90
- Google Chrome for Testing 129.0.6668.89
- ChromeDriver 129.0.6668.89
- Selenium server 4.25.0
#### Environment variables
| Name | Value |
| --------------- | --------------------------------------- |
| CHROMEWEBDRIVER | /usr/local/share/chromedriver-mac-arm64 |
| EDGEWEBDRIVER | |
| GECKOWEBDRIVER | |
### Java
| Version | Environment Variable |
| -------------------- | -------------------- |
| 11.0.24+8 | JAVA_HOME_11_arm64 |
| 17.0.12+7 | JAVA_HOME_17_arm64 |
| 21.0.4+7.0 (default) | JAVA_HOME_21_arm64 |
### Cached Tools
#### Python
- 3.11.9
- 3.12.7
#### Node.js
- 18.20.4
- 20.17.0
#### Go
- 1.21.13
- 1.22.8
- 1.23.2
### Rust Tools
- Cargo 1.81.0
- Rust 1.81.0
- Rustdoc 1.81.0
- Rustup 1.27.1
#### Packages
- Clippy 0.1.81
- Rustfmt 1.7.1-stable
### PowerShell Tools
- PowerShell 7.4.5
#### PowerShell Modules
- Az: 12.3.0
- Pester: 5.6.1
- PSScriptAnalyzer: 1.22.0
### Xcode
| Version | Build | Path |
| -------------- | -------- | ----------------------------------- |
| 16.1 (beta) | 16B5014f | /Applications/Xcode_16.1_beta_2.app |
| 16.0 (default) | 16A242d | /Applications/Xcode_16.app |
#### Installed SDKs
| SDK | SDK Name | Xcode Version |
| ------------------------ | -------------------- | ------------- |
| macOS 15.0 | macosx15.0 | 16.0 |
| macOS 15.1 | macosx15.1 | 16.1 |
| iOS 18.0 | iphoneos18.0 | 16.0 |
| iOS 18.1 | iphoneos18.1 | 16.1 |
| Simulator - iOS 18.0 | iphonesimulator18.0 | 16.0 |
| Simulator - iOS 18.1 | iphonesimulator18.1 | 16.1 |
| tvOS 18.0 | appletvos18.0 | 16.0 |
| tvOS 18.1 | appletvos18.1 | 16.1 |
| Simulator - tvOS 18.0 | appletvsimulator18.0 | 16.0 |
| Simulator - tvOS 18.1 | appletvsimulator18.1 | 16.1 |
| watchOS 11.0 | watchos11.0 | 16.0 |
| watchOS 11.1 | watchos11.1 | 16.1 |
| Simulator - watchOS 11.0 | watchsimulator11.0 | 16.0 |
| Simulator - watchOS 11.1 | watchsimulator11.1 | 16.1 |
| visionOS 2.0 | xros2.0 | 16.0 |
| Simulator - visionOS 2.0 | xrsimulator2.0 | 16.0 |
| Simulator - visionOS 2.1 | xrsimulator2.1 | 16.1 |
| visionOS 2.1 | xros2.1 | 16.1 |
| DriverKit 24.0 | driverkit24.0 | 16.0 |
| DriverKit 24.1 | driverkit24.1 | 16.1 |
#### Installed Simulators
| OS | Simulators |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS 18.0 | iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| iOS 18.1 | iPhone 16<br>iPhone 16 Plus<br>iPhone 16 Pro<br>iPhone 16 Pro Max<br>iPhone SE (3rd generation)<br>iPad (10th generation)<br>iPad Air 11-inch (M2)<br>iPad Air 13-inch (M2)<br>iPad mini (6th generation)<br>iPad Pro 11-inch (M4)<br>iPad Pro 13-inch (M4) |
| tvOS 18.0 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| tvOS 18.1 | Apple TV<br>Apple TV 4K (3rd generation)<br>Apple TV 4K (3rd generation) (at 1080p) |
| watchOS 11.0 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Ultra 2 (49mm) |
| watchOS 11.1 | Apple Watch SE (40mm) (2nd generation)<br>Apple Watch SE (44mm) (2nd generation)<br>Apple Watch Series 10 (42mm)<br>Apple Watch Series 10 (46mm)<br>Apple Watch Ultra 2 (49mm) |
| visionOS 2.0 | Apple Vision Pro |
| visionOS 2.1 | Apple Vision Pro |
### Android
| Package Name | Version |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android Emulator | 35.2.10 |
| Android SDK Platforms | android-35 (rev 1)<br>android-34-ext8 (rev 1)<br>android-34-ext12 (rev 1)<br>android-34-ext11 (rev 1)<br>android-34-ext10 (rev 1)<br>android-33-ext5 (rev 1)<br>android-33-ext4 (rev 1) |
| Android SDK Platform-Tools | 35.0.2 |
| Android Support Repository | 47.0.0 |
| CMake | 3.22.1 |
| Google Play services | 49 |
| Google Repository | 58 |
| NDK | 26.3.11579264<br>27.1.12297006 (default) |
#### Environment variables
| Name | Value |
| ----------------------- | --------------------------------------------------- |
| ANDROID_HOME | /Users/runner/Library/Android/sdk |
| ANDROID_NDK | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_LATEST_HOME | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_NDK_ROOT | /Users/runner/Library/Android/sdk/ndk/27.1.12297006 |
| ANDROID_SDK_ROOT | /Users/runner/Library/Android/sdk |
@@ -0,0 +1,44 @@
################################################################################
## File: Configure-Toolset.ps1
## Team: CI-Build
## Desc: Configure toolset
################################################################################
Import-Module "~/image-generation/helpers/Common.Helpers.psm1"
function Get-ToolsetToolFullPath {
param
(
[Parameter(Mandatory)] [string] $ToolName,
[Parameter(Mandatory)] [string] $ToolVersion,
[Parameter(Mandatory)] [string] $ToolArchitecture
)
$toolPath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath $toolName
$toolPathVersion = Join-Path -Path $toolPath -ChildPath $toolVersion
$foundVersion = Get-Item $toolPathVersion | Sort-Object -Property {[version]$_.name} -Descending | Select-Object -First 1
$installationDir = Join-Path -Path $foundVersion -ChildPath $toolArchitecture
return $installationDir
}
$arch = Get-Architecture
$toolcache = (Get-ToolsetContent).toolcache
foreach ($tool in $toolcache) {
$toolName = $tool.name
$toolEnvironment = $tool.arch.$arch.variable_template
if (-not $toolEnvironment) {
continue
}
foreach ($toolVersion in $tool.arch.$arch.versions) {
Write-Host "Set $toolName $toolVersion environment variable..."
$toolPath = Get-ToolsetToolFullPath -ToolName $toolName -ToolVersion $toolVersion -ToolArchitecture $arch
$envName = $toolEnvironment -f $toolVersion.split(".")
# Add environment variable name=value
$envVar = "export {0}={1}" -f $envName, $toolPath
Add-Content -Path "${env:HOME}/.bashrc" -Value $envVar
}
}
@@ -0,0 +1,68 @@
################################################################################
## File: Configure-Xcode-Simulators.ps1
## Team: CI-Build
## Desc: CHeck and remove duplicate simulators
################################################################################
Import-Module "~/image-generation/helpers/Common.Helpers.psm1"
Import-Module "~/image-generation/helpers/Xcode.Helpers.psm1"
$arch = Get-Architecture
$xcodeVersions = (Get-ToolsetContent).xcode.${arch}.versions
# Switch to each Xcode version
foreach ($xcodeVersion in $xcodeVersions.link) {
write-host "Switching to Xcode $xcodeVersion"
Switch-Xcode -Version $XcodeVersion
# Make object of all simulators
$devicesList = $(xcrun simctl list -j devices | ConvertFrom-Json)
$devicesObject = [System.Collections.ArrayList]@()
foreach ($runtime in $devicesList.devices.psobject.Properties.name) {
foreach ($device in $devicesList.devices.$runtime) {
$devicesObject += [PSCustomObject]@{
runtime = $runtime
DeviceName = $($device.name)
DeviceId = $($device.udid)
DeviceCreationTime = (Get-Item $HOME/Library/Developer/CoreSimulator/Devices/$($device.udid)).CreationTime
}
}
}
# Remove duplicates
foreach ($simRuntume in $devicesObject.runtime | Sort-Object -Unique) {
[System.Collections.ArrayList]$sameRuntimeDevices = [array]$($devicesObject | Where-Object {$_.runtime -eq $simRuntume} | Sort-Object -Property DeviceName)
Write-Host "///////////////////////////////////////////////////////////////////"
Write-Host "// Checking for duplicates in $simRuntume "
$devicesAsHashTable = $sameRuntimeDevices | Group-Object -Property DeviceName -AsHashTable -AsString
foreach ($key in $devicesAsHashTable.Keys) {
if ( $devicesAsHashTable[$key].count -gt 1) {
Write-Host "// Duplicates for $key - $($devicesAsHashTable[$key].count)"
}
}
Write-Host "///////////////////////////////////////////////////////////////////"
for ($i = 0; $i -lt $sameRuntimeDevices.Count; $i++) {
if ( [string]::IsNullOrEmpty($($sameRuntimeDevices[$i+1].DeviceName)) ){
Write-Host "No more devices to compare in $simRuntume"
Write-Host "-------------------------------------------------------------------"
continue
}
Write-Host "$($sameRuntimeDevices[$i].DeviceName) - DeviceId $($sameRuntimeDevices[$i].DeviceId) comparing with"
Write-Host "$($sameRuntimeDevices[$i+1].DeviceName) - DeviceId $($sameRuntimeDevices[$i+1].DeviceId)"
Write-Host "-------------------------------------------------------------------"
if ($sameRuntimeDevices[$i].DeviceName -eq $sameRuntimeDevices[$i+1].DeviceName) {
write-host "*******************************************************************"
write-host "** Duplicate found"
if ($sameRuntimeDevices[$i].DeviceCreationTime -lt $sameRuntimeDevices[$i+1].DeviceCreationTime) {
Write-Host "** will be removed $($sameRuntimeDevices[$i+1].DeviceName) with id $($sameRuntimeDevices[$i+1].DeviceId)"
xcrun simctl delete $sameRuntimeDevices[$i+1].DeviceId
$sameRuntimeDevices.RemoveAt($i+1)
} else {
Write-Host "** will be removed $($sameRuntimeDevices[$i].DeviceName) with id $($sameRuntimeDevices[$i].DeviceId)"
xcrun simctl delete $sameRuntimeDevices[$i].DeviceId
$sameRuntimeDevices.RemoveAt($i)
}
write-host "*******************************************************************"
}
}
}
}
@@ -0,0 +1,57 @@
################################################################################
## File: Install-Toolset.ps1
## Team: CI-Build
## Desc: Install toolset
################################################################################
Import-Module "~/image-generation/tests/Helpers.psm1"
Import-Module "~/image-generation/helpers/Common.Helpers.psm1"
Function Install-Asset {
param(
[Parameter(Mandatory=$true)]
[object] $ReleaseAsset
)
Write-Host "Download $($ReleaseAsset.filename) archive..."
$assetArchivePath = Invoke-DownloadWithRetry $ReleaseAsset.download_url
Write-Host "Extract $($ReleaseAsset.filename) content..."
$assetFolderPath = Join-Path "/tmp" "$($ReleaseAsset.filename)-temp-dir"
New-Item -ItemType Directory -Path $assetFolderPath | Out-Null
tar -xzf $assetArchivePath -C $assetFolderPath
Write-Host "Invoke installation script..."
Push-Location -Path $assetFolderPath
Invoke-Expression "bash ./setup.sh"
Pop-Location
}
$arch = Get-Architecture
# Get toolcache content from toolset
$toolsToInstall = @("Python", "Node", "Go")
$tools = (Get-ToolsetContent).toolcache | Where-Object {$toolsToInstall -contains $_.Name}
foreach ($tool in $tools) {
# Get versions manifest for current tool
$assets = Invoke-RestMethod $tool.url -MaximumRetryCount 10 -RetryIntervalSec 30
# Get github release asset for each version
foreach ($version in $tool.arch.$arch.versions) {
$asset = $assets | Where-Object version -like $version `
| Select-Object -ExpandProperty files `
| Where-Object { ($_.platform -eq $tool.platform) -and ($_.arch -eq $arch)} `
| Select-Object -First 1
Write-Host "Installing $($tool.name) $version..."
if ($null -ne $asset) {
Install-Asset -ReleaseAsset $asset
} else {
Write-Host "Asset was not found in versions manifest"
exit 1
}
}
}
Invoke-PesterTests "Toolcache"
@@ -0,0 +1,71 @@
################################################################################
## File: Install-Xcode.ps1
## Desc: Install Xcode
################################################################################
$ErrorActionPreference = "Stop"
Import-Module "$env:HOME/image-generation/helpers/Common.Helpers.psm1"
Import-Module "$env:HOME/image-generation/helpers/Xcode.Installer.psm1" -DisableNameChecking
$arch = Get-Architecture
[Array]$xcodeVersions = (Get-ToolsetContent).xcode.$arch.versions
write-host $xcodeVersions
$defaultXcode = (Get-ToolsetContent).xcode.default
[Array]::Reverse($xcodeVersions)
$threadCount = "5"
Write-Host "Installing Xcode versions..."
$xcodeVersions | ForEach-Object -ThrottleLimit $threadCount -Parallel {
$ErrorActionPreference = "Stop"
Import-Module "$env:HOME/image-generation/helpers/Common.Helpers.psm1"
Import-Module "$env:HOME/image-generation/helpers/Xcode.Installer.psm1" -DisableNameChecking
Install-XcodeVersion -Version $_.version -LinkTo $_.link -Sha256Sum $_.sha256
Confirm-XcodeIntegrity -Version $_.link
}
$xcodeVersions | ForEach-Object {
Approve-XcodeLicense -Version $_.link
}
Write-Host "Configuring Xcode versions..."
$xcodeVersions | ForEach-Object {
Write-Host "Configuring Xcode $($_.link) ..."
Invoke-XcodeRunFirstLaunch -Version $_.link
if ($_.install_runtimes -eq 'true') {
# Additional simulator runtimes are included by default for Xcode < 14
Install-AdditionalSimulatorRuntimes -Version $_.link
}
ForEach($runtime in $_.runtimes) {
Write-Host "Installing Additional runtimes for Xcode '$runtime' ..."
$xcodebuildPath = Get-XcodeToolPath -Version $_.link -ToolName 'xcodebuild'
Invoke-ValidateCommand "sudo $xcodebuildPath -downloadPlatform $runtime" | Out-Null
}
}
Invoke-XcodeRunFirstLaunch -Version $defaultXcode
Write-Host "Configuring Xcode symlinks..."
$xcodeVersions | ForEach-Object {
Build-XcodeSymlinks -Version $_.link -Symlinks $_.symlinks
# Skip creating symlink to install multiple releases of the same Xcode version side-by-side
if ($_."skip-symlink" -ne "true") {
Build-ProvisionatorSymlink -Version $_.link
}
}
Write-Host "Rebuilding Launch Services database ..."
$xcodeVersions | ForEach-Object {
Initialize-XcodeLaunchServicesDb -Version $_.link
}
Write-Host "Setting default Xcode to $defaultXcode"
Switch-Xcode -Version $defaultXcode
New-Item -Path "/Applications/Xcode.app" -ItemType SymbolicLink -Value (Get-XcodeRootPath -Version $defaultXcode) | Out-Null
Write-Host "Setting environment variables 'XCODE_<VERSION>_DEVELOPER_DIR'"
Set-XcodeDeveloperDirEnvironmentVariables -XcodeList $xcodeVersions.link
@@ -0,0 +1,56 @@
################################################################################
## File: Update-XcodeSimulators.ps1
## Desc: Check available Xcode simulators and create missing ones
################################################################################
$ErrorActionPreference = "Stop"
Import-Module "$env:HOME/image-generation/helpers/Xcode.Helpers.psm1" -DisableNameChecking
Import-Module "$env:HOME/image-generation/software-report/SoftwareReport.Xcode.psm1" -DisableNameChecking
function Test-SimulatorInstalled {
param(
[Parameter(Mandatory)]
[string] $RuntimeId,
[Parameter(Mandatory)]
[string] $DeviceId,
[Parameter(Mandatory)]
[string] $SimulatorName,
[Parameter(Mandatory)]
[string] $XcodeVersion
)
$simctlPath = Get-XcodeToolPath -Version $XcodeVersion -ToolName "simctl"
if (-not (Test-Path $simctlPath)) {
Write-Host "Skip validating simulator '$SimulatorName [$RuntimeId]' because Xcode $XcodeVersion is not installed"
return
}
$simulatorFullNameDebug = "$SimulatorName [$RuntimeId]"
Write-Host "Checking Xcode simulator '$simulatorFullNameDebug' (Xcode $XcodeVersion)..."
# Get all available devices
[string]$rawDevicesInfo = Invoke-Expression "$simctlPath list devices --json"
$jsonDevicesInfo = ($rawDevicesInfo | ConvertFrom-Json).devices
# Checking if simulator already exists
$existingSimulator = $jsonDevicesInfo.$RuntimeId | Where-Object { $_.deviceTypeIdentifier -eq $DeviceId } | Select-Object -First 1
if ($null -eq $existingSimulator) {
Write-Host "Simulator '$simulatorFullNameDebug' is missed. Creating it..."
Invoke-Expression "$simctlPath create '$SimulatorName' '$DeviceId' '$RuntimeId'"
} elseif ($existingSimulator.name -ne $SimulatorName) {
Write-Host "Simulator '$simulatorFullNameDebug' is named incorrectly. Renaming it from '$($existingSimulator.name)' to '$SimulatorName'..."
Invoke-Expression "$simctlPath rename '$($existingSimulator.udid)' '$SimulatorName'"
} else {
Write-Host "Simulator '$simulatorFullNameDebug' is installed correctly."
}
}
# First run doesn't provide full data about devices
Get-XcodeInfoList | Out-Null
Write-Host "Validating and fixing Xcode simulators..."
Get-BrokenXcodeSimulatorsList | ForEach-Object {
Test-SimulatorInstalled -RuntimeId $_.RuntimeId -DeviceId $_.DeviceId -SimulatorName $_.SimulatorName -XcodeVersion $_.XcodeVersion
}
@@ -0,0 +1,11 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-auto-updates.sh
## Desc: Disabling automatic updates
################################################################################
sudo softwareupdate --schedule off
defaults write com.apple.SoftwareUpdate AutomaticDownload -int 0
defaults write com.apple.SoftwareUpdate CriticalUpdateInstall -int 0
defaults write com.apple.commerce AutoUpdate -bool false
defaults write com.apple.SoftwareUpdate AutomaticCheckEnabled -bool false
@@ -0,0 +1,20 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-autologin.sh
## Desc: add a Daemon to re-detect the attached network interfaces after vm is booted.
## Maintainer: @timsutton
## script was taken from https://github.com/timsutton/osx-vm-templates/blob/master/scripts/autologin.sh
################################################################################
echo "Enabling automatic GUI login for the '$USERNAME' user.."
python3 $HOME/bootstrap/kcpassword.py "$PASSWORD"
/usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "$USERNAME"
/usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUserScreenLocked -bool false
: '
The MIT License (MIT)
Copyright (c) 2013-2017 Timothy Sutton
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'
@@ -0,0 +1,32 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-hostname.sh
## Desc: Change the hostname at startup to prevent duplicates
## Hostname and Computername should contain .local in name to avoid name resolution issues
################################################################################
tee -a /usr/local/bin/change_hostname.sh > /dev/null <<\EOF
#!/bin/bash -e -o pipefail
name="Mac-$(python3 -c 'from time import time; print(int(round(time() * 1000)))')"
scutil --set HostName "${name}.local"
scutil --set LocalHostName $name
scutil --set ComputerName "${name}.local"
EOF
chmod +x /usr/local/bin/change_hostname.sh
sudo tee -a /Library/LaunchDaemons/change_hostname.plist > /dev/null <<\EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>change-hostname</string>
<key>Program</key>
<string>/usr/local/bin/change_hostname.sh</string>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
@@ -0,0 +1,99 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-machine.sh
## Desc: Configure guest OS settings
################################################################################
source ~/utils/utils.sh
echo "Enabling developer mode..."
sudo /usr/sbin/DevToolsSecurity --enable
# Turn off hibernation and get rid of the sleepimage
sudo pmset hibernatemode 0
sudo rm -f /var/vm/sleepimage
# Disable App Nap System Wide
defaults write NSGlobalDomain NSAppSleepDisabled -bool YES
# Disable Keyboard Setup Assistant window
sudo defaults write /Library/Preferences/com.apple.keyboardtype "keyboardtype" -dict-add "3-7582-0" -int 40
# Update VoiceOver Utility to allow VoiceOver to be controlled with AppleScript
# by creating a special Accessibility DB file (SIP must be disabled) and
# updating the user defaults system to reflect this change.
if csrutil status | grep -Eq "System Integrity Protection status: (disabled|unknown)"; then
sudo bash -c 'echo -n "a" > /private/var/db/Accessibility/.VoiceOverAppleScriptEnabled'
fi
defaults write com.apple.VoiceOver4/default SCREnableAppleScript -bool YES
# https://developer.apple.com/support/expiration/
# Enterprise iOS Distribution Certificates generated between February 7 and September 1st, 2020 will expire on February 7, 2023.
# Rotate the certificate before expiration to ensure your apps are installed and signed with an active certificate.
# Confirm that the correct intermediate certificate is installed by verifying the expiration date is set to 2030.
# sudo security delete-certificate -Z FF6797793A3CD798DC5B2ABEF56F73EDC9F83A64 /Library/Keychains/System.keychain
swiftc -suppress-warnings "${HOME}/image-generation/add-certificate.swift"
certs=(
AppleWWDRCAG3.cer
DeveloperIDG2CA.cer
)
for cert in ${certs[@]}; do
echo "Adding ${cert} certificate"
cert_path="${HOME}/${cert}"
curl -fsSL "https://www.apple.com/certificateauthority/${cert}" --output ${cert_path}
sudo ./add-certificate ${cert_path}
rm ${cert_path}
done
rm -f ./add-certificate
# enable-automationmode-without-authentication
brew install expect
retry=10
while [[ $retry -gt 0 ]]; do
{
/usr/bin/expect <<EOF
spawn automationmodetool enable-automationmode-without-authentication
expect "password"
send "${PASSWORD}\r"
expect {
"succeeded." { puts "Automation mode enabled successfully"; exit 0 }
eof
}
EOF
} && break
retry=$((retry-1))
if [[ $retry -eq 0 ]]; then
echo "No retry attempts left"
exit 1
fi
sleep 10
done
echo "Getting terminal windows"
launchctl_output=$(launchctl list | grep -i terminal || true)
if [ -n "$launchctl_output" ]; then
term_service=$(echo "$launchctl_output" | cut -f3)
echo "Close terminal windows: gui/501/${term_service}"
launchctl bootout gui/501/${term_service} && sleep 5
else
echo "No open terminal windows found."
fi
# test enable-automationmode-without-authentication
if [[ ! "$(automationmodetool)" =~ "DOES NOT REQUIRE" ]]; then
echo "Failed to enable enable-automationmode-without-authentication option"
exit 1
fi
# Create symlink for tests running
if [[ ! -d "/usr/local/bin" ]];then
sudo mkdir -p -m 775 /usr/local/bin
sudo chown $USER:admin /usr/local/bin
fi
chmod +x $HOME/utils/invoke-tests.sh
sudo ln -s $HOME/utils/invoke-tests.sh /usr/local/bin/invoke_tests
@@ -0,0 +1,44 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-max-files-limitation.sh
## Desc: Configure max files limitation
################################################################################
Launch_Daemons="/Library/LaunchDaemons"
# EOF in quotes to disable variable expansion
echo "Creating limit.maxfiles.plist"
cat > ${Launch_Daemons}/limit.maxfiles.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxfiles</string>
<key>Program</key>
<string>/Users/runner/limit-maxfiles.sh</string>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
</dict>
</plist>
EOF
# Creating script for applying workaround https://developer.apple.com/forums/thread/735798
cat > /Users/runner/limit-maxfiles.sh << EOF
#!/bin/bash
sudo launchctl limit maxfiles 256 unlimited
sudo launchctl limit maxfiles 65536 524288
EOF
echo "limit.maxfiles.sh permissions changing"
chmod +x /Users/runner/limit-maxfiles.sh
echo "limit.maxfiles.plist permissions changing"
chown root:wheel "${Launch_Daemons}/limit.maxfiles.plist"
chmod 0644 ${Launch_Daemons}/limit.maxfiles.plist
echo "Done, limit.maxfiles has been updated"
@@ -0,0 +1,39 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-network-interface-detection.sh
## Desc: add a Daemon to re-detect the attached network interfaces after vm is booted.
## Maintainer: @timsutton
## script was taken from https://github.com/timsutton/osx-vm-templates/blob/master/scripts/add-network-interface-detection.sh
################################################################################
PLIST=/Library/LaunchDaemons/sonoma.detectnewhardware.plist
cat <<EOF > ${PLIST}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>sonoma.detectnewhardware</string>
<key>ProgramArguments</key>
<array>
<string>/usr/sbin/networksetup</string>
<string>-detectnewhardware</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
# These should be already set as follows, but since they're required
# in order to load properly, we set them explicitly.
/bin/chmod 644 ${PLIST}
/usr/sbin/chown root:wheel ${PLIST}
: '
The MIT License (MIT)
Copyright (c) 2013-2017 Timothy Sutton
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'
@@ -0,0 +1,19 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-ntpconf.sh
## Desc: Configure NTP servers and set the timezone to UTC
################################################################################
echo Additional NTP servers adding into /etc/ntp.conf file...
cat > /etc/ntp.conf << EOF
server 0.pool.ntp.org
server 1.pool.ntp.org
server 2.pool.ntp.org
server 3.pool.ntp.org
server time.apple.com
server time.windows.com
EOF
# Set the timezone to UTC.
echo "The Timezone setting to UTC..."
ln -sf /usr/share/zoneinfo/UTC /etc/localtime
@@ -0,0 +1,40 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-preimagedata.sh
## Desc: Configure data used in the image
################################################################################
source ~/utils/utils.sh
arch=$(get_arch)
imagedata_file="$HOME/imagedata.json"
image_version=$(echo $IMAGE_VERSION | cut -d _ -f 2)
os_name=$(sw_vers -productName)
os_version=$(sw_vers -productVersion)
os_build=$(sw_vers -buildVersion)
label_version=$(echo $os_version | cut -d. -f1)
if [[ $arch == "arm64" ]]; then
image_label="macos-${label_version}-arm64"
else
image_label="macos-${label_version}"
fi
software_url="https://github.com/actions/runner-images/blob/${image_label}/${image_version}/images/macos/${image_label}-Readme.md"
releaseUrl="https://github.com/actions/runner-images/releases/tag/${image_label}%2F${image_version}"
cat <<EOF > $imagedata_file
[
{
"group": "Operating System",
"detail": "${os_name}\n${os_version}\n${os_build}"
},
{
"group": "Runner Image",
"detail": "Image: ${image_label}\nVersion: ${image_version}\nIncluded Software: ${software_url}\nImage Release: ${releaseUrl}"
}
]
EOF
echo "export ImageVersion=$image_version" >> $HOME/.bashrc
echo "export ImageOS=$IMAGE_OS" >> $HOME/.bashrc
@@ -0,0 +1,30 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-screensaver.sh
## Desc: Configure screensaver
################################################################################
# set screensaver idleTime to 0, to prevent turning screensaver on
macUUID=$(ioreg -rd1 -c IOPlatformExpertDevice | grep -i "UUID" | cut -c27-62)
rm -rf /Users/$USERNAME/Library/Preferences/com.apple.screensaver.$macUUID.plist
rm -rf /Users/$USERNAME/Library/Preferences/ByHost/com.apple.screensaver.$macUUID.plist
rm -rf /Users/$USERNAME/Library/Preferences/com.apple.screensaver.plist
rm -rf /Users/$USERNAME/Library/Preferences/ByHost/com.apple.screensaver.plist
defaults write /Users/$USERNAME/Library/Preferences/com.apple.screensaver.$macUUID.plist idleTime -string 0
defaults write /Users/$USERNAME/Library/Preferences/com.apple.screensaver.$macUUID.plist CleanExit "YES"
defaults write /Users/$USERNAME/Library/Preferences/ByHost/com.apple.screensaver.$macUUID.plist idleTime -string 0
defaults write /Users/$USERNAME/Library/Preferences/ByHost/com.apple.screensaver.$macUUID.plist CleanExit "YES"
defaults write /Users/$USERNAME/Library/Preferences/com.apple.screensaver.plist idleTime -string 0
defaults write /Users/$USERNAME/Library/Preferences/com.apple.screensaver.plist CleanExit "YES"
defaults write /Users/$USERNAME/Library/Preferences/ByHost/com.apple.screensaver.plist idleTime -string 0
defaults write /Users/$USERNAME/Library/Preferences/ByHost/com.apple.screensaver.plist CleanExit "YES"
chown -R $USERNAME:staff /Users/$USERNAME/Library/Preferences/ByHost/
chown -R $USERNAME:staff /Users/$USERNAME/Library/Preferences/
killall cfprefsd
# Set values to 0, to prevent sleep at all
pmset -a displaysleep 0 sleep 0 disksleep 0
@@ -0,0 +1,19 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-shell.sh
## Desc: Configure shell to use bash
################################################################################
source ~/utils/utils.sh
arch=$(get_arch)
echo "Changing shell to bash"
sudo chsh -s /bin/bash $USERNAME
sudo chsh -s /bin/bash root
# Check MacOS architecture and add HOMEBREW PATH to bashrc
if [[ $arch == "arm64" ]]; then
echo "Adding Homebrew environment to bash"
# Discussed here: https://github.com/Homebrew/brew/pull/18366
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.bashrc
fi
@@ -0,0 +1,11 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-ssh.sh
## Desc: Configure ssh
################################################################################
[[ ! -d ~/.ssh ]] && mkdir ~/.ssh 2>/dev/null
chmod 777 ~/.ssh
ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> ~/.ssh/known_hosts
ssh-keyscan -t rsa ssh.dev.azure.com >> ~/.ssh/known_hosts
@@ -0,0 +1,55 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-system.sh
## Desc: Post deployment system configuration actions
################################################################################
source ~/utils/utils.sh
# Close all finder windows because they can interfere with UI tests
close_finder_window
# Remove Parallels Desktop
# https://github.com/actions/runner-images/issues/6105
# https://github.com/actions/runner-images/issues/10143
if is_Monterey || is_SonomaX64 || is_VenturaX64; then
brew uninstall parallels
fi
# Put documentation to $HOME root
cp $HOME/image-generation/output/software-report/systeminfo.* $HOME/
# Put build vm assets (xamarin-selector) scripts to proper directory
if is_Monterey || is_Sonoma || is_Ventura; then
mkdir -p /usr/local/opt/$USER/scripts
mv $HOME/image-generation/assets/* /usr/local/opt/$USER/scripts
find /usr/local/opt/$USER/scripts -type f -name "*\.sh" -exec chmod +x {} \;
fi
# Remove fastlane cached cookie
rm -rf ~/.fastlane
# Clean up npm cache which collected during image-generation
# we have to do that here because `npm install` is run in a few different places during image-generation
npm cache clean --force
# Clean yarn cache
yarn cache clean
# Clean up temporary directories
sudo rm -rf ~/utils /tmp/*
# Erase all indexes and wait until the rebuilding process ends,
# for now there is no way to get status of indexing process, it takes around 3 minutes to accomplish
sudo mdutil -E /
sudo log stream | grep -q -E 'mds.*Released.*BackgroundTask' || true
echo "Indexing completed"
# delete symlink for tests running
sudo rm -f /usr/local/bin/invoke_tests
# Clean Homebrew downloads
sudo rm -rf /Users/$USER/Library/Caches/Homebrew/downloads/*
# Uninstall expect used in configure-machine.sh
brew uninstall expect
@@ -0,0 +1,75 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-tccdb-macos.sh
## Desc: Configure permissions to the TCC.db
################################################################################
source ~/utils/utils.sh
# /Library/Application\ Support/com.apple.TCC/TCC.db
systemValuesArray=(
"'kTCCServiceAccessibility','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321319"
"'kTCCServicePostEvent','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1566321326"
"'kTCCServiceSystemPolicyAllFiles','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
"'kTCCServiceAccessibility','com.apple.dt.Xcode-Helper',0,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1551941368"
"'kTCCServiceSystemPolicyAllFiles','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
"'kTCCServiceSystemPolicyAllFiles','/usr/libexec/sshd-keygen-wrapper',1,0,4,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'UNUSED',NULL,0,1639660695"
"'kTCCServiceSystemPolicyAllFiles','com.apple.Terminal',0,2,4,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'UNUSED',NULL,0,1678990068"
"'kTCCServiceAccessibility','/usr/libexec/sshd-keygen-wrapper',1,2,4,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'UNUSED',NULL,0,1644564233"
"'kTCCServiceAccessibility','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1591180502"
"'kTCCServiceAccessibility','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
"'kTCCServiceMicrophone','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576661342"
"'kTCCServiceScreenCapture','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1599831148"
"'kTCCServiceScreenCapture','com.devexpress.testcafe-browser-tools',0,2,3,1,X'fade0c0000000068000000010000000700000007000000080000001443fa4ca5141baeda21aeca1f50894673b440d4690000000800000014f8afcf6e69791b283e55bd0b03e39e422745770e0000000800000014bf4fc1aed64c871a49fc6bc9dd3878ce5d4d17c6',NULL,0,'UNUSED',NULL,0,1687952810"
"'kTCCServicePostEvent','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1644565949"
"'kTCCServiceScreenCapture','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159"
"'kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552"
"'kTCCServiceAccessibility','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,NULL,1592919552"
# Allow Full Disk Access for "Microsoft Defender for macOS" to bypass installation on-flight
"'kTCCServiceSystemPolicyAllFiles','com.microsoft.wdav',0,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,0,1643970979"
"'kTCCServiceSystemPolicyAllFiles','com.microsoft.wdav.epsext',0,2,4,1,NULL,NULL,NULL,'UNUSED',NULL,0,1643970979"
"'kTCCServiceSystemPolicyNetworkVolumes','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
"'kTCCServiceSystemPolicyNetworkVolumes','com.apple.Terminal',0,2,4,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'UNUSED',NULL,0,1678990068"
)
for values in "${systemValuesArray[@]}"; do
if is_Sonoma || is_Sequoia; then
# TCC access table in Sonoma has extra 4 columns: pid, pid_version, boot_uuid, last_reminded
configure_system_tccdb "$values,NULL,NULL,'UNUSED',${values##*,}"
else
configure_system_tccdb "$values"
fi
done
# $HOME/Library/Application\ Support/com.apple.TCC/TCC.db
userValuesArray=(
"'kTCCServiceUbiquity','com.apple.mail',0,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1551941469"
"'kTCCServiceUbiquity','com.apple.TextEdit',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465787445646974000000000003',NULL,NULL,'UNUSED',NULL,0,1566368356"
"'kTCCServiceUbiquity','com.apple.CloudDocs.MobileDocumentsFileProvider',0,2,0,1,X'fade0c000000004c0000000100000006000000020000002f636f6d2e6170706c652e436c6f7564446f63732e4d6f62696c65446f63756d656e747346696c6550726f76696465720000000003',NULL,NULL,'UNUSED',NULL,0,1570793290"
"'kTCCServiceAppleEvents','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1574241374"
"'kTCCServiceSystemPolicyAllFiles','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
"'kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,3,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1644564201"
"'kTCCServiceAppleEvents','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1591180478"
"'kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,0,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1591357685"
"'kTCCServiceAppleEvents','/bin/bash',1,2,0,1,NULL,NULL,0,'com.apple.systemevents',NULL,NULL,1591532620"
"'kTCCServiceAppleEvents','/bin/bash',1,2,0,1,NULL,NULL,0,'com.apple.finder',NULL,NULL,1592919552"
"'kTCCServiceMicrophone','com.apple.CoreSimulator.SimulatorTrampoline',0,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576347152"
"'kTCCServiceMicrophone','/usr/local/opt/runner/runprovisioner.sh',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,NULL,1576661342"
"'kTCCServiceUbiquity','/System/Library/PrivateFrameworks/PhotoLibraryServices.framework/Versions/A/Support/photolibraryd',1,2,5,1,NULL,NULL,NULL,'UNUSED',NULL,0,1619461750"
"'kTCCServiceUbiquity','com.apple.PassKitCore',0,2,5,1,NULL,NULL,NULL,'UNUSED',NULL,0,1619516250"
"'kTCCServiceAppleEvents','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1629294900"
"'kTCCServiceAppleEvents','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,3,1,NULL,NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,164456761"
"'kTCCServiceAppleEvents','/Library/Application Support/Veertu/Anka/addons/ankarund',1,2,3,1,NULL,NULL,0,'com.apple.Terminal',X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,1655808179"
"'kTCCServiceAppleEvents','/usr/libexec/sshd-keygen-wrapper',1,2,3,1,X'fade0c000000003c0000000100000006000000020000001d636f6d2e6170706c652e737368642d6b657967656e2d7772617070657200000000000003',NULL,0,'com.apple.Terminal',X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,1650386089"
"'kTCCServicePostEvent','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
"'kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552"
"'kTCCServiceScreenCapture','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159"
"'kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.systemevents',X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003',NULL,1592919552"
)
for values in "${userValuesArray[@]}"; do
if is_Sonoma || is_Sequoia; then
# TCC access table in Sonoma has extra 4 columns: pid, pid_version, boot_uuid, last_reminded
configure_user_tccdb "$values,NULL,NULL,'UNUSED',${values##*,}"
else
configure_user_tccdb "$values"
fi
done
@@ -0,0 +1,42 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-windows.sh
## Desc: Close open windows
################################################################################
source ~/utils/utils.sh
# Close System Preferences window because in Ventura arm64 it is opened by default on Apperance tab
if is_Arm64; then
echo "Close System Preferences window"
osascript -e 'tell application "System Preferences" to quit'
fi
retry=10
while [[ $retry -gt 0 ]]; do
openwindows=$(osascript -e 'tell application "System Events" to get every window of (every process whose class of windows contains window)') && break
retry=$((retry-1))
if [[ $retry -eq 0 ]]; then
echo "No retry attempts left"
exit 1
fi
sleep 30
done
IFS=',' read -r -a windowslist <<< "$openwindows"
if [[ -n ${openwindows} ]]; then
echo "Found opened window:"
fi
for key in ${!windowslist[@]}; do
if [[ ${windowslist[$key]} =~ "NotificationCenter" ]]; then
echo "[Warning] ${windowslist[$key]}"
else
echo " - ${windowslist[$key]}" | xargs
scripterror=true
fi
done
if [[ ${scripterror} = true ]]; then
exit 1
fi
@@ -0,0 +1,144 @@
#!/usr/bin/env ruby
################################################################################
## File: configure-xcode-simulators.rb
## Desc: List all simulators, find duplicate type and delete them.
## Maintainer: @vlas-voloshin
## script was taken from https://gist.github.com/vlas-voloshin/f9982128200345cd3fb7
################################################################################
class SimDevice
attr_accessor :runtime
attr_accessor :name
attr_accessor :identifier
attr_accessor :timestamp
def initialize(runtime, name, identifier, timestamp)
@runtime = runtime
@name = name
@identifier = identifier
@timestamp = timestamp
end
def to_s
return "#{@name} - #{@runtime} (#{@identifier}) [#{@timestamp}]"
end
def equivalent_to_device(device)
return @runtime == device.runtime && @name == device.name
end
end
# Executes a shell command and returns the result from stdout
def execute_simctl_command(command)
return %x[xcrun simctl #{command}]
end
# Retrieves the creation date/time of simulator with specified identifier
def simulator_creation_date(identifier)
directory = Dir.home() + "/Library/Developer/CoreSimulator/Devices/" + identifier
if (Dir.exists?(directory))
if (File::Stat.method_defined?(:birthtime))
return File.stat(directory).birthtime
else
return File.stat(directory).ctime
end
else
# Simulator directory is not yet created - treat it as if it was created right now (happens with new iOS 9 sims)
return Time.now
end
end
# Deletes specified simulator
def delete_device(device)
execute_simctl_command("delete #{device.identifier}")
end
puts("Searching for simulators...")
# Retrieve the list of existing simulators
devices = []
runtime = ""
execute_simctl_command("list devices").lines.each do |line|
case line[0]
when '='
# First header, skip it
when '-'
# Runtime header
runtime = line.scan(/-- (.+?) --/).flatten[0]
else
name_and_identifier = line.scan(/\s+(.+?) \(([\w\d]+-[\w\d]+-[\w\d-]+)\)/)[0]
name = name_and_identifier[0]
identifier = name_and_identifier[1]
timestamp = simulator_creation_date(identifier)
device = SimDevice.new(runtime, name, identifier, timestamp)
devices.push(device)
end
end
# Sort the simulators by their creation timestamp, ascending
devices = devices.sort { |a, b| a.timestamp <=> b.timestamp }
duplicates = {}
# Enumerate all devices except for the last one
for i in 0..devices.count-2
device = devices[i]
# Enumerate all devices *after* this one (created *later*)
for j in i+1..devices.count-1
potential_duplicate = devices[j]
if potential_duplicate.equivalent_to_device(device)
duplicates[potential_duplicate] = device
# Break out of the inner loop if a duplicate is found - if another duplicate exists,
# it will be found when this one is reached in the outer loop
break
end
end
end
if duplicates.count == 0
puts("You don't have duplicate simulators!")
exit()
end
puts("Looks like you have #{duplicates.count} duplicate simulator#{duplicates.count > 1 ? "s" : ""}:")
duplicates.each_pair do |duplicate, original|
puts
puts("#{duplicate}")
puts("--- duplicate of ---")
puts("#{original}")
end
puts
puts("Each duplicate was determined as the one created later than the 'original'.")
puts("Deleting...")
duplicates.each_key do |duplicate|
delete_device(duplicate)
end
puts("Done!")
=begin
MIT License
Copyright (c) 2015-2019 Vlas Voloshin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=end
@@ -0,0 +1,34 @@
#!/bin/bash -e -o pipefail
################################################################################
## File: configure-xcode.sh
## Desc: Configure Xcode after installation
################################################################################
source ~/utils/utils.sh
XCODE_LIST=($(get_toolset_value '.xcode.versions | reverse | .[].link'))
DEFAULT_XCODE_VERSION=$(get_toolset_value '.xcode.default')
# https://github.com/microsoft/appcenter/issues/847
# Assets.xcassets : error : CoreData: error: (6922) I/O error for database
# at $HOME/Library/Developer/Xcode/UserData/IB Support/Simulator Devices/{GUID}
echo "Erase a device's contents and settings:"
for XCODE_VERSION in ${XCODE_LIST[@]}; do
echo " Xcode Version: ${XCODE_VERSION}"
launchctl remove com.apple.CoreSimulator.CoreSimulatorService || true
#add sleep to let CoreSimulatorService to exit
sleep 3
# Select xcode version by default
sudo xcode-select -s /Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer
# Erase a device's contents and settings
xcrun simctl erase all
#add sleep due to sometimes "xcrun simctl list" takes more than a few moments and script fails when trying to remove CoreSimulatorSerivce
sleep 10
done
# Select xcode version by default
echo "Setting Xcode ${DEFAULT_XCODE_VERSION} as default"
sudo xcode-select -s /Applications/Xcode_${DEFAULT_XCODE_VERSION}.app/Contents/Developer

Some files were not shown because too many files have changed in this diff Show More