Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7114759dc4 | |||
| 33e216db1d | |||
| fcf3d0ac96 |
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* @actions/runner-images-team
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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!
|
||||||
@@ -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!
|
||||||
@@ -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
|
||||||
@@ -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 }}
|
||||||
@@ -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']
|
||||||
|
})
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
})
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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 }}
|
||||||
|
});
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"streetsidesoftware.code-spell-checker",
|
||||||
|
"hashicorp.hcl",
|
||||||
|
"davidanson.vscode-markdownlint",
|
||||||
|
"ms-vscode.powershell",
|
||||||
|
"timonwong.shellcheck"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+25
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+42
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
@@ -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] |  |
|
||||||
|
| Ubuntu 22.04 | `ubuntu-22.04` | [ubuntu-22.04] |  |
|
||||||
|
| Ubuntu 20.04 | `ubuntu-20.04` | [ubuntu-20.04] |  |
|
||||||
|
| macOS 15 <sup>beta</sup> | `macos-15-large`| [macOS-15] |  |
|
||||||
|
| macOS 15 Arm64 <sup>beta</sup> | `macos-15` or `macos-15-xlarge` | [macOS-15-arm64] |  |
|
||||||
|
| macOS 14 | `macos-latest-large` or `macos-14-large`| [macOS-14] |  |
|
||||||
|
| macOS 14 Arm64 |`macos-latest`, `macos-14`, `macos-latest-xlarge` or `macos-14-xlarge`| [macOS-14-arm64] |  |
|
||||||
|
| macOS 13 | `macos-13` or `macos-13-large` | [macOS-13] |  |
|
||||||
|
| macOS 13 Arm64 | `macos-13-xlarge` | [macOS-13-arm64] |  |
|
||||||
|
| macOS 12 <sup>deprecated</sup> | `macos-12` or `macos-12-large`| [macOS-12] |  |
|
||||||
|
| Windows Server 2022 | `windows-latest` or `windows-2022` | [windows-2022] |  |
|
||||||
|
| Windows Server 2019 | `windows-2019` | [windows-2019] |  |
|
||||||
|
|
||||||
|
### 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>
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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}"
|
||||||
|
}
|
||||||
@@ -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?
|
||||||
|
|
||||||
|
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
}
|
||||||
+603
@@ -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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"tool": "Credential Scanner",
|
||||||
|
"suppressions": [
|
||||||
|
{
|
||||||
|
"placeholder": "P@ssword!!",
|
||||||
|
"_justification": "Password used by SQL Express. It is required to interact with database."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)"
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[ -f $HOME/.bashrc ] && source $HOME/.bashrc
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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.
|
||||||
|
```
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
```
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
```
|
||||||
|
|
||||||
@@ -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 |
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
Reference in New Issue
Block a user