Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b34a2727e |
+2
-1
@@ -1 +1,2 @@
|
||||
* text=auto eol=lf
|
||||
# Do not normalize line endings
|
||||
* -text
|
||||
@@ -1 +0,0 @@
|
||||
* @actions/runner-images-team
|
||||
@@ -1,57 +0,0 @@
|
||||
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 22.04
|
||||
- label: Ubuntu 24.04
|
||||
- label: Ubuntu Slim
|
||||
- label: macOS 13
|
||||
- label: macOS 13 Arm64
|
||||
- label: macOS 14
|
||||
- label: macOS 14 Arm64
|
||||
- label: macOS 15
|
||||
- label: macOS 15 Arm64
|
||||
- label: macOS 26 Arm64
|
||||
- label: Windows Server 2019
|
||||
- label: Windows Server 2022
|
||||
- label: Windows Server 2025
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Mitigation ways
|
||||
description: Steps or options for impact mitigation
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Submit a bug report
|
||||
title: ''
|
||||
labels: t:Bug report
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is, and why you consider it to be a bug.
|
||||
|
||||
**Area for Triage**:
|
||||
<!-- See https://github.com/actions/virtual-environments/tree/master/triage-rules.yml for areas -->
|
||||
|
||||
**Question, Bug, or Feature?**:
|
||||
<!-- Choose "Question", "Bug", or "Feature" -->
|
||||
|
||||
**Virtual environments affected**
|
||||
- [ ] macOS 10.15
|
||||
- [ ] Ubuntu 16.04 LTS
|
||||
- [ ] Ubuntu 18.04 LTS
|
||||
- [ ] Windows Server 2016 R2
|
||||
- [ ] Windows Server 2019
|
||||
|
||||
**Expected behavior**
|
||||
A description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A description with steps to reproduce the issue. If your have a public example or repo to share,
|
||||
please provide the link.
|
||||
|
||||
1. Step 1
|
||||
2. Step 2
|
||||
@@ -1,72 +0,0 @@
|
||||
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 22.04
|
||||
- label: Ubuntu 24.04
|
||||
- label: Ubuntu Slim
|
||||
- label: macOS 13
|
||||
- label: macOS 13 Arm64
|
||||
- label: macOS 14
|
||||
- label: macOS 14 Arm64
|
||||
- label: macOS 15
|
||||
- label: macOS 15 Arm64
|
||||
- label: macOS 26 Arm64
|
||||
- label: Windows Server 2019
|
||||
- label: Windows Server 2022
|
||||
- label: Windows Server 2025
|
||||
- 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
|
||||
@@ -1,6 +0,0 @@
|
||||
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,40 @@
|
||||
---
|
||||
name: Tool request
|
||||
about: Request a new tool or update to a tool
|
||||
title: Update/Add [tool name]
|
||||
labels: t:Enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Tool information**
|
||||
- Tool name: <!--- Name -->
|
||||
- Tool license: <!--- Type of licensing for desired tool -->
|
||||
- Add or update? <!--- Add or update? -->
|
||||
- Desired version: <!--- Let us know if you're requesting a specific version, dev/RC, whatever is latest, etc. -->
|
||||
- Approximate size: <!--- Leave blank if not known -->
|
||||
- If this is an add request:
|
||||
- Brief description of tool: <!--- Description -->
|
||||
- URL for tool's homepage: <!--- URL -->
|
||||
|
||||
**Area for Triage**:
|
||||
<!-- See https://github.com/actions/virtual-environments/tree/master/triage-rules.yml for areas -->
|
||||
|
||||
**Question, Bug, or Feature?**:
|
||||
<!-- Choose "Question", "Bug", or "Feature" -->
|
||||
|
||||
**Virtual environments affected**
|
||||
- [ ] macOS 10.15
|
||||
- [ ] Ubuntu 16.04 LTS
|
||||
- [ ] Ubuntu 18.04 LTS
|
||||
- [ ] Windows Server 2016 R2
|
||||
- [ ] Windows Server 2019
|
||||
|
||||
**Can this tool be installed during the build?**
|
||||
<!--- If so, please provide a description with required steps. -->
|
||||
|
||||
**Tool installation time in runtime**
|
||||
<!--- How long does it take to install the tool? -->
|
||||
|
||||
**Are you willing to submit a PR?**
|
||||
<!--- We accept contributions! -->
|
||||
@@ -1,85 +0,0 @@
|
||||
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 22.04
|
||||
- label: Ubuntu 24.04
|
||||
- label: Ubuntu Slim
|
||||
- label: macOS 13
|
||||
- label: macOS 13 Arm64
|
||||
- label: macOS 14
|
||||
- label: macOS 14 Arm64
|
||||
- label: macOS 15
|
||||
- label: macOS 15 Arm64
|
||||
- label: macOS 26 Arm64
|
||||
- label: Windows Server 2019
|
||||
- label: Windows Server 2022
|
||||
- label: Windows Server 2025
|
||||
- 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!
|
||||
@@ -1,39 +0,0 @@
|
||||
# GitHub Copilot Instructions for Actions Runner Images Repository
|
||||
|
||||
## Scope and goals
|
||||
|
||||
- This repository serves as the source for building GitHub Actions runner and Azure DevOps agent images for Windows, Ubuntu, and macOS. You can find exact versions in the [Available Images](../README.md#available-images) section of README.md. Windows and Ubuntu images build on Azure infrastructure using Packer; macOS images use Anka virtualization.
|
||||
- Emphasize best practices for contributing to open-source projects, including code style, commit messages, and pull request etiquette.
|
||||
- Prefer clarity and correctness over creativity. If information is missing, ask clarifying questions or insert TODOs instead of guessing.
|
||||
|
||||
## Code and command instructions
|
||||
|
||||
- Follow the code style guide in [CONTRIBUTING.md](../CONTRIBUTING.md#code-style-guide) for Bash and PowerShell scripts, including naming conventions, file structure, and indentation rules.
|
||||
- Focus on re-using helpers when writing scripts. Windows, Linux and Ubuntu scripts have helper functions available to simplify installation and validation.
|
||||
- Always confirm versions and installation paths against existing toolset files and installation scripts.
|
||||
|
||||
## Output format
|
||||
|
||||
- Use GitHub Flavored Markdown only. Avoid raw HTML unless necessary.
|
||||
- One H1 (`#`) per page, followed by logical, sequential headings (`##`, `###`, …).
|
||||
- Use fenced code blocks with language identifiers (` ```bash `, ` ```json `, ` ```yaml `, etc.).
|
||||
- Use blockquote callouts for notes:
|
||||
> [!NOTE] Context or nuance
|
||||
> [!TIP] Helpful hint
|
||||
> [!WARNING] Risks or breaking changes
|
||||
> [!IMPORTANT] Critical requirement for functionality
|
||||
|
||||
## Style and tone
|
||||
|
||||
- Audience: Open-source contributors, GitHub Actions maintainers, and developers building custom runner images. Assume familiarity with CI/CD concepts, Packer, and basic infrastructure provisioning, but explain platform-specific details (Azure for Windows/Ubuntu, Anka for macOS) when relevant.
|
||||
- Voice: Second person ("you"), active voice, imperative for operational steps.
|
||||
- Be concise: short paragraphs and sentences. Prefer lists and step-by-steps, especially for operational procedures and troubleshooting.
|
||||
- Use inclusive, accessible language. Avoid idioms, sarcasm, and culturally specific references.
|
||||
- English: en-US (spelling, punctuation, and units).
|
||||
|
||||
## Safety and integrity
|
||||
|
||||
- Do not expose sensitive credentials (API tokens, Azure subscription IDs, etc.) in code examples.
|
||||
- Do not fabricate tool versions, installation paths, or software availability without verifying against toolset files or actual installation scripts.
|
||||
- Always call out assumptions and limitations explicitly, especially for changes affecting runner image behavior or software availability.
|
||||
- If ambiguous requests are made about image modifications, ask clarifying questions about target OS, tool versions, and compatibility requirements before proceeding.
|
||||
@@ -1,10 +1,8 @@
|
||||
# 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.
|
||||
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
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
name: Check Outdated Version Pinning
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * 1' # Run at 12:00 UTC every Monday
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-pinning-dates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Validate JSON Schema
|
||||
shell: pwsh
|
||||
run: ./helpers/CheckOutdatedVersionPinning.ps1
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -1,72 +0,0 @@
|
||||
# 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' ]
|
||||
# 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@v5
|
||||
|
||||
# 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
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Create GitHub release
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-github-release]
|
||||
|
||||
|
||||
jobs:
|
||||
Create_GitHub_release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Create release for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b #v1.20.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 }}
|
||||
allowUpdates: true
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Create Pull Request
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-pr]
|
||||
|
||||
|
||||
jobs:
|
||||
Create_pull_request:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
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 --force
|
||||
|
||||
- name: Create pull request for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
id: create-pr
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const pulls = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
head: `${context.repo.owner}:${{ github.event.client_payload.ReleaseBranchName }}-docs`,
|
||||
base: "${{ github.event.client_payload.PullRequestBase }}",
|
||||
state: 'open'
|
||||
});
|
||||
|
||||
if (pulls.data.length > 0) {
|
||||
console.log(`Pull request already exists: ${pulls.data[0].html_url}`);
|
||||
return pulls.data[0].number;
|
||||
} else {
|
||||
console.log('No existing pull request found, creating new one');
|
||||
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@v8
|
||||
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']
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
name: Create SBOM for the release
|
||||
|
||||
run-name: Collecting SBOM for ${{ github.event.client_payload.agentSpec || 'unknown image' }} - ${{ github.event.client_payload.imageVersion || 'unknown version' }}
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [generate-sbom]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
sbom-check:
|
||||
outputs:
|
||||
check_status: ${{ steps.check.outputs.status }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_ID: ${{ github.event.client_payload.ReleaseID }}
|
||||
steps:
|
||||
- name: Check SBOM asset for release ${{ env.RELEASE_ID }}
|
||||
id: check
|
||||
shell: pwsh
|
||||
run: |
|
||||
$apiUrl = "https://api.github.com/repos/actions/runner-images/releases/$env:RELEASE_ID"
|
||||
$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 $env:RELEASE_ID wasn't found"
|
||||
exit 1
|
||||
}
|
||||
foreach ($asset in $response.assets) {
|
||||
if ($asset.name -like '*sbom*') {
|
||||
echo "status=sbom_exists" >> $env:GITHUB_OUTPUT
|
||||
return "Release $env:RELEASE_ID already contains a SBOM"
|
||||
}
|
||||
}
|
||||
Write-Host "Release has been found, SBOM is not attached, starting generation."
|
||||
echo "status=okay" >> $env:GITHUB_OUTPUT
|
||||
|
||||
building-sbom:
|
||||
needs: sbom-check
|
||||
if: ${{ needs.sbom-check.outputs.check_status == 'okay' }}
|
||||
runs-on: ${{ github.event.client_payload.agentSpec }}
|
||||
env:
|
||||
AGENT_SPEC: ${{ github.event.client_payload.agentSpec }}
|
||||
RELEASE_ID: ${{ github.event.client_payload.ReleaseID }}
|
||||
IMAGE_VERSION: ${{ github.event.client_payload.imageVersion }}
|
||||
steps:
|
||||
- name: Available image version check
|
||||
run: |
|
||||
$expectedVersion = $env:IMAGE_VERSION
|
||||
$runnerVersion = $env:ImageVersion
|
||||
|
||||
# Split versions by dot
|
||||
$expectedParts = $expectedVersion.Split('.')
|
||||
$runnerParts = $runnerVersion.Split('.')
|
||||
|
||||
# Determine what parts to compare
|
||||
$minLength = [Math]::Min($expectedParts.Length, $runnerParts.Length)
|
||||
$expectedComparable = $expectedParts[0..($minLength-1)] -join '.'
|
||||
$runnerComparable = $runnerParts[0..($minLength-1)] -join '.'
|
||||
|
||||
# Perform the comparison
|
||||
if ($expectedComparable -ne $runnerComparable) {
|
||||
throw "Version mismatch: Expected version '$expectedVersion' doesn't match runner version '$runnerVersion'"
|
||||
}
|
||||
|
||||
- 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 C:/syft
|
||||
|
||||
- name: Install SYFT tool on Ubuntu
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
- name: Install SYFT v1.24.0 on macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v1.24.0
|
||||
|
||||
- name: Run SYFT on Windows
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: C:/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' }}
|
||||
# Skip protected folders to avoid prompt privileges that block process indefinitely (https://github.com/anchore/syft/issues/1367)
|
||||
run: sudo syft dir:/ -vv -o spdx-json=sbom.json --exclude ./Users --exclude ./System/Volumes --exclude ./private
|
||||
shell: bash
|
||||
|
||||
- name: Compress SBOM file
|
||||
run: Compress-Archive sbom.json sbom.json.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sbom-${{ env.AGENT_SPEC }}-${{ env.IMAGE_VERSION }}
|
||||
path: sbom.json.zip
|
||||
if-no-files-found: warn
|
||||
|
||||
- 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/${{ env.RELEASE_ID }}/assets{?name,label}"
|
||||
asset_path: ./sbom.json.zip
|
||||
asset_name: sbom.${{ env.AGENT_SPEC }}.json.zip
|
||||
asset_content_type: application/zip
|
||||
@@ -0,0 +1,32 @@
|
||||
# Adapted from: https://github.com/microsoft/azure-pipelines-tasks/blob/master/.github/workflows/blank.yml
|
||||
# This action labels and assigns newly opened issues
|
||||
|
||||
name: Issue triager
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- uses: damccorm/tag-ur-it@master
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: "./triage-rules.yml"
|
||||
|
||||
- if: always()
|
||||
uses: actions/github-script@0.4.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['needs triage']
|
||||
})
|
||||
@@ -1,36 +0,0 @@
|
||||
# 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@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Lint Code Base
|
||||
uses: github/super-linter/slim@v7
|
||||
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
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Merge pull request
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [merge-pr]
|
||||
|
||||
|
||||
jobs:
|
||||
Merge_pull_request:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
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@v8
|
||||
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@v8
|
||||
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"
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
# 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@v5
|
||||
|
||||
- name: Run Software Report module tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
Invoke-Pester -Output Detailed "helpers/software-report-base/tests"
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
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
|
||||
outputs:
|
||||
ci_workflow_run_id: ${{ steps.resolve.outputs.ci_workflow_run_id }}
|
||||
ci_workflow_run_url: ${{ steps.resolve.outputs.ci_workflow_run_url }}
|
||||
env:
|
||||
CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
CI_REPO: ${{ vars.CI_REPO }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Trigger Build workflow
|
||||
run: |
|
||||
Import-Module ./helpers/GitHubApi.psm1
|
||||
$gitHubApi = Get-GithubApi -Repository "${env:CI_REPO}" -AccessToken "${env:CI_PR_TOKEN}"
|
||||
|
||||
$eventType = "trigger-${{ inputs.image_type }}-build"
|
||||
[string] $prGuid = New-Guid
|
||||
$clientPayload = @{
|
||||
pr_title = "${env:PR_TITLE} - " + $prGuid
|
||||
custom_repo = "${{ github.event.pull_request.head.repo.full_name }}"
|
||||
custom_repo_commit_hash = "${{ github.event.pull_request.head.sha }}"
|
||||
}
|
||||
|
||||
$gitHubApi.DispatchWorkflow($eventType, $clientPayload)
|
||||
"PR_GUID=$prGuid" | Out-File -Append -FilePath $env:GITHUB_ENV
|
||||
|
||||
- name: Resolve Workflow Run ID
|
||||
id: resolve
|
||||
run: |
|
||||
Import-Module ./helpers/GitHubApi.psm1
|
||||
$gitHubApi = Get-GithubApi -Repository "${env:CI_REPO}" -AccessToken "${env:CI_PR_TOKEN}"
|
||||
|
||||
$workflowFileName = $("{0}.yml" -f "${{ inputs.image_type }}").ToLower()
|
||||
$WorkflowSearchPattern = "${env:PR_GUID}"
|
||||
|
||||
# It might take a few minutes for the action to start
|
||||
$attempt = 1
|
||||
do {
|
||||
$workflowRuns = $gitHubApi.GetWorkflowRuns($WorkflowFileName).workflow_runs
|
||||
$workflowRunId = ($workflowRuns | Where-Object {$_.display_title -match $WorkflowSearchPattern}).id | Select-Object -First 1
|
||||
|
||||
if (-not ([string]::IsNullOrEmpty($workflowRunId))) {
|
||||
$workflowRun = $gitHubApi.GetWorkflowRun($workflowRunId)
|
||||
Write-Host "Found the workflow run with ID $workflowRunId on attempt $attempt. Workflow run link: $($workflowRun.html_url)"
|
||||
"ci_workflow_run_id=$workflowRunId" | Out-File -Append -FilePath $env:GITHUB_OUTPUT
|
||||
"ci_workflow_run_url=$($workflowRun.html_url)" | Out-File -Append -FilePath $env:GITHUB_OUTPUT
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host "Workflow run for $WorkflowSearchPattern pattern not found on attempt $attempt."
|
||||
$attempt += 1
|
||||
Start-Sleep 30
|
||||
} until ($attempt -eq 10)
|
||||
|
||||
if ([string]::IsNullOrEmpty($workflowRunId)) {
|
||||
throw "Failed to find a workflow run for '$WorkflowSearchPattern'."
|
||||
}
|
||||
|
||||
wait-completion:
|
||||
runs-on: ubuntu-latest
|
||||
needs: trigger-workflow
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Wait for workflow completion
|
||||
env:
|
||||
CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }}
|
||||
CI_REPO: ${{ vars.CI_REPO }}
|
||||
run: |
|
||||
./helpers/WaitWorkflowCompletion.ps1 `
|
||||
-WorkflowRunId "${{ needs.trigger-workflow.outputs.ci_workflow_run_id }}" `
|
||||
-Repository "${env:CI_REPO}" `
|
||||
-AccessToken "${env:CI_PR_TOKEN}"
|
||||
|
||||
- name: Add Summary
|
||||
if: always()
|
||||
run: |
|
||||
"# Test Partner Image" >> $env:GITHUB_STEP_SUMMARY
|
||||
"| Key | Value |" >> $env:GITHUB_STEP_SUMMARY
|
||||
"| :-----------: | :--------: |" >> $env:GITHUB_STEP_SUMMARY
|
||||
"| Workflow Run | [Link](${{ needs.trigger-workflow.outputs.ci_workflow_run_url }}) |" >> $env:GITHUB_STEP_SUMMARY
|
||||
"| Workflow Result | $env:CI_WORKFLOW_RUN_RESULT |" >> $env:GITHUB_STEP_SUMMARY
|
||||
" " >> $env:GITHUB_STEP_SUMMARY
|
||||
|
||||
cancel-workflow:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [trigger-workflow, wait-completion]
|
||||
if: cancelled()
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Cancel workflow
|
||||
env:
|
||||
CI_PR_TOKEN: ${{ secrets.CI_PR_TOKEN }}
|
||||
CI_REPO: ${{ vars.CI_REPO }}
|
||||
run: |
|
||||
Import-Module ./helpers/GitHubApi.psm1
|
||||
|
||||
$gitHubApi = Get-GithubApi -Repository "${env:CI_REPO}" -AccessToken "${env:CI_PR_TOKEN}"
|
||||
$gitHubApi.CancelWorkflowRun("${{ needs.trigger-workflow.outputs.ci_workflow_run_id }}")
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Update release
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [update-github-release]
|
||||
|
||||
|
||||
jobs:
|
||||
Update_GitHub_release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Update release for ${{ github.event.client_payload.ReleaseBranchName }}
|
||||
uses: actions/github-script@v8
|
||||
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 }}
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Validate JSON Schema
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
validate-json-schema:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Validate JSON Schema
|
||||
shell: pwsh
|
||||
run: ./helpers/CheckJsonSchema.ps1
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
+1
-10
@@ -267,12 +267,6 @@ paket-files/
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# VSCode settings
|
||||
.vscode/**
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
@@ -393,7 +387,4 @@ public
|
||||
.dynamodb/
|
||||
|
||||
# visual studio code launch configuration
|
||||
launch.json
|
||||
|
||||
# Ignore dynamic template
|
||||
images/*/*-temp.json
|
||||
launch.json
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"hashicorp.hcl",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"ms-vscode.powershell",
|
||||
"timonwong.shellcheck"
|
||||
]
|
||||
}
|
||||
Vendored
+2
-32
@@ -1,34 +1,4 @@
|
||||
{
|
||||
"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"
|
||||
],
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"**/toolset-*.json"
|
||||
],
|
||||
"url": "./schemas/toolset-schema.json"
|
||||
}
|
||||
]
|
||||
|
||||
// When enabled, will trim trailing whitespace when you save a file.
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
|
||||
+12
-233
@@ -1,250 +1,29 @@
|
||||
# Contributing
|
||||
## Contributing
|
||||
|
||||
[fork]: https://github.com/actions/runner-images/fork
|
||||
[pr]: https://github.com//actions/runner-images/compare
|
||||
[fork]: https://github.com/actions/virtual-environments/fork
|
||||
[pr]: https://github.com//actions/virtual-environments/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.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Submitting a pull request](#submitting-a-pull-request)
|
||||
- [Adding a new tool to an image](#adding-a-new-tool-to-an-image)
|
||||
- [Code style guide](#code-style-guide)
|
||||
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).
|
||||
|
||||
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 [Adding a new tool to an image](#adding-a-new-tool-to-an-image) 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].
|
||||
1. [Fork][fork] and clone the repository
|
||||
1. Create a new branch: `git checkout -b my-branch-name`
|
||||
1. Make your changes, ensure that they include steps to install and validate post-install (e.g. [kind.sh](images/linux/scripts/installers/kind.sh)).
|
||||
1. Test your changes by [creating VHD and deploying a VM](help/CreateImageAndAzureResources.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.
|
||||
- Include complete details of why this is needed in the PR description. If it's a new piece tool being installed, consider cross-platform. If the tool is available in other platforms (MacOS, Windows, Linux), make sure you include it in as many as possible.
|
||||
- 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](https://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.
|
||||
|
||||
## Adding a new tool to an image
|
||||
|
||||
### 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. [Ubuntu2204-Readme.md](images/ubuntu/Ubuntu2204-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.
|
||||
|
||||
## Code style guide
|
||||
|
||||
The principles of clean code apply to all languages. The main points are:
|
||||
|
||||
- Use meaningful names for variables, functions, files, etc.
|
||||
- Keep functions short and simple.
|
||||
- Use comments to explain what the code does.
|
||||
- Use a consistent code style, naming convention, and file structure.
|
||||
|
||||
### File structure
|
||||
|
||||
- Each file should have a header with a title and a short description of the file.
|
||||
- Each file should have a newline at the end.
|
||||
- Use blank lines to separate logical blocks of code, but don't abuse blank lines:
|
||||
- Don't add a blank line in the beginning and end of a block or function.
|
||||
- Don't add blank lines between logically connected statements.
|
||||
- Avoid trailing whitespace.
|
||||
|
||||
### Bash scripts
|
||||
|
||||
#### Naming convention for bash scripts
|
||||
|
||||
- Use lowercase letters for variable names.
|
||||
- Use uppercase letters for constants.
|
||||
- Use underscores to separate words in variable names.
|
||||
|
||||
#### Bash script structure
|
||||
|
||||
Each script should start with the following shebang:
|
||||
|
||||
```bash
|
||||
#!/bin/bash -e
|
||||
```
|
||||
|
||||
> TODO: do we need to set pipefail?
|
||||
|
||||
This will make the script exit if any command fails.
|
||||
|
||||
After the shebang, add a header with the following format:
|
||||
|
||||
```bash
|
||||
################################################################################
|
||||
## File: <filename>
|
||||
## Desc: <short description of what the script does>
|
||||
################################################################################
|
||||
```
|
||||
|
||||
Then import helpers that are used in the script.
|
||||
|
||||
For Linux:
|
||||
|
||||
```bash
|
||||
source $HELPER_SCRIPTS/os.sh
|
||||
source $HELPER_SCRIPTS/install.sh
|
||||
source $HELPER_SCRIPTS/etc-environment.sh
|
||||
```
|
||||
|
||||
For macOS:
|
||||
|
||||
```bash
|
||||
source ~/utils/utils.sh
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You don't need to import all helpers, only the ones that are used in the script.
|
||||
|
||||
After that, add the script code.
|
||||
|
||||
### Indentations and line breaks in bash scripts
|
||||
|
||||
- Use 4 spaces for indentation.
|
||||
- Use 1 space between `if`/`for`/`while` and `[[` and between `[[` and the condition.
|
||||
- Place `then`/`do` on the new line.
|
||||
- For short `if`/`for`/`while` statements, use the one-line format.
|
||||
- Break long pipelines using `\`.
|
||||
|
||||
### Other recommendations for bash scripts
|
||||
|
||||
- For command substitution, use `$()` instead of backticks.
|
||||
- Use `[[` instead of `[` for conditional expressions.
|
||||
- Prefer using long options instead of short keys, but there are exceptions, e.g.:
|
||||
- `tar -xzf`
|
||||
- `apt-get -yqq`
|
||||
- `curl -sSLf`
|
||||
- `wget -qO-`
|
||||
|
||||
### PowerShell scripts
|
||||
|
||||
#### Naming convention for PowerShell scripts
|
||||
|
||||
- Use camelCase for variable names.
|
||||
- Use uppercase letters for constants.
|
||||
- Use `Verb-Noun` and PascalCase for function names.
|
||||
|
||||
### PowerShell script structure
|
||||
|
||||
Each script should start with the following header:
|
||||
|
||||
```powershell
|
||||
################################################################################
|
||||
## File: <filename>
|
||||
## Desc: <short description of what the script does>
|
||||
################################################################################
|
||||
```
|
||||
|
||||
Then declare functions that are used in the script.
|
||||
|
||||
> TODO: do we need to set the error action preference and progress preference?
|
||||
>
|
||||
> ```powershell
|
||||
> $ErrorActionPreference = "Stop"
|
||||
> $ProgressPreference = "SilentlyContinue"
|
||||
> ```
|
||||
|
||||
For Linux and macOS, import helpers that are used in the script:
|
||||
|
||||
For Linux:
|
||||
|
||||
```powershell
|
||||
Import-Module "$env:HELPER_SCRIPTS/Tests.Helpers.psm1" -DisableNameChecking
|
||||
```
|
||||
|
||||
For macOS:
|
||||
|
||||
```powershell
|
||||
Import-Module "$env:HOME/image-generation/helpers/Common.Helpers.psm1"
|
||||
Import-Module "$env:HOME/image-generation/helpers/Xcode.Helpers.psm1" -DisableNameChecking
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You don't need to import all helpers, only the ones that are used in the script.
|
||||
|
||||
After that, add the script code.
|
||||
|
||||
### Indentations and line breaks in PowerShell scripts
|
||||
|
||||
- Use 4 spaces for indentation.
|
||||
- Use 1 space between `if`/`elseif`/`foreach` and `(` but not between `(` and the condition.
|
||||
- Add a space before and after pipe `|` and redirection `>` operators.
|
||||
- Align properties in hash tables.
|
||||
- Use [1TBS](https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_(OTBS)) style for curly braces:
|
||||
- If block of statement is long, then place it on the new line, indent it, and add a closing curly brace on the new line.
|
||||
- If block of statement is short, then place it on the same line as the statement.
|
||||
|
||||
```powershell
|
||||
function Show-Example1 {
|
||||
$exampleVariable = Get-ChildItem $env:TEMP
|
||||
$exampleVariable | ForEach-Object {
|
||||
$itemName = $_.Name
|
||||
$itemPath = $_.FullName
|
||||
}
|
||||
}
|
||||
|
||||
$Example2 | Some-Function -Arguments @{Parameter1 = "Disabled"}
|
||||
```
|
||||
|
||||
- Avoid using aliases.
|
||||
- Break long pipelines using backticks or use [splatting](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.3):
|
||||
|
||||
```powershell
|
||||
# Instead of this
|
||||
Copy-Item -Path "test.txt" -Destination "test2.txt" -WhatIf
|
||||
|
||||
# you can use this
|
||||
$HashArguments = @{
|
||||
Path = "test.txt"
|
||||
Destination = "test2.txt"
|
||||
WhatIf = $true
|
||||
}
|
||||
Copy-Item @HashArguments
|
||||
```
|
||||
|
||||
When using backticks be extra careful with trailing whitespace as they can cause errors.
|
||||
|
||||
### Other recommendations for PowerShell scripts
|
||||
|
||||
- Verify exit codes of commands.
|
||||
- When writing a function, provide a docstring that describes what the function does.
|
||||
- Write [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 GitHub
|
||||
Copyright (c) 2020 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
|
||||
|
||||
@@ -1,217 +1,46 @@
|
||||
# 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 |
|
||||
| --------------------|---------------------|--------------------|
|
||||
| Ubuntu 24.04 | `ubuntu-latest` or `ubuntu-24.04` | [ubuntu-24.04] |
|
||||
| Ubuntu 22.04 | `ubuntu-22.04` | [ubuntu-22.04] |
|
||||
| macOS 26 Arm64 `beta` | `macos-26` or `macos-26-xlarge` | [macOS-26-arm64] |
|
||||
| macOS 15 | `macos-latest-large`, `macos-15-large`, or `macos-15-intel` | [macOS-15] |
|
||||
| macOS 15 Arm64 | `macos-latest`, `macos-15`, or `macos-15-xlarge` | [macOS-15-arm64] |
|
||||
| macOS 14 | `macos-14-large`| [macOS-14] |
|
||||
| macOS 14 Arm64 | `macos-14` 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] |
|
||||
| Windows Server 2025 | `windows-latest` or `windows-2025` | [windows-2025] |
|
||||
| Windows Server 2022 | `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
|
||||
[windows-2019]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2019-Readme.md
|
||||
[windows-2025]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md
|
||||
[windows-2022]: https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-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
|
||||
[macOS-26-arm64]: https://github.com/actions/runner-images/blob/main/images/macos/macos-26-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. Note for [Ubuntu images see details.](./docs/dotnet-ubuntu.md) |
|
||||
| GCC <br/> GNU Fortran <br/> Clang <br/> GNU C++ | 3 latest major versions |
|
||||
| Android NDK | 1 latest non-LTS, 2 latest LTS versions |
|
||||
| Xcode | - only one major version of Xcode will be supported per macOS version <br/> - all minor versions of the supported major version will be available <br/> - beta and RC versions will be provided "as-is" in the latest available macOS image only no matter of beta/GA status of the image <br/> - when a new patch version is released, the previous patch version will be replaced |
|
||||
| Xcode Platforms | - only three major.minor versions of platform tools and simulator runtimes will be available for installed Xcode, including beta/RC versions |
|
||||
|
||||
### 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) | [docker](https://download.docker.com/linux/ubuntu) <br/> [Eclipse-Temurin (Adoptium)](https://packages.adoptium.net/artifactory/deb/) <br/> [Erlang](https://packages.erlang-solutions.com/ubuntu) <br/> [Firefox](https://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>
|
||||
# GitHub Actions Virtual Environments
|
||||
This repository contains the source used to create the [virtual environments](https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners) for GitHub Actions hosted runners, as well as the VM images of [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 file bug reports, or request that tools be added/updated, please [open an issue using the appropriate template](https://github.com/actions/virtual-environments/issues/new/choose). To build a VM machine from this repo's source, see the [instructions](./help/CreateImageAndAzureResources.md).
|
||||
|
||||
For general questions about using the virtual environments or writing your Actions workflow, please open requests in the [GitHub Actions Community Forum](https://github.community/t5/GitHub-Actions/bd-p/actions).
|
||||
|
||||
## Available Environments
|
||||
| Environment | YAML Label | Included Software | Latest Release & Rollout Progress |
|
||||
| --------------------|---------------------|--------------------|---------------------|
|
||||
| Ubuntu 18.04 | `ubuntu-latest` or `ubuntu-18.04` | [ubuntu-18.04] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=ubuntu18&redirect=1)
|
||||
| Ubuntu 16.04 | `ubuntu-16.04` | [ubuntu-16.04] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=ubuntu16&redirect=1) |
|
||||
| macOS 10.15 | `macos-latest` or `macos-10.15` | [macOS-10.15] | *Coming soon* |
|
||||
| Windows Server 2019 | `windows-latest` or `windows-2019` | [windows-2019] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=windows-2019&redirect=1)
|
||||
| Windows Server 2016 | `windows-2016` | [windows-2016] | [](https://actionvirtualenvironmentsstatus.azurewebsites.net/api/status?imageName=windows-2016&redirect=1)
|
||||
|
||||
***Looking for other Linux distributions?*** 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 virtual environments. Alternatively, you can leverage [self-hosted runners] and fully customize your environment to your needs.
|
||||
|
||||
***Where is the macOS source?*** We are in the process of preparing our macOS source to live in this repo so we can take contributions from the community. Until then, we appreciate your patience and ask you continue to make tool requests by filing issues.
|
||||
|
||||
## Software Guidelines
|
||||
In general, these are the guidelines we consider when deciding what to pre-install:
|
||||
|
||||
- Tools and ecosystems that are broadly popular and widely-used will be given priority.
|
||||
- Recent versions of tools will be given priority over older versions.
|
||||
- Tools and versions that are deprecated or have reached end-of-life will not be added.
|
||||
- Tools and versions will typically be removed 6 months after they are deprecated or have reached end-of-life.
|
||||
- If a tool can be installed during the build, we will evaluate how much time is saved
|
||||
and how much space is used by having the tool pre-installed.
|
||||
|
||||
## Updates to virtual environments
|
||||
*Cadence*
|
||||
|
||||
We typically deploy weekly updates to the software on the virtual environments.
|
||||
For some tools, we always install the latest at the time of the deployment; for others,
|
||||
we pin the tool to specific version(s).
|
||||
|
||||
*Following Along / Change Notifications*
|
||||
|
||||
* **High Impact Changes** (ex. breaking changes, new or deprecated environments) will be posted to the GitHub Changelog on our [blog](https://github.blog/changelog/) and on [twitter](https://twitter.com/GHchangelog).
|
||||
* **Regular Weekly Rhythm** can be followed by watching [Releases](https://github.com/actions/virtual-environments/releases) to see when we generate candidate environments or deploy new ones. You can also track upcoming changes on the [virtual environment project](https://github.com/actions/virtual-environments/projects/1) to see which issues are under development.
|
||||
|
||||
[ubuntu-18.04]: https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md
|
||||
[ubuntu-16.04]: https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1604-README.md
|
||||
[Windows-2019]: https://github.com/actions/virtual-environments/blob/master/images/win/Windows2019-Readme.md
|
||||
[windows-2016]: https://github.com/actions/virtual-environments/blob/master/images/win/Windows2016-Readme.md
|
||||
[macOS-10.15]: https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md
|
||||
[self-hosted runners]: https://help.github.com/en/actions/hosting-your-own-runners
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-slate
|
||||
@@ -1,294 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
################################################################################
|
||||
## File: diff-image-versions.sh
|
||||
## Desc: Compare software versions between two runner image releases
|
||||
## Usage: ./diff-image-versions.sh <os-name> <version1> <version2>
|
||||
##
|
||||
## Example:
|
||||
## ./diff-image-versions.sh ubuntu22 20251102.127 20251125.163
|
||||
## ./diff-image-versions.sh win25 20251102.77 20251125.122
|
||||
## ./diff-image-versions.sh macos-14 20251102.0024 20251125.0031
|
||||
################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "${0}") <os-name> <version1> <version2>
|
||||
|
||||
Compare runner image versions and display software changes.
|
||||
|
||||
Arguments:
|
||||
os-name OS identifier (ubuntu22, ubuntu24, win19, win22, win25,
|
||||
macos-13, macos-14, macos-15, or arm64 variants)
|
||||
version1 Earlier version (YYYYMMDD.NNN)
|
||||
version2 Later version (YYYYMMDD.NNN)
|
||||
|
||||
Examples:
|
||||
$(basename "${0}") ubuntu22 20251102.127 20251125.163
|
||||
$(basename "${0}") win25 20251102.77 20251125.122
|
||||
EOF
|
||||
}
|
||||
|
||||
get_readme_path() {
|
||||
local os_name="${1}"
|
||||
local os_folder=""
|
||||
local pattern=""
|
||||
|
||||
# Determine OS folder and readme filename pattern
|
||||
case "${os_name}" in
|
||||
ubuntu*)
|
||||
os_folder="ubuntu"
|
||||
local version="${os_name#ubuntu}"
|
||||
pattern="Ubuntu${version}04-Readme.md"
|
||||
;;
|
||||
win*)
|
||||
os_folder="windows"
|
||||
local version="${os_name#win}"
|
||||
pattern="Windows20${version}-Readme.md"
|
||||
;;
|
||||
macos*)
|
||||
os_folder="macos"
|
||||
pattern="${os_name}-Readme.md"
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown OS '${os_name}'" >&2
|
||||
echo "Valid: ubuntu*, win*, macos-*" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local readme_path="images/${os_folder}/${pattern}"
|
||||
|
||||
# Verify file exists in git repository
|
||||
if ! git cat-file -e "HEAD:${readme_path}" 2>/dev/null; then
|
||||
echo "Error: Readme not found: ${readme_path}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "${readme_path}"
|
||||
}
|
||||
|
||||
validate_version() {
|
||||
local version="${1}"
|
||||
|
||||
if [[ ! "${version}" =~ ^[0-9]{8}\.[0-9]+$ ]]; then
|
||||
echo "Error: Invalid version '${version}'" >&2
|
||||
echo "Format: YYYYMMDD.NNN (e.g., 20251102.127)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
tag_exists() {
|
||||
local tag="${1}"
|
||||
|
||||
if git rev-parse "${tag}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
echo "Error: Tag '${tag}' not found" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
# Check arguments
|
||||
if [[ $# -ne 3 ]]; then
|
||||
usage
|
||||
return 1
|
||||
fi
|
||||
|
||||
local os_name="${1}"
|
||||
local version1="${2}"
|
||||
local version2="${3}"
|
||||
|
||||
# Validate inputs
|
||||
validate_version "${version1}" || return 1
|
||||
validate_version "${version2}" || return 1
|
||||
|
||||
# Get readme path
|
||||
local readme_path
|
||||
readme_path="$(get_readme_path "${os_name}")" || return 1
|
||||
|
||||
# Construct git tags
|
||||
local tag1="${os_name}/${version1}"
|
||||
local tag2="${os_name}/${version2}"
|
||||
|
||||
# Verify tags exist
|
||||
tag_exists "${tag1}" || return 1
|
||||
tag_exists "${tag2}" || return 1
|
||||
|
||||
# Get release dates
|
||||
local date1
|
||||
local date2
|
||||
date1=$(git log -1 --format="%ci" "${tag1}" | cut -d' ' -f1)
|
||||
date2=$(git log -1 --format="%ci" "${tag2}" | cut -d' ' -f1)
|
||||
|
||||
# Calculate days between releases
|
||||
local days_diff
|
||||
days_diff=$(( ($(date -d "${date2}" +%s) - $(date -d "${date1}" +%s)) / 86400 ))
|
||||
|
||||
# Display header
|
||||
echo "================================================================================"
|
||||
echo "Comparing: ${os_name}"
|
||||
echo " From: ${version1} (${date1})"
|
||||
echo " To: ${version2} (${date2})"
|
||||
echo " Span: ${days_diff} days"
|
||||
echo "================================================================================"
|
||||
echo ""
|
||||
|
||||
# Perform diff with minimal context (only changed lines with colors)
|
||||
# ANSI codes: ^[[31m (red for -), ^[[32m (green for +), ^[[36m (cyan for @@)
|
||||
# Filter to show only lines starting with red/green (additions/deletions)
|
||||
local diff_output
|
||||
diff_output=$(git diff --color=always --unified=0 "${tag1}:${readme_path}" "${tag2}:${readme_path}" | \
|
||||
grep -E $'^\x1b\\[(31|32)m' | \
|
||||
grep -v -E $'^\x1b\\[1m(---|\\+\\+\\+)')
|
||||
|
||||
if [[ -n "${diff_output}" ]]; then
|
||||
# Extract announcements from both versions
|
||||
local announcements1
|
||||
local announcements2
|
||||
announcements1=$(git show "${tag1}:${readme_path}" | sed -n '/| Announcements |/,/^\*\*\*$/p' | grep -E '^\| \[' | sed 's/^| \[/• [/' | sed 's/ |$//' || true)
|
||||
announcements2=$(git show "${tag2}:${readme_path}" | sed -n '/| Announcements |/,/^\*\*\*$/p' | grep -E '^\| \[' | sed 's/^| \[/• [/' | sed 's/ |$//' || true)
|
||||
|
||||
# Show announcement changes
|
||||
if [[ "${announcements1}" != "${announcements2}" ]]; then
|
||||
echo "📢 Announcement Changes:"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
if [[ -n "${announcements2}" ]]; then
|
||||
echo "${announcements2}"
|
||||
else
|
||||
echo "(no announcements)"
|
||||
fi
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Extract cached tools sections
|
||||
local cached_tools1
|
||||
local cached_tools2
|
||||
cached_tools1=$(git show "${tag1}:${readme_path}" | sed -n '/^### Cached Tools$/,/^###[^#]/p' | head -n -1 || true)
|
||||
cached_tools2=$(git show "${tag2}:${readme_path}" | sed -n '/^### Cached Tools$/,/^###[^#]/p' | head -n -1 || true)
|
||||
|
||||
# Show cached tools changes
|
||||
if [[ "${cached_tools1}" != "${cached_tools2}" ]]; then
|
||||
local cached_diff
|
||||
cached_diff=$(git diff --color=always --unified=2 --no-index \
|
||||
<(echo "${cached_tools1}") <(echo "${cached_tools2}") 2>/dev/null | \
|
||||
grep -E $'(^\x1b\\[(31|32)m[-+]| #### )' | \
|
||||
sed -r 's/\x1b\[m$//' || true)
|
||||
|
||||
if [[ -n "${cached_diff}" ]]; then
|
||||
echo "🔧 Cached Tools Changes (setup-* actions):"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo "${cached_diff}"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Full Diff:"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo "${diff_output}"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
|
||||
# Count changes
|
||||
local changes
|
||||
changes=$(echo "${diff_output}" | wc -l)
|
||||
echo "Changes: ${changes} lines"
|
||||
|
||||
# Parse version changes for breaking change analysis
|
||||
local breaking_changes=()
|
||||
local removals=()
|
||||
local additions=()
|
||||
|
||||
# Extract clean lines (strip ANSI codes)
|
||||
while IFS= read -r line; do
|
||||
if [[ "${line}" =~ ^\-(.+)$ ]]; then
|
||||
removals+=("${BASH_REMATCH[1]}")
|
||||
elif [[ "${line}" =~ ^\+(.+)$ ]]; then
|
||||
additions+=("${BASH_REMATCH[1]}")
|
||||
fi
|
||||
done < <(echo "${diff_output}" | sed -r 's/\x1b\[[0-9;]*m//g')
|
||||
|
||||
# Detect breaking changes
|
||||
for removed in "${removals[@]}"; do
|
||||
local tool_name=""
|
||||
local old_version=""
|
||||
local found_match=false
|
||||
|
||||
# Try to extract tool name and version (handle various formats)
|
||||
if [[ "${removed}" =~ ^([^0-9]+[[:space:]]+)([0-9]+\.[0-9]+[^[:space:]]*) ]]; then
|
||||
tool_name="${BASH_REMATCH[1]}"
|
||||
old_version="${BASH_REMATCH[2]}"
|
||||
elif [[ "${removed}" =~ ^([^0-9]+[[:space:]]+v)([0-9]+\.[0-9]+[^[:space:]]*) ]]; then
|
||||
tool_name="${BASH_REMATCH[1]}"
|
||||
old_version="${BASH_REMATCH[2]}"
|
||||
fi
|
||||
|
||||
# If we found a semver-style version, look for matching addition
|
||||
if [[ -n "${tool_name}" && -n "${old_version}" ]]; then
|
||||
for added in "${additions[@]}"; do
|
||||
if [[ "${added}" =~ ^${tool_name}([0-9]+\.[0-9]+[^[:space:]]*) ]]; then
|
||||
local new_version="${BASH_REMATCH[1]}"
|
||||
found_match=true
|
||||
|
||||
# Extract major version for semver comparison
|
||||
if [[ "${old_version}" =~ ^([0-9]+)\. && "${new_version}" =~ ^([0-9]+)\. ]]; then
|
||||
local old_major="${BASH_REMATCH[1]}"
|
||||
local new_major="${BASH_REMATCH[1]}"
|
||||
|
||||
[[ "${old_version}" =~ ^([0-9]+)\. ]] && old_major="${BASH_REMATCH[1]}"
|
||||
[[ "${new_version}" =~ ^([0-9]+)\. ]] && new_major="${BASH_REMATCH[1]}"
|
||||
|
||||
if [[ ${new_major} -gt ${old_major} ]]; then
|
||||
breaking_changes+=("🔴 ${tool_name}${old_version} → ${new_version} (major version bump)")
|
||||
fi
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# If no match found and looks like a versioned tool, it's a removal
|
||||
if [[ ${found_match} == false && -n "${old_version}" ]]; then
|
||||
breaking_changes+=("❌ ${removed} (removed)")
|
||||
elif [[ ${found_match} == false && "${removed}" =~ [0-9]+\.[0-9]+ ]]; then
|
||||
breaking_changes+=("❌ ${removed} (removed)")
|
||||
fi
|
||||
done
|
||||
|
||||
# Display breaking changes
|
||||
if [[ ${#breaking_changes[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "⚠️ Breaking changes detected (${#breaking_changes[@]}):"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
printf '%s\n' "${breaking_changes[@]}"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
fi
|
||||
else
|
||||
echo "No changes found."
|
||||
fi
|
||||
|
||||
# Display PR link and commit count
|
||||
local pr_number
|
||||
pr_number=$(git log --all --format="%s" --grep="${version2}" | \
|
||||
grep -oP '\(#\K[0-9]+(?=\))' | head -1)
|
||||
|
||||
local commit_count
|
||||
commit_count=$(git rev-list --count "${tag1}..${tag2}")
|
||||
|
||||
echo "Commits: ${commit_count}"
|
||||
|
||||
if [[ -n "${pr_number}" ]]; then
|
||||
echo "PR: https://github.com/actions/runner-images/pull/${pr_number}"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Execute main function
|
||||
main "$@"
|
||||
@@ -0,0 +1 @@
|
||||
Placeholder for GitHub Pages
|
||||
@@ -1,317 +0,0 @@
|
||||
# GitHub Actions Runner Images
|
||||
|
||||
The runner-images project uses [Packer](https://www.packer.io/) to generate disk images for Windows 2019/2022 and Ubuntu 22.04/24.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/) or 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, a resource group in that subscription and a build agent configured as described above.
|
||||
|
||||
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 store the resulting artifact (e.g., "imagegen-test").
|
||||
The resource group must already exist in your Azure subscription;
|
||||
- `AzureLocation` - the location where resources will be created (e.g., "East US");
|
||||
- `ImageType` - the type of image to build (valid options are "Windows2019", "Windows2022", "Windows2025", "Ubuntu2204", "Ubuntu2404").
|
||||
|
||||
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 commands:
|
||||
|
||||
```powershell
|
||||
packer plugins install github.com/hashicorp/azure 2.2.1
|
||||
|
||||
packer build -only "$BuildName*" `
|
||||
-var "subscription_id=$SubscriptionId" `
|
||||
-var "client_id=$ClientId" `
|
||||
-var "client_secret=$ClientSecret" `
|
||||
-var "install_password=$InstallPassword" `
|
||||
-var "location=$Location" `
|
||||
-var "image_os=$ImageOS" `
|
||||
-var "managed_image_name=$ImageName" `
|
||||
-var "managed_image_resource_group_name=$ImageResourceGroupName" `
|
||||
-var "tenant_id=$TenantId" `
|
||||
$TemplatePath
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `BuildName` - name of the build defined in Packer template's `build{}` block (e.g. "ubuntu-24_04", "windows-2025");
|
||||
- `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");
|
||||
- `ImageOS` - the type of OS that will be deployed as a temporary VM (e.g. "ubuntu24", "win25");
|
||||
- `ImageName` and `ImageResourceGroupName` - name of the resource group where the managed image will be stored;
|
||||
- `TemplatePath` - path to the folder with Packer template files (e.g., "images/windows/templates").
|
||||
|
||||
### 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.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Ubuntu .NET Core Versions
|
||||
|
||||
.NET has changed the recommended install methods for Ubuntu from 2404.
|
||||
|
||||
This document gives an overview of these change and the impact this has on the `runner-images`.
|
||||
|
||||
## .NET Core for Ubuntu 2004 and 2204
|
||||
|
||||
2004 and 2204 use the [Microsoft Package repository](https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu-install?tabs=dotnet8&pivots=os-linux-ubuntu-2004) to install .NET deb files built and published by the .NET team.
|
||||
|
||||
## .NET Core Versions from Ubuntu 2404
|
||||
|
||||
The .NET Core team have worked with Canonical and Ubuntu now provides its own .NET packages.
|
||||
|
||||
These are the recommended install path and, as-such what is installed on the image.
|
||||
|
||||
> The release of Ubuntu 24.04 is just around the corner. Canonical-produced .NET 6, 7, and 8 packages will be available on day one, for "Noble Numbat". Microsoft will not be publishing .NET packages to the 24.04 feed at packages.microsoft.com.
|
||||
|
||||
You can read the [full announcement from .NET team here](https://github.com/dotnet/core/discussions/9258). We'll briefly summarize how this change may impact users of the image.
|
||||
|
||||
### [`Feature Bands`](https://learn.microsoft.com/dotnet/core/porting/versioning-sdk-msbuild-vs)
|
||||
|
||||
Going forward only the `1xx` feature band will be present in the image as Ubuntu only build and publish this band.
|
||||
|
||||
> Most distros, including Ubuntu, stick to the .1xx feature band for the lifetime of a major .NET version. They make this choice because .1xx is (effectively) the "compatibility band". Higher bands can have breaking changes.
|
||||
> This means there will no longer be packages available for .2xx and later feature bands. Such packages have been exclusively available from Microsoft. If users see an incompatibility between .1xx and higher feature bands, we ask that you please report it in the dotnet/sdk repo. [link: dotnet/core discussion](https://github.com/dotnet/core/discussions/9258)
|
||||
|
||||
If you need a higher feature band for your Actions the recommendation is to use the [`setup-dotnet`](https://github.com/actions/setup-dotnet) action to install the desired version.
|
||||
|
||||
### .NET MAUI
|
||||
|
||||
.NET MAUI is [not included](https://github.com/dotnet/core/discussions/9258#discussioncomment-9548857) in the Ubuntu .NET package. There is work [ongoing to fix.](https://github.com/dotnet/core/discussions/9258#discussioncomment-9548857)
|
||||
|
||||
You should be able to resolve this by using the [`setup-dotnet`](https://github.com/actions/setup-dotnet) action to install the desired version.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Build an image.
|
||||
|
||||
## Create required Azure resources and generate an image
|
||||
|
||||
1. Generating required Azure resources and running a packer build for a targeted image is automated [here](../helpers/GenerateResourcesAndImage.ps1).
|
||||
|
||||
## Create a VM based on the template created by Packer
|
||||
|
||||
1. At the end of the output from running Packer above is a URL to the VM resource template with a read access token. It ends with `.json` plus a query string. Note this URL. For now, it seems like there is an [Az CLI bug](https://github.com/Azure/azure-cli/issues/5899) with specifying the template through a URI. So download the template locally and note the path of the template file.
|
||||
1. Generating the required Azure resources and creating the VM is automated [here](../helpers/CreateAzureVMFromPackerTemplate.ps1).
|
||||
1. After the VM is created, remote into it using its public IP address. You can test the installed tools or install the Azure Pipelines agent and connect it to your Azure DevOps organization.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Debugging Failed Packer Builds
|
||||
|
||||
## Step 1: Run packer build `-on-error=ask`
|
||||
When you run the `packer build` command, give it the `-on-error=ask` flag.
|
||||
By default, `packer build` will delete the resource group as soon as the build fails.
|
||||
`-on-error=ask` will pause it and wait for your input so you have time to remote in to the VM and diagnose the failure.
|
||||
|
||||
When the build fails, you will see this:
|
||||
|
||||

|
||||
|
||||
## Step 2: Find the resource group name in the build log
|
||||
At the beginning of the build log (written to console), find the resource group name for the VM:
|
||||
|
||||

|
||||
|
||||
Log into the Azure Portal. Find that resource group under `Resource groups`. You should see the resources for the Packer build:
|
||||
|
||||

|
||||
|
||||
## Step 3: Connect to the VM
|
||||
Select the VM in the resource group. Click `Connect:`
|
||||
|
||||
This will download an RDP file. Open that and enter the credentials found in the JSON file you pass to `packer build`:
|
||||
|
||||

|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
@@ -1,42 +0,0 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# A JSON schema validator which supports outputting line numbers for errors
|
||||
# this allows us to put annotations on builds for errors in the JSON files
|
||||
# `Test-Json` built in cmdline doesn't. No existing cli tool supports this
|
||||
# that I could find either. See: https://github.com/lawrencegripper/gripdev-json-schema-validator
|
||||
Install-Module -Name GripDevJsonSchemaValidator -Force -Scope CurrentUser
|
||||
|
||||
# Find all toolset JSON files
|
||||
$toolsetFiles = Get-ChildItem -Recurse -Filter "toolset-*.json" | Where-Object { $_.Name -notlike "*schema.json" }
|
||||
$schemaFilePath = "./schemas/toolset-schema.json"
|
||||
|
||||
$toolsetHasErrors = $false
|
||||
foreach ($file in $toolsetFiles) {
|
||||
Write-Host ""
|
||||
Write-Host "🔍 Validating $($file.FullName)" -ForegroundColor Cyan
|
||||
|
||||
$validationResult = Test-JsonSchema -SchemaPath $schemaFilePath -JsonPath $file.FullName -PrettyPrint $false
|
||||
|
||||
if ($validationResult.Valid) {
|
||||
Write-Host "✅ JSON is valid." -ForegroundColor Green
|
||||
} else {
|
||||
# File has been modified since the commit, enforce validation
|
||||
$toolsetHasErrors = $true
|
||||
Write-Host "`n❌ JSON validation failed!" -ForegroundColor Red
|
||||
Write-Host " Found the following errors:`n" -ForegroundColor Yellow
|
||||
|
||||
$validationResult.Errors | ForEach-Object {
|
||||
Write-Host $_.UserMessage
|
||||
if ($env:GITHUB_ACTIONS -eq 'true') {
|
||||
Write-Host "Adding annotation"
|
||||
Write-Host "::error file=$($file.Name),line=$($_.LineNumber)::$($_.UserMessage.Replace("`n", '%0A'))"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($toolsetHasErrors) {
|
||||
Write-Error "One or more toolset JSON files failed schema validation. See the error output above for more details."
|
||||
} else {
|
||||
Write-Host "Schema validation completed successfully"
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Find all toolset JSON files
|
||||
$toolsetFiles = Get-ChildItem -Recurse -Filter "toolset-*.json" | Where-Object { $_.Name -notlike "*schema.json" }
|
||||
|
||||
$expiringPins = @()
|
||||
$now = Get-Date
|
||||
$warningDays = 30 # Warn if expiring within 30 days
|
||||
|
||||
foreach ($file in $toolsetFiles) {
|
||||
Write-Host "Processing $($file.Name)"
|
||||
$content = Get-Content $file.FullName | ConvertFrom-Json
|
||||
|
||||
# Recursively search for pinnedDetails in the JSON
|
||||
function Search-PinnedDetails {
|
||||
param($obj, $path)
|
||||
|
||||
$foundPins = @()
|
||||
|
||||
if ($obj -is [System.Management.Automation.PSCustomObject]) {
|
||||
foreach ($prop in $obj.PSObject.Properties) {
|
||||
if ($prop.Name -eq "pinnedDetails") {
|
||||
Write-Host "Found pinned version at $path"
|
||||
$reviewAt = [DateTime]::Parse($prop.Value.'review-at')
|
||||
$daysUntilExpiry = ($reviewAt - $now).Days
|
||||
|
||||
if ($daysUntilExpiry -lt $warningDays) {
|
||||
Write-Host "Adding to expiringPins array"
|
||||
$foundPins += @{
|
||||
Path = $path
|
||||
File = $file.Name
|
||||
ReviewAt = $reviewAt
|
||||
DaysUntilExpiry = $daysUntilExpiry
|
||||
Reason = $prop.Value.reason
|
||||
Link = $prop.Value.link
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$foundPins += Search-PinnedDetails -obj $prop.Value -path "$path.$($prop.Name)"
|
||||
}
|
||||
}
|
||||
} elseif ($obj -is [Array]) {
|
||||
for ($i = 0; $i -lt $obj.Count; $i++) {
|
||||
$foundPins += Search-PinnedDetails -obj $obj[$i] -path "$path[$i]"
|
||||
}
|
||||
}
|
||||
|
||||
return $foundPins
|
||||
}
|
||||
|
||||
$expiringPins += Search-PinnedDetails -obj $content -path $file.Name
|
||||
}
|
||||
|
||||
if ($expiringPins) {
|
||||
$issueBody = "# Version Pinning Review Required`n`n"
|
||||
$issueBody += "The following pinned versions need review:`n`n"
|
||||
|
||||
foreach ($pin in $expiringPins) {
|
||||
$status = if ($pin.DaysUntilExpiry -lt 0) { "EXPIRED" } else { "Expiring Soon" }
|
||||
$issueBody += "## $($status) - $($pin.Path)`n"
|
||||
$issueBody += "- **File**: $($pin.File)`n"
|
||||
$issueBody += "- **Review Date**: $($pin.ReviewAt.ToString('yyyy-MM-dd'))`n"
|
||||
$issueBody += "- **Days until expiry**: $($pin.DaysUntilExpiry)`n"
|
||||
$issueBody += "- **Reason**: $($pin.Reason)`n"
|
||||
$issueBody += "- **Original PR**: $($pin.Link)`n`n"
|
||||
}
|
||||
|
||||
if ($env:GITHUB_ACTIONS -eq 'true') {
|
||||
# In GitHub Actions, create an issue
|
||||
Write-Host "Creating issue"
|
||||
$tempFile = [System.IO.Path]::GetTempFileName()
|
||||
Set-Content -Path $tempFile -Value $issueBody
|
||||
gh issue create --title "Version Pinning Review Found Expired Pinned Versions" --body-file $tempFile
|
||||
Remove-Item -Path $tempFile
|
||||
}
|
||||
|
||||
Write-Host "`nIssue Content:`n"
|
||||
Write-Host $issueBody
|
||||
}
|
||||
else {
|
||||
Write-Host "No expiring pins found."
|
||||
if ($env:GITHUB_ACTIONS -eq 'true') {
|
||||
"expired_pins=0" >> $env:GITHUB_OUTPUT
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
Function CreateAzureVMFromPackerTemplate {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A helper function to deploy a VM from a generated image.
|
||||
Creates an Azure VM from a template. Also generates network resources in Azure to make the VM accessible.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates an Azure VM from a template. Also generates network resources in Azure to make the VM accessible.
|
||||
Creates Azure resources and kicks off a packer image generation for the selected image type.
|
||||
|
||||
.PARAMETER SubscriptionId
|
||||
The Azure subscription Id where resources will be created.
|
||||
@@ -12,12 +12,12 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
.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 TemplatFilePath
|
||||
The path for the json template generated by packer during image generation locally.
|
||||
|
||||
.PARAMETER VirtualMachineName
|
||||
The name of the virtual machine to be generated.
|
||||
|
||||
|
||||
.PARAMETER AdminUserName
|
||||
The administrator username for the virtual machine to be created.
|
||||
|
||||
@@ -28,7 +28,7 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
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"
|
||||
CreateAzureVMFromPackerTemplate -SubscriptionId {YourSubscriptionId} -ResourceGroupName {ResourceGroupName} -TemplateFile "C:\BuildVmImages\temporaryTemplate.json" -VirtualMachineName "testvm1" -AdminUsername "shady1" -AdminPassword "SomeSecurePassword1" -AzureLocation "eastus"
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $True)]
|
||||
@@ -36,7 +36,7 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $ResourceGroupName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $ManagedImageName,
|
||||
[string] $TemplateFilePath,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $VirtualMachineName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
@@ -48,38 +48,25 @@ Function CreateAzureVMFromPackerTemplate {
|
||||
)
|
||||
|
||||
$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
|
||||
$vnetName = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$subnetName = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$nicName = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$publicIpName = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
|
||||
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 "Creating a Vnet and a Subnet"
|
||||
az network vnet create -g $ResourceGroupName -l $AzureLocation --name $vnetName --address-prefix 10.0.0.0/16 --subscription $subscriptionId
|
||||
az network vnet subnet create -g $ResourceGroupName --vnet-name $vnetName -n $subnetName --address-prefix 10.0.1.0/24 --subscription $subscriptionId
|
||||
|
||||
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)
|
||||
Write-Host "Creating a network interface card (NIC)."
|
||||
$nic = az network nic create -g $ResourceGroupName --vnet-name $vnetName --subnet $subnetName -n $nicName --subscription $subscriptionId
|
||||
$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 "create public IP."
|
||||
az network public-ip create -g $ResourceGroupName -n $publicIpName --subscription $subscriptionId --allocation-method Static --location $AzureLocation --sku Standard --version IPv4
|
||||
|
||||
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 "Adding the public IP to the NIC."
|
||||
az network nic ip-config update --name ipconfig1 --nic-name $nicName --resource-group $ResourceGroupName --subscription $subscriptionId --public-ip-address $publicIpName
|
||||
|
||||
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}"
|
||||
Write-Host "Creating the VM"
|
||||
az group deployment create --resource-group $ResourceGroupName --subscription $subscriptionId --name $VirtualMachineName --template-file $templateFilePath --parameters vmSize=$vmSize vmName=$VirtualMachineName adminUserName=$AdminUsername adminPassword=$AdminPassword networkInterfaceId=$networkId
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
enum ImageType {
|
||||
Windows2019 = 1
|
||||
Windows2022 = 2
|
||||
Windows2025 = 3
|
||||
Ubuntu2204 = 4
|
||||
Ubuntu2404 = 5
|
||||
Windows2016 = 0
|
||||
Windows2019 = 1
|
||||
Ubuntu1604 = 2
|
||||
Ubuntu1804 = 3
|
||||
}
|
||||
|
||||
Function Get-PackerTemplate {
|
||||
Function Get-PackerTemplatePath {
|
||||
param (
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $RepositoryRoot,
|
||||
@@ -16,110 +15,57 @@ Function Get-PackerTemplate {
|
||||
[ImageType] $ImageType
|
||||
)
|
||||
|
||||
$relativePath = "N/A"
|
||||
|
||||
switch ($ImageType) {
|
||||
# Note: Double Join-Path is required to support PowerShell 5.1
|
||||
([ImageType]::Windows2016) {
|
||||
$relativePath = "\images\win\Windows2016-Azure.json"
|
||||
}
|
||||
([ImageType]::Windows2019) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "build.windows-2019.pkr.hcl"
|
||||
$imageOS = "win19"
|
||||
$relativePath = "\images\win\Windows2019-Azure.json"
|
||||
}
|
||||
([ImageType]::Windows2022) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "build.windows-2022.pkr.hcl"
|
||||
$imageOS = "win22"
|
||||
([ImageType]::Ubuntu1604) {
|
||||
$relativePath = "\images\linux\ubuntu1604.json"
|
||||
}
|
||||
([ImageType]::Windows2025) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "windows" "templates") "build.windows-2025.pkr.hcl"
|
||||
$imageOS = "win25"
|
||||
([ImageType]::Ubuntu1804) {
|
||||
$relativePath = "\images\linux\ubuntu1804.json"
|
||||
}
|
||||
([ImageType]::Ubuntu2204) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "build.ubuntu-22_04.pkr.hcl"
|
||||
$imageOS = "ubuntu22"
|
||||
}
|
||||
([ImageType]::Ubuntu2404) {
|
||||
$relativeTemplatePath = Join-Path (Join-Path "ubuntu" "templates") "build.ubuntu-24_04.pkr.hcl"
|
||||
$imageOS = "ubuntu24"
|
||||
}
|
||||
default { throw "Unknown type of image" }
|
||||
}
|
||||
|
||||
$imageTemplatePath = [IO.Path]::Combine($RepositoryRoot, "images", $relativeTemplatePath)
|
||||
# Specific template selection using Packer's "-only" functionality
|
||||
$buildName = [IO.Path]::GetFileName($imageTemplatePath).Split(".")[1]
|
||||
|
||||
if (-not (Test-Path $imageTemplatePath)) {
|
||||
throw "Template for image '$ImageType' doesn't exist on path '$imageTemplatePath'."
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
"BuildName" = $buildName
|
||||
"ImageOS" = $imageOS
|
||||
"Path" = [IO.Path]::GetDirectoryName($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
|
||||
return $RepositoryRoot + $relativePath;
|
||||
}
|
||||
|
||||
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.
|
||||
Creates Azure resources and kicks off a packer image generation for the selected image type.
|
||||
|
||||
.PARAMETER SubscriptionId
|
||||
The Azure subscription id where the Azure resources will be created.
|
||||
The Azure subscription Id where resources will be created.
|
||||
|
||||
.PARAMETER ResourceGroupName
|
||||
The name of the resource group to store the resulting artifact. Resource group must already exist.
|
||||
.PARAMETER ImageType
|
||||
The type of image to generate. Valid values are: Windows2019, Windows2022, Windows2025, Ubuntu2204, Ubuntu2404.
|
||||
.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"
|
||||
The Azure resource group name where the Azure resources will be created.
|
||||
|
||||
.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 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.
|
||||
.PARAMETER PluginVersion
|
||||
Specify the version of the packer Azure plugin to use. The default is "2.2.1".
|
||||
The root path of the image generation repository source.
|
||||
|
||||
.PARAMETER ImageType
|
||||
The type of the image being generated. Valid options are: {"Windows2016", "Windows2019", "Ubuntu1604", "Ubuntu1804"}.
|
||||
|
||||
.PARAMETER AzureLocation
|
||||
The location of the resources being created in Azure. For example "East US".
|
||||
|
||||
.PARAMETER Force
|
||||
Delete the resource group if it exists without user confirmation.
|
||||
|
||||
.PARAMETER GithubFeedToken
|
||||
GitHub PAT to download tool packages from GitHub Package Registry
|
||||
|
||||
.EXAMPLE
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\runner-images" -ImageType Ubuntu2204 -AzureLocation "East US"
|
||||
GenerateResourcesAndImage -SubscriptionId {YourSubscriptionId} -ResourceGroupName "shsamytest1" -ImageGenerationRepositoryRoot "C:\virtual-environments" -ImageType Ubuntu1604 -AzureLocation "East US"
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $True)]
|
||||
@@ -127,198 +73,109 @@ Function GenerateResourcesAndImage {
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $ResourceGroupName,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $ImageGenerationRepositoryRoot,
|
||||
[Parameter(Mandatory = $True)]
|
||||
[ImageType] $ImageType,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $ManagedImageName = "Runner-Image-$($ImageType)",
|
||||
[Parameter(Mandatory = $True)]
|
||||
[string] $AzureLocation,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $ImageGenerationRepositoryRoot = $pwd,
|
||||
[int] $SecondsToWaitForServicePrincipalSetup = 30,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[int] $SecondsToWaitForServicePrincipalSetup = 120,
|
||||
[string] $GithubFeedToken,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $AzureClientId,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $AzureClientSecret,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $AzureTenantId,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[string] $PluginVersion = "2.2.1",
|
||||
[Parameter(Mandatory = $False)]
|
||||
[switch] $RestrictToAgentIpAddress,
|
||||
[Parameter(Mandatory = $False)]
|
||||
[ValidateSet("abort", "ask", "cleanup", "run-cleanup-provisioner")]
|
||||
[string] $OnError = "ask",
|
||||
[Parameter(Mandatory = $False)]
|
||||
[hashtable] $Tags = @{}
|
||||
[Switch] $Force
|
||||
)
|
||||
|
||||
Show-LatestCommit -ErrorAction SilentlyContinue
|
||||
|
||||
# Validate packer is installed
|
||||
$PackerBinary = Get-Command "packer"
|
||||
if (-not ($PackerBinary)) {
|
||||
throw "'packer' binary is not found on PATH."
|
||||
if (([string]::IsNullOrEmpty($GithubFeedToken)))
|
||||
{
|
||||
Write-Error "'-GithubFeedToken' parameter is not specified. You have to specify valid GitHub PAT to download tool packages from GitHub Package Registry"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get template path
|
||||
$PackerTemplate = Get-PackerTemplate -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
|
||||
Write-Debug "Template path: $($PackerTemplate.Path)."
|
||||
$builderScriptPath = Get-PackerTemplatePath -RepositoryRoot $ImageGenerationRepositoryRoot -ImageType $ImageType
|
||||
$ServicePrincipalClientSecret = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper();
|
||||
$InstallPassword = $env:UserName + [System.GUID]::NewGuid().ToString().ToUpper();
|
||||
|
||||
# Prepare list of allowed inbound IP addresses
|
||||
if ($RestrictToAgentIpAddress) {
|
||||
$AgentIp = (Invoke-RestMethod https://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 plugins install github.com/hashicorp/azure $PluginVersion
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Packer plugins download failed."
|
||||
}
|
||||
|
||||
Write-Host "Validating packer template..."
|
||||
& $PackerBinary validate `
|
||||
"-only=$($PackerTemplate.BuildName)*" `
|
||||
"-var=client_id=fake" `
|
||||
"-var=client_secret=fake" `
|
||||
"-var=subscription_id=$($SubscriptionId)" `
|
||||
"-var=tenant_id=fake" `
|
||||
"-var=location=$($AzureLocation)" `
|
||||
"-var=image_os=$($PackerTemplate.ImageOS)" `
|
||||
"-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)" `
|
||||
$PackerTemplate.Path
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Packer template validation failed."
|
||||
}
|
||||
Login-AzureRmAccount
|
||||
Set-AzureRmContext -SubscriptionId $SubscriptionId
|
||||
|
||||
$alreadyExists = $true;
|
||||
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."
|
||||
}
|
||||
else {
|
||||
throw "Resource group '$ResourceGroupName' does not exist."
|
||||
}
|
||||
|
||||
# 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)" `
|
||||
-only "$($PackerTemplate.BuildName)*" `
|
||||
-var "client_id=$($ServicePrincipalAppId)" `
|
||||
-var "client_secret=$($ServicePrincipalPassword)" `
|
||||
-var "subscription_id=$($SubscriptionId)" `
|
||||
-var "tenant_id=$($TenantId)" `
|
||||
-var "location=$($AzureLocation)" `
|
||||
-var "image_os=$($PackerTemplate.ImageOS)" `
|
||||
-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)" `
|
||||
$PackerTemplate.Path
|
||||
|
||||
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."
|
||||
Get-AzureRmResourceGroup -Name $ResourceGroupName
|
||||
Write-Verbose "Resource group was found, will delete and recreate it."
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Resource group was not found, will create it."
|
||||
$alreadyExists = $false;
|
||||
}
|
||||
|
||||
if ($alreadyExists) {
|
||||
if($Force -eq $true) {
|
||||
# Cleanup the resource group if it already exitsted before
|
||||
Remove-AzureRmResourceGroup -Name $ResourceGroupName -Force
|
||||
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $AzureLocation
|
||||
} else {
|
||||
$title = "Delete Resource Group"
|
||||
$message = "The resource group you specified already exists. Do you want to clean it up?"
|
||||
|
||||
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
|
||||
"Delete the resource group including all resources."
|
||||
|
||||
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
|
||||
"Keep the resource group and continue."
|
||||
|
||||
$stop = New-Object System.Management.Automation.Host.ChoiceDescription "&Stop", `
|
||||
"Stop the current action."
|
||||
|
||||
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $stop)
|
||||
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
|
||||
|
||||
switch ($result)
|
||||
{
|
||||
0 { Remove-AzureRmResourceGroup -Name $ResourceGroupName -Force; New-AzureRmResourceGroup -Name $ResourceGroupName -Location $AzureLocation }
|
||||
1 { <# Do nothing #> }
|
||||
2 { exit }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $AzureLocation
|
||||
}
|
||||
|
||||
# This script should follow the recommended naming conventions for azure resources
|
||||
$storageAccountName = if($ResourceGroupName.EndsWith("-rg")) {
|
||||
$ResourceGroupName.Substring(0, $ResourceGroupName.Length -3)
|
||||
} else { $ResourceGroupName }
|
||||
|
||||
# Resource group names may contain special characters, that are not allowed in the storage account name
|
||||
$storageAccountName = $storageAccountName.Replace("-", "").Replace("_", "").Replace("(", "").Replace(")", "").ToLower()
|
||||
$storageAccountName += "001"
|
||||
|
||||
New-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -AccountName $storageAccountName -Location $AzureLocation -SkuName "Standard_LRS"
|
||||
|
||||
$spDisplayName = [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
$sp = New-AzureRmADServicePrincipal -DisplayName $spDisplayName -Password (ConvertTo-SecureString $ServicePrincipalClientSecret -AsPlainText -Force)
|
||||
|
||||
$spAppId = $sp.ApplicationId
|
||||
$spClientId = $sp.ApplicationId
|
||||
$spObjectId = $sp.Id
|
||||
Start-Sleep -Seconds $SecondsToWaitForServicePrincipalSetup
|
||||
|
||||
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $spAppId
|
||||
Start-Sleep -Seconds $SecondsToWaitForServicePrincipalSetup
|
||||
$sub = Get-AzureRmSubscription -SubscriptionId $SubscriptionId
|
||||
$tenantId = $sub.TenantId
|
||||
# "", "Note this variable-setting script for running Packer with these Azure resources in the future:", "==============================================================================================", "`$spClientId = `"$spClientId`"", "`$ServicePrincipalClientSecret = `"$ServicePrincipalClientSecret`"", "`$SubscriptionId = `"$SubscriptionId`"", "`$tenantId = `"$tenantId`"", "`$spObjectId = `"$spObjectId`"", "`$AzureLocation = `"$AzureLocation`"", "`$ResourceGroupName = `"$ResourceGroupName`"", "`$storageAccountName = `"$storageAccountName`"", "`$install_password = `"$install_password`"", ""
|
||||
|
||||
packer.exe build -on-error=ask `
|
||||
-var "client_id=$($spClientId)" `
|
||||
-var "client_secret=$($ServicePrincipalClientSecret)" `
|
||||
-var "subscription_id=$($SubscriptionId)" `
|
||||
-var "tenant_id=$($tenantId)" `
|
||||
-var "object_id=$($spObjectId)" `
|
||||
-var "location=$($AzureLocation)" `
|
||||
-var "resource_group=$($ResourceGroupName)" `
|
||||
-var "storage_account=$($storageAccountName)" `
|
||||
-var "install_password=$($InstallPassword)" `
|
||||
-var "github_feed_token=$($GithubFeedToken)" `
|
||||
$builderScriptPath
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
class GithubApi
|
||||
{
|
||||
[string] $Repository
|
||||
[object] hidden $AuthHeader
|
||||
|
||||
GithubApi(
|
||||
[string] $Repository,
|
||||
[string] $AccessToken
|
||||
) {
|
||||
$this.Repository = $Repository
|
||||
$this.AuthHeader = $this.BuildAuth($AccessToken)
|
||||
}
|
||||
|
||||
[object] hidden BuildAuth([string]$AccessToken) {
|
||||
if ([string]::IsNullOrEmpty($AccessToken)) {
|
||||
return $null
|
||||
}
|
||||
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("'':${AccessToken}"))
|
||||
return @{
|
||||
Authorization = "Basic ${base64AuthInfo}"
|
||||
}
|
||||
}
|
||||
|
||||
[string] hidden BuildBaseUrl([string]$Repository, [string]$ApiPrefix) {
|
||||
return "https://$ApiPrefix.github.com/repos/$Repository"
|
||||
}
|
||||
|
||||
[object] GetWorkflowRuns([string]$WorkflowId) {
|
||||
$url = "actions/workflows/$WorkflowId/runs"
|
||||
$response = $this.InvokeRestMethod($url, 'GET', $null, $null)
|
||||
return $response
|
||||
}
|
||||
|
||||
[object] GetWorkflowRun([string]$WorkflowRunId) {
|
||||
$url = "actions/runs/$WorkflowRunId"
|
||||
$response = $this.InvokeRestMethod($url, 'GET', $null, $null)
|
||||
return $response
|
||||
}
|
||||
|
||||
[object] DispatchWorkflow([string]$EventType, [object]$EventPayload) {
|
||||
$url = "dispatches"
|
||||
$body = @{
|
||||
"event_type" = $EventType
|
||||
"client_payload" = $EventPayload
|
||||
} | ConvertTo-Json
|
||||
$response = $this.InvokeRestMethod($url, 'POST', $null, $body)
|
||||
return $response
|
||||
}
|
||||
|
||||
[object] CancelWorkflowRun([string]$workflowRunId) {
|
||||
$url = "actions/runs/$workflowRunId/cancel"
|
||||
$response = $this.InvokeRestMethod($url, 'POST', $null, $null)
|
||||
return $response
|
||||
}
|
||||
|
||||
[string] hidden BuildUrl([string]$url, [string]$RequestParams, [string]$ApiPrefix) {
|
||||
$baseUrl = $this.BuildBaseUrl($this.Repository, $ApiPrefix)
|
||||
if ([string]::IsNullOrEmpty($RequestParams)) {
|
||||
return "$($baseUrl)/$($url)"
|
||||
} else {
|
||||
return "$($baseUrl)/$($url)?$($requestParams)"
|
||||
}
|
||||
}
|
||||
|
||||
[object] hidden InvokeRestMethod(
|
||||
[string] $url,
|
||||
[string] $Method,
|
||||
[string] $RequestParams,
|
||||
[string] $body
|
||||
) {
|
||||
$requestUrl = $this.BuildUrl($url, $RequestParams, "api")
|
||||
$params = @{
|
||||
Method = $Method
|
||||
ContentType = "application/json"
|
||||
Uri = $requestUrl
|
||||
Headers = @{}
|
||||
}
|
||||
if ($this.AuthHeader) {
|
||||
$params.Headers += $this.AuthHeader
|
||||
}
|
||||
if (![string]::IsNullOrEmpty($body)) {
|
||||
$params.Body = $body
|
||||
}
|
||||
|
||||
$response = Invoke-RestMethod @params
|
||||
return $response
|
||||
}
|
||||
}
|
||||
|
||||
function Get-GithubApi {
|
||||
param (
|
||||
[string] $Repository,
|
||||
[string] $AccessToken
|
||||
)
|
||||
|
||||
return [GithubApi]::New($Repository, $AccessToken)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
Param (
|
||||
[Parameter(Mandatory)]
|
||||
[string] $WorkflowRunId,
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Repository,
|
||||
[Parameter(Mandatory)]
|
||||
[string] $AccessToken,
|
||||
[int] $RetryIntervalSeconds = 300,
|
||||
[int] $MaxRetryCount = 0
|
||||
)
|
||||
|
||||
Import-Module (Join-Path $PSScriptRoot "GitHubApi.psm1")
|
||||
|
||||
function Wait-ForWorkflowCompletion($WorkflowRunId, $RetryIntervalSeconds) {
|
||||
do {
|
||||
Start-Sleep -Seconds $RetryIntervalSeconds
|
||||
$workflowRun = $gitHubApi.GetWorkflowRun($WorkflowRunId)
|
||||
} until ($workflowRun.status -eq "completed")
|
||||
|
||||
return $workflowRun
|
||||
}
|
||||
|
||||
$gitHubApi = Get-GithubApi -Repository $Repository -AccessToken $AccessToken
|
||||
|
||||
$attempt = 1
|
||||
do {
|
||||
$finishedWorkflowRun = Wait-ForWorkflowCompletion -WorkflowRunId $WorkflowRunId -RetryIntervalSeconds $RetryIntervalSeconds
|
||||
Write-Host "Workflow run finished with result: $($finishedWorkflowRun.conclusion)"
|
||||
if ($finishedWorkflowRun.conclusion -in ("success", "cancelled", "timed_out")) {
|
||||
break
|
||||
} elseif ($finishedWorkflowRun.conclusion -eq "failure") {
|
||||
if ($attempt -le $MaxRetryCount) {
|
||||
Write-Host "Workflow run will be restarted. Attempt $attempt of $MaxRetryCount"
|
||||
$gitHubApi.ReRunFailedJobs($WorkflowRunId)
|
||||
$attempt += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} while ($true)
|
||||
|
||||
Write-Host "Last result: $($finishedWorkflowRun.conclusion)."
|
||||
"CI_WORKFLOW_RUN_RESULT=$($finishedWorkflowRun.conclusion)" | Out-File -Append -FilePath $env:GITHUB_ENV
|
||||
|
||||
if ($finishedWorkflowRun.conclusion -in ("failure", "cancelled", "timed_out")) {
|
||||
exit 1
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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
|
||||
@@ -1,56 +0,0 @@
|
||||
############################
|
||||
### 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())
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
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]
|
||||
}
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -1,558 +0,0 @@
|
||||
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
@@ -1,603 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
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 |
|
||||
|
||||
'@
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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,77 @@
|
||||
# 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
|
||||
|
||||
jobs:
|
||||
- job:
|
||||
pool: ci-agent-pool
|
||||
timeoutInMinutes: 600
|
||||
variables:
|
||||
- group: Image Generation Variables
|
||||
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
displayName: 'Download custom repository'
|
||||
condition: and(ne(variables['CUSTOM_REPOSITORY_URL'], ''), ne(variables['CUSTOM_REPOSITORY_BRANCH'], ''))
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host "Clean up default repository"
|
||||
Remove-Item -path './*' -Recurse -Force
|
||||
Write-Host "Download $(CUSTOM_REPOSITORY_BRANCH) branch from $(CUSTOM_REPOSITORY_URL)"
|
||||
$env:GIT_REDIRECT_STDERR = '2>&1'
|
||||
git clone $(CUSTOM_REPOSITORY_URL) . -b $(CUSTOM_REPOSITORY_BRANCH) --single-branch --depth 1
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Build VM'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/build-image.ps1
|
||||
arguments: -ResourcesNamePrefix $(Build.BuildId) `
|
||||
-ClientId $(CLIENT_ID) `
|
||||
-ClientSecret $(CLIENT_SECRET) `
|
||||
-Image ${{ parameters.image_type }} `
|
||||
-ResourceGroup $(AZURE_RESOURCE_GROUP) `
|
||||
-StorageAccount $(AZURE_STORAGE_ACCOUNT) `
|
||||
-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) `
|
||||
-GitHubFeedToken $(GITHUB_TOKEN)
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Create release for VM deployment'
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/create-release.ps1
|
||||
arguments: -BuildId $(Build.BuildId) `
|
||||
-Organization $(RELEASE_TARGET_ORGANIZATION) `
|
||||
-DefinitionId $(RELEASE_TARGET_DEFINITION_ID) `
|
||||
-Project $(RELEASE_TARGET_PROJECT) `
|
||||
-ImageName ${{ parameters.image_type }} `
|
||||
-AccessToken $(RELEASE_TARGET_TOKEN)
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Output Readme file content'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$docsPath = Get-ChildItem -Path "images" -Include ${{ parameters.image_readme_name }} -Recurse -Depth 1 | Select-Object -First 1
|
||||
Get-Content -Path $docsPath
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Clean up resources'
|
||||
condition: always()
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: ./images.CI/cleanup.ps1
|
||||
arguments: -ResourcesNamePrefix $(Build.BuildId) `
|
||||
-ClientId $(CLIENT_ID) `
|
||||
-ClientSecret $(CLIENT_SECRET) `
|
||||
-Image ${{ parameters.image_type }} `
|
||||
-SubscriptionId $(AZURE_SUBSCRIPTION) `
|
||||
-TenantId $(AZURE_TENANT)
|
||||
@@ -0,0 +1,20 @@
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: Daily
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: ubuntu1604
|
||||
image_readme_name: Ubuntu1604-README.md
|
||||
@@ -0,0 +1,20 @@
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: Daily
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: ubuntu1804
|
||||
image_readme_name: Ubuntu1804-README.md
|
||||
@@ -0,0 +1,20 @@
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: Daily
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: Windows2016-Azure
|
||||
image_readme_name: Windows2016-Readme.md
|
||||
@@ -0,0 +1,20 @@
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: Daily
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
always: true
|
||||
|
||||
trigger: none
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- template: image-generation.yml
|
||||
parameters:
|
||||
image_type: Windows2019-Azure
|
||||
image_readme_name: Windows2019-Readme.md
|
||||
@@ -0,0 +1,63 @@
|
||||
param(
|
||||
[String] [Parameter (Mandatory=$true)] $Image,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientId,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientSecret,
|
||||
[String] [Parameter (Mandatory=$true)] $GitHubFeedToken,
|
||||
[String] [Parameter (Mandatory=$true)] $ResourcesNamePrefix,
|
||||
[String] [Parameter (Mandatory=$true)] $Location,
|
||||
[String] [Parameter (Mandatory=$true)] $ResourceGroup,
|
||||
[String] [Parameter (Mandatory=$true)] $StorageAccount,
|
||||
[String] [Parameter (Mandatory=$true)] $SubscriptionId,
|
||||
[String] [Parameter (Mandatory=$true)] $TenantId,
|
||||
[String] [Parameter (Mandatory=$true)] $VirtualNetworkName,
|
||||
[String] [Parameter (Mandatory=$true)] $VirtualNetworkRG,
|
||||
[String] [Parameter (Mandatory=$true)] $VirtualNetworkSubnet
|
||||
)
|
||||
|
||||
$TemplatePath = (Get-ChildItem -Path "images" -Include "$Image.json" -Recurse -Depth 2).FullName
|
||||
if (-not $TemplatePath)
|
||||
{
|
||||
Write-Error "'-Image' parameter is not valid. You have to specify correct image type."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TempResourceGroupName = "${ResourcesNamePrefix}_${Image}"
|
||||
$InstallPassword = [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
|
||||
packer validate -syntax-only $TemplatePath
|
||||
|
||||
$SensitiveData = @(
|
||||
'OSType',
|
||||
'StorageAccountLocation',
|
||||
'OSDiskUri',
|
||||
'OSDiskUriReadOnlySas',
|
||||
'TemplateUri',
|
||||
'TemplateUriReadOnlySas',
|
||||
': ->'
|
||||
)
|
||||
|
||||
Write-Host "Show Packer Version"
|
||||
packer --version
|
||||
|
||||
Write-Host "Build $Image VM"
|
||||
packer build -var "capture_name_prefix=$ResourcesNamePrefix" `
|
||||
-var "client_id=$ClientId" `
|
||||
-var "client_secret=$ClientSecret" `
|
||||
-var "install_password=$InstallPassword" `
|
||||
-var "github_feed_token=$GitHubFeedToken" `
|
||||
-var "location=$Location" `
|
||||
-var "resource_group=$ResourceGroup" `
|
||||
-var "storage_account=$StorageAccount" `
|
||||
-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" `
|
||||
$TemplatePath `
|
||||
| Where-Object {
|
||||
#Filter sensitive data from Packer logs
|
||||
$currentString = $_
|
||||
$sensitiveString = $SensitiveData | Where-Object { $currentString -match $_ }
|
||||
$sensitiveString -eq $null
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
param(
|
||||
[String] [Parameter (Mandatory=$true)] $Image,
|
||||
[String] [Parameter (Mandatory=$true)] $ResourcesNamePrefix,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientId,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientSecret,
|
||||
[String] [Parameter (Mandatory=$true)] $SubscriptionId,
|
||||
[String] [Parameter (Mandatory=$true)] $TenantId
|
||||
)
|
||||
|
||||
az login --service-principal --username $ClientId --password $ClientSecret --tenant $TenantId | Out-Null
|
||||
|
||||
$TempResourceGroupName = "${ResourcesNamePrefix}_${Image}"
|
||||
|
||||
$groupExist = az group exists --name $TempResourceGroupName --subscription $SubscriptionId | Out-Null
|
||||
if ($groupExist -eq "true") {
|
||||
Write-Host "Found a match, deleting temporary files"
|
||||
az group delete --name $TempResourceGroupName --subscription $SubscriptionId --yes | Out-Null
|
||||
Write-Host "Temporary group was deleted succesfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "No temporary groups found"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
param(
|
||||
[UInt32] [Parameter (Mandatory)] $BuildId,
|
||||
[String] [Parameter (Mandatory)] $Organization,
|
||||
[String] [Parameter (Mandatory)] $Project,
|
||||
[String] [Parameter (Mandatory)] $ImageName,
|
||||
[String] [Parameter (Mandatory)] $DefinitionId,
|
||||
[String] [Parameter (Mandatory)] $AccessToken
|
||||
)
|
||||
|
||||
$Body = @{
|
||||
definitionId = $DefinitionId
|
||||
variables = @{
|
||||
ImageBuildId = @{
|
||||
value = $BuildId
|
||||
}
|
||||
ImageName = @{
|
||||
value = $ImageName
|
||||
}
|
||||
}
|
||||
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}"
|
||||
}
|
||||
|
||||
$NewRelease = Invoke-RestMethod $URL -Body $Body -Method "POST" -Headers $headers -ContentType "application/json"
|
||||
|
||||
Write-Host "Created release: $($NewRelease._links.web.href)"
|
||||
@@ -1,77 +0,0 @@
|
||||
param(
|
||||
[String] [Parameter (Mandatory=$true)] $TemplatePath,
|
||||
[String] [Parameter (Mandatory=$true)] $BuildTemplateName,
|
||||
[String] [Parameter (Mandatory=$true)] $ClientId,
|
||||
[String] [Parameter (Mandatory=$false)] $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=$true)] $ImageOS, # e.g. "ubuntu22", "ubuntu22" or "win19", "win22", "win25"
|
||||
[String] [Parameter (Mandatory=$false)] $UseAzureCliAuth = "false",
|
||||
[String] [Parameter (Mandatory=$false)] $PluginVersion = "2.3.3",
|
||||
[String] [Parameter (Mandatory=$false)] $VirtualNetworkName,
|
||||
[String] [Parameter (Mandatory=$false)] $VirtualNetworkRG,
|
||||
[String] [Parameter (Mandatory=$false)] $VirtualNetworkSubnet,
|
||||
[String] [Parameter (Mandatory=$false)] $AllowedInboundIpAddresses = "[]",
|
||||
[hashtable] [Parameter (Mandatory=$false)] $Tags = @{}
|
||||
)
|
||||
|
||||
if (-not (Test-Path $TemplatePath))
|
||||
{
|
||||
Write-Error "'-TemplatePath' parameter is not valid. You have to specify correct Template Path"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$buildName = $($BuildTemplateName).Split(".")[1]
|
||||
$InstallPassword = [System.GUID]::NewGuid().ToString().ToUpper()
|
||||
|
||||
$SensitiveData = @(
|
||||
'OSType',
|
||||
'StorageAccountLocation',
|
||||
'OSDiskUri',
|
||||
'OSDiskUriReadOnlySas',
|
||||
'TemplateUri',
|
||||
'TemplateUriReadOnlySas',
|
||||
': ->'
|
||||
)
|
||||
|
||||
$azure_tags = $Tags | ConvertTo-Json -Compress
|
||||
|
||||
Write-Host "Show Packer Version"
|
||||
packer --version
|
||||
|
||||
Write-Host "Download packer plugins"
|
||||
packer plugins install github.com/hashicorp/azure $pluginVersion
|
||||
|
||||
Write-Host "Validate packer template"
|
||||
packer validate -syntax-only -only "$buildName*" $TemplatePath
|
||||
|
||||
Write-Host "Build $buildName VM"
|
||||
packer build -only "$buildName*" `
|
||||
-var "client_id=$ClientId" `
|
||||
-var "client_secret=$ClientSecret" `
|
||||
-var "install_password=$InstallPassword" `
|
||||
-var "location=$Location" `
|
||||
-var "image_os=$ImageOS" `
|
||||
-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)" `
|
||||
-var "use_azure_cli_auth=$UseAzureCliAuth" `
|
||||
-var "azure_tags=$azure_tags" `
|
||||
-color=false `
|
||||
$TemplatePath `
|
||||
| Where-Object {
|
||||
#Filter sensitive data from Packer logs
|
||||
$currentString = $_
|
||||
$sensitiveString = $SensitiveData | Where-Object { $currentString -match $_ }
|
||||
$sensitiveString -eq $null
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
param(
|
||||
[Parameter (Mandatory=$true)] [string] $TempResourceGroupName
|
||||
)
|
||||
|
||||
$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"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
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)"
|
||||
@@ -1,122 +0,0 @@
|
||||
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)"
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
$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,321 @@
|
||||
# Ubuntu 16.04.6 LTS
|
||||
The following software is installed on machines with the 20200406.2 update.
|
||||
***
|
||||
- 7-Zip 9.20
|
||||
- Ansible (ansible 2.9.6)
|
||||
- AzCopy (azcopy 7.3.0-netcore)
|
||||
- Azure CLI (azure-cli 2.3.1)
|
||||
- Azure CLI (azure-devops 0.18.0)
|
||||
- Basic CLI:
|
||||
- curl
|
||||
- dnsutils
|
||||
- file
|
||||
- ftp
|
||||
- iproute2
|
||||
- iputils-ping
|
||||
- jq
|
||||
- libc++-dev
|
||||
- libc++abi-dev
|
||||
- libcurl3
|
||||
- libicu55
|
||||
- libunwind8
|
||||
- locales
|
||||
- netcat
|
||||
- openssh-client
|
||||
- parallel
|
||||
- rsync
|
||||
- shellcheck
|
||||
- sudo
|
||||
- telnet
|
||||
- time
|
||||
- tzdata
|
||||
- unzip
|
||||
- upx
|
||||
- wget
|
||||
- zip
|
||||
- zstd
|
||||
- AWS CLI (aws-cli/1.18.37 Python/2.7.12 Linux/4.15.0-1075-azure botocore/1.15.37)
|
||||
- build-essential
|
||||
- nvm (0.35.3)
|
||||
- Clang 6.0 (6.0.0)
|
||||
- Clang 8 (8.0.0)
|
||||
- Clang 9 (9.0.1)
|
||||
- Swift version 5.2.1 (swift-5.2.1-RELEASE)
|
||||
Target: x86_64-unknown-linux-gnu
|
||||
- CMake (cmake version 3.16.2)
|
||||
- Docker Compose (docker-compose version 1.25.4, build 8d51620a)
|
||||
- Docker-Moby (Docker version 3.0.11+azure, build eb310fca49568dccd87c6136f774ef6fff2a1b51)
|
||||
- .NET Core SDK:
|
||||
- 3.1.201
|
||||
- 3.1.200
|
||||
- 3.1.103
|
||||
- 3.1.101
|
||||
- 3.1.100
|
||||
- 3.0.103
|
||||
- 3.0.102
|
||||
- 3.0.101
|
||||
- 3.0.100
|
||||
- 2.2.402
|
||||
- 2.2.401
|
||||
- 2.2.301
|
||||
- 2.2.300
|
||||
- 2.2.207
|
||||
- 2.2.206
|
||||
- 2.2.205
|
||||
- 2.2.204
|
||||
- 2.2.203
|
||||
- 2.2.202
|
||||
- 2.2.110
|
||||
- 2.2.109
|
||||
- 2.2.108
|
||||
- 2.2.107
|
||||
- 2.2.106
|
||||
- 2.2.105
|
||||
- 2.2.104
|
||||
- 2.2.103
|
||||
- 2.2.102
|
||||
- 2.2.101
|
||||
- 2.2.100
|
||||
- 2.1.805
|
||||
- 2.1.804
|
||||
- 2.1.803
|
||||
- 2.1.802
|
||||
- 2.1.801
|
||||
- 2.1.701
|
||||
- 2.1.700
|
||||
- 2.1.610
|
||||
- 2.1.609
|
||||
- 2.1.608
|
||||
- 2.1.607
|
||||
- 2.1.606
|
||||
- 2.1.605
|
||||
- 2.1.604
|
||||
- 2.1.603
|
||||
- 2.1.602
|
||||
- 2.1.513
|
||||
- 2.1.512
|
||||
- 2.1.511
|
||||
- 2.1.510
|
||||
- 2.1.509
|
||||
- 2.1.508
|
||||
- 2.1.507
|
||||
- 2.1.506
|
||||
- 2.1.505
|
||||
- 2.1.504
|
||||
- 2.1.503
|
||||
- 2.1.502
|
||||
- 2.1.500
|
||||
- 2.1.403
|
||||
- 2.1.402
|
||||
- 2.1.401
|
||||
- 2.1.302
|
||||
- 2.1.301
|
||||
- 2.1.300
|
||||
- Erlang (Erlang (SMP,ASYNC_THREADS,HIPE) (BEAM) emulator version 10.7)
|
||||
- Firefox (Mozilla Firefox 74.0.1)
|
||||
- Geckodriver (0.26.0); Gecko Driver is available via GECKOWEBDRIVER environment variable
|
||||
- GNU C++ 7.4.0
|
||||
- GNU C++ 8.3.0
|
||||
- GNU C++ 9.2.1
|
||||
- GNU Fortran 8.3.0
|
||||
- GNU Fortran 9.2.1
|
||||
- Git (2.26.0)
|
||||
- Git Large File Storage (LFS) (2.10.0)
|
||||
- Hub CLI (2.14.2)
|
||||
- Google Chrome (Google Chrome 80.0.3987.163 )
|
||||
- ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882}); Chrome Driver is available via CHROMEWEBDRIVER environment variable
|
||||
- Google Cloud SDK (287.0.0)
|
||||
- Haskell Cabal (cabal-install version 2.0.0.1
|
||||
compiled using version 2.0.1.1 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 2.2.0.0
|
||||
compiled using version 2.2.0.1 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 2.4.1.0
|
||||
compiled using version 2.4.1.0 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 3.0.1.0
|
||||
compiled using version 3.0.2.0 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 3.2.0.0
|
||||
compiled using version 3.2.0.0 of the Cabal library )
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.0.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.2.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.4.4)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.3)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.4)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.5)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.8.1)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.8.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.8.3)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.10.1)
|
||||
- Haskell Stack (Version 2.1.3, Git revision 636e3a759d51127df2b62f90772def126cdf6d1f (7735 commits) x86_64 hpack-0.31.2)
|
||||
- Heroku (heroku/7.39.2 linux-x64 node-v12.13.0)
|
||||
- HHVM (HipHop VM 4.52.0 (rel))
|
||||
- ImageMagick
|
||||
- Azul Zulu OpenJDK:
|
||||
- 7 (openjdk version "1.7.0_252")
|
||||
- 8 (openjdk version "1.8.0_242") (default)
|
||||
- 11 (openjdk version "11.0.6" 2020-01-14 LTS)
|
||||
- 12 (openjdk version "12.0.2" 2019-07-16)
|
||||
- Ant (Apache Ant(TM) version 1.9.6 compiled on July 20 2018)
|
||||
- Gradle 6.3
|
||||
- Maven (Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f))
|
||||
- Kind (kind v0.7.0 go1.13.6 linux/amd64)
|
||||
- kubectl (error: Missing or incomplete configuration info. Please point to an existing, complete config file:)
|
||||
- helm (v3.1.2+gd878d4d)
|
||||
- Leiningen (Leiningen 2.9.3 on Java 1.8.0_242 OpenJDK 64-Bit Server VM)
|
||||
- Mercurial (Mercurial Distributed SCM (version 4.4.1))
|
||||
- Miniconda (conda 4.8.2)
|
||||
- Mono (Mono JIT compiler version 6.8.0.105 (tarball Tue Feb 4 21:20:35 UTC 2020))
|
||||
- MySQL (mysql Ver 14.14 Distrib 5.7.29, for Linux (x86_64) using EditLine wrapper)
|
||||
- MySQL Server (user:root password:root)
|
||||
- MS SQL Server Client Tools
|
||||
- Node.js (v12.16.1)
|
||||
- Grunt (grunt-cli v1.2.0)
|
||||
- Gulp (CLI version: 2.2.0
|
||||
Local version: Unknown)
|
||||
- n (6.4.0)
|
||||
- Parcel (1.12.4)
|
||||
- TypeScript (Version 3.8.3)
|
||||
- Webpack (4.42.1)
|
||||
- Webpack CLI (3.3.11)
|
||||
- Yarn (1.22.4)
|
||||
- Bazel (bazel 3.0.0)
|
||||
- Bazelisk (1.3.0)
|
||||
- PhantomJS (2.1.1)
|
||||
- PHP 5.6 (PHP 5.6.40-26+ubuntu16.04.1+deb.sury.org+1 (cli) )
|
||||
- PHP 7.0 (PHP 7.0.33-26+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Mar 20 2020 15:00:31) ( NTS ))
|
||||
- PHP 7.1 (PHP 7.1.33-14+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Mar 20 2020 13:57:17) ( NTS ))
|
||||
- PHP 7.2 (PHP 7.2.29-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Mar 20 2020 13:54:16) ( NTS ))
|
||||
- PHP 7.3 (PHP 7.3.16-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Mar 20 2020 13:51:21) ( NTS ))
|
||||
- PHP 7.4 (PHP 7.4.4 (cli) (built: Mar 20 2020 13:47:17) ( NTS ))
|
||||
- Composer (Composer version 1.10.1 2020-03-13 20:34:27)
|
||||
- PHPUnit (PHPUnit 7.5.20 by Sebastian Bergmann and contributors.)
|
||||
- Pollinate
|
||||
- psql (PostgreSQL) 9.5.21
|
||||
- Powershell (PowerShell 7.0.0)
|
||||
- ruby (2.3.1p112)
|
||||
- gem (3.1.2)
|
||||
- rustup (1.21.1)
|
||||
- rust (1.42.0)
|
||||
- cargo (1.42.0)
|
||||
- rustfmt (1.4.11-stable)
|
||||
- clippy (0.0.212)
|
||||
- rustdoc (1.42.0)
|
||||
- bindgen (0.53.2)
|
||||
- cbindgen (0.14.0)
|
||||
- Julia (julia version 1.4.0)
|
||||
- sbt (1.3.9)
|
||||
- Selenium server standalone (available via SELENIUM_JAR_PATH environment variable)
|
||||
- Sphinx Open Source Search Server
|
||||
- Subversion (svn, version 1.9.3 (r1718519))
|
||||
- Terraform (Terraform v0.12.24)
|
||||
- Packer (1.5.5)
|
||||
- Vcpkg 2020.02.04-unknownhash
|
||||
- Zeit Now CLI (17.1.1)
|
||||
- MongoDB on Linux (2020-04-07T01:29:52.795+0000 I CONTROL [initandlisten] db version v4.2.5
|
||||
2020-04-07T01:29:52.795+0000 I CONTROL [initandlisten] git version: 2261279b51ea13df08ae708ff278f0679c59dc32
|
||||
2020-04-07T01:29:52.795+0000 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.0.2g 1 Mar 2016)
|
||||
- Go 1.11 (go version go1.11.13 linux/amd64)
|
||||
- Go 1.12 (go version go1.12.17 linux/amd64)
|
||||
- Go 1.13 (go version go1.13.9 linux/amd64)
|
||||
- Go 1.14 (go version go1.14.1 linux/amd64)
|
||||
- Google Repository 58
|
||||
- Google Play services 49
|
||||
- Google APIs 24
|
||||
- Google APIs 23
|
||||
- Google APIs 22
|
||||
- Google APIs 21
|
||||
- CMake 3.10.2.4988404
|
||||
3.6.4111459
|
||||
- Android Support Repository 47.0.0
|
||||
- Android Solver for ConstraintLayout 1.0.2
|
||||
- Android Solver for ConstraintLayout 1.0.1
|
||||
- Android SDK Platform-Tools 29.0.6
|
||||
- Android SDK Platform 29
|
||||
- Android SDK Platform 28
|
||||
- Android SDK Platform 27
|
||||
- Android SDK Platform 26
|
||||
- Android SDK Platform 25
|
||||
- Android SDK Platform 24
|
||||
- Android SDK Platform 23
|
||||
- Android SDK Platform 22
|
||||
- Android SDK Platform 21
|
||||
- Android SDK Platform 19
|
||||
- Android SDK Platform 17
|
||||
- Android SDK Platform 15
|
||||
- Android SDK Platform 10
|
||||
- Android SDK Patch Applier v4
|
||||
- Android SDK Build-Tools 29.0.3
|
||||
- Android SDK Build-Tools 29.0.2
|
||||
- Android SDK Build-Tools 29.0.0
|
||||
- Android SDK Build-Tools 28.0.3
|
||||
- Android SDK Build-Tools 28.0.2
|
||||
- Android SDK Build-Tools 28.0.1
|
||||
- Android SDK Build-Tools 28.0.0
|
||||
- Android SDK Build-Tools 27.0.3
|
||||
- Android SDK Build-Tools 27.0.2
|
||||
- Android SDK Build-Tools 27.0.1
|
||||
- Android SDK Build-Tools 27.0.0
|
||||
- Android SDK Build-Tools 26.0.3
|
||||
- Android SDK Build-Tools 26.0.2
|
||||
- Android SDK Build-Tools 26.0.1
|
||||
- Android SDK Build-Tools 26.0.0
|
||||
- Android SDK Build-Tools 25.0.3
|
||||
- Android SDK Build-Tools 25.0.2
|
||||
- Android SDK Build-Tools 25.0.1
|
||||
- Android SDK Build-Tools 25.0.0
|
||||
- Android SDK Build-Tools 24.0.3
|
||||
- Android SDK Build-Tools 24.0.2
|
||||
- Android SDK Build-Tools 24.0.1
|
||||
- Android SDK Build-Tools 24.0.0
|
||||
- Android SDK Build-Tools 23.0.3
|
||||
- Android SDK Build-Tools 23.0.2
|
||||
- Android SDK Build-Tools 23.0.1
|
||||
- Android SDK Build-Tools 22.0.1
|
||||
- Android SDK Build-Tools 21.1.2
|
||||
- Android SDK Build-Tools 20.0.0
|
||||
- Android SDK Build-Tools 19.1.0
|
||||
- Android SDK Build-Tools 17.0.0
|
||||
- Android NDK 21.0.6113669
|
||||
- Android ConstraintLayout 1.0.2
|
||||
- Android ConstraintLayout 1.0.1
|
||||
- Az Module (1.0.0)
|
||||
- Az Module (1.6.0)
|
||||
- Az Module (2.3.2)
|
||||
- Az Module (2.6.0)
|
||||
- Az Module (2.8.0)
|
||||
- Az Module (3.1.0)
|
||||
- Az Module (3.5.0)
|
||||
- Cached container images
|
||||
- node:10 (Digest: sha256:816cfaee24dc2cea534e21d7f9c55f3b22c8bc6af61d8445f8d0178168ef3b28)
|
||||
- node:12 (Digest: sha256:46f4c17e1edbde36d60a9f6362db7912cfe301bac89afef7cc92421ab3e7ca18)
|
||||
- buildpack-deps:stretch (Digest: sha256:a711a64b26285b42cd02618a191ec6c99650449f698858e0e2cc0535ab85e4dd)
|
||||
- debian:9 (Digest: sha256:344470192d10cf7cf0bedcb183effbe99065829a8dbe68e65e703faf46fc10c1)
|
||||
- debian:8 (Digest: sha256:a77aa078ad4769799286bba5fc198deda5bfd4d0144a24e6aa0c8dddf0a22833)
|
||||
- node:10-alpine (Digest: sha256:9a88e3bc3f845b74d2fd8adcbc64608736a8be4a3e9dc7aa34fa743e3677a552)
|
||||
- node:12-alpine (Digest: sha256:6b5b783c9cfe229af0bd5b0b677dd32005bb22d58465f3d0fe7fbd1c60ce068c)
|
||||
- alpine:3.8 (Digest: sha256:2bb501e6173d9d006e56de5bce2720eb06396803300fe1687b58a7ff32bf4c14)
|
||||
- alpine:3.9 (Digest: sha256:115731bab0862031b44766733890091c17924f9b7781b79997f5f163be262178)
|
||||
- alpine:3.10 (Digest: sha256:7c3773f7bcc969f03f8f653910001d99a9d324b4b9caa008846ad2c3089f5a5f)
|
||||
- jekyll/builder:latest (Digest: sha256:875de39aa06300c749e26a79f39cdfa31404e4f2dbd2953cd4ebb0442bffda9e)
|
||||
- alpine:3.7 (Digest: sha256:8421d9a84432575381bfabd248f1eb56f3aa21d9d7cd2511583c68c9b7511d10)
|
||||
- mcr.microsoft.com/azure-pipelines/node8-typescript:latest (Digest: sha256:e52e60b9f71183969830a3664279b5d8c799b4b0ec2c25a0686f7c02f6a9669a)
|
||||
- Python:
|
||||
- Python 2.7.17
|
||||
- Python 3.5.9
|
||||
- Python 3.6.10
|
||||
- Python 3.7.6
|
||||
- Python 3.8.2
|
||||
- Ruby:
|
||||
- Ruby 2.4.10
|
||||
- Ruby 2.5.8
|
||||
- Ruby 2.6.6
|
||||
- Ruby 2.7.1
|
||||
- PyPy:
|
||||
- PyPy 2.7.17
|
||||
- PyPy 3.6.9
|
||||
- Python (Python 2.7.12)
|
||||
- pip (pip 8.1.1 from /usr/lib/python2.7/dist-packages (python 2.7))
|
||||
- Python3 (Python 3.5.2)
|
||||
- pip3 (pip 8.1.1 from /usr/lib/python3/dist-packages (python 3.5))
|
||||
- Boost C++ Libraries 1.69.0
|
||||
- Boost C++ Libraries 1.72.0
|
||||
@@ -0,0 +1,323 @@
|
||||
# Ubuntu 18.04.4 LTS
|
||||
The following software is installed on machines with the 20200428.2 update.
|
||||
***
|
||||
- Homebrew on Linux (Homebrew 2.2.13
|
||||
Homebrew/linuxbrew-core (git revision ab12a; last commit 2020-04-28))
|
||||
- 7-Zip 16.02
|
||||
- Ansible (ansible 2.9.7)
|
||||
- AzCopy (azcopy 7.3.0-netcore)
|
||||
- Azure CLI (azure-cli 2.5.0)
|
||||
- Azure CLI (azure-devops 3.1.0)
|
||||
- Basic CLI:
|
||||
- curl
|
||||
- dnsutils
|
||||
- file
|
||||
- ftp
|
||||
- iproute2
|
||||
- iputils-ping
|
||||
- jq
|
||||
- libcurl3
|
||||
- libgbm-dev
|
||||
- libicu55
|
||||
- libunwind8
|
||||
- locales
|
||||
- netcat
|
||||
- openssh-client
|
||||
- parallel
|
||||
- rsync
|
||||
- shellcheck
|
||||
- sudo
|
||||
- telnet
|
||||
- time
|
||||
- tzdata
|
||||
- unzip
|
||||
- upx
|
||||
- wget
|
||||
- zip
|
||||
- zstd
|
||||
- Alibaba Cloud CLI (3.0.39)
|
||||
- AWS CLI (aws-cli/1.18.47 Python/2.7.17 Linux/5.0.0-1036-azure botocore/1.15.47)
|
||||
- build-essential
|
||||
- Clang 6.0 (6.0.0)
|
||||
- Clang 8 (8.0.0)
|
||||
- Clang 9 (9.0.0)
|
||||
- Swift version 5.2.2 (swift-5.2.2-RELEASE)
|
||||
Target: x86_64-unknown-linux-gnu
|
||||
- CMake (cmake version 3.17.0)
|
||||
- Podman (podman --version)
|
||||
Buildah (buildah --version)
|
||||
Skopeo (skopeo --version)
|
||||
- Docker Compose (docker-compose version 1.25.4, build 8d51620a)
|
||||
- Docker-Moby (Docker version 3.0.11+azure, build eb310fca49568dccd87c6136f774ef6fff2a1b51)
|
||||
- Docker-Buildx (0.3.1+azure)
|
||||
- .NET Core SDK:
|
||||
- 3.1.201
|
||||
- 3.1.200
|
||||
- 3.1.103
|
||||
- 3.1.101
|
||||
- 3.1.100
|
||||
- 3.0.103
|
||||
- 3.0.102
|
||||
- 3.0.101
|
||||
- 3.0.100
|
||||
- 2.2.402
|
||||
- 2.2.401
|
||||
- 2.2.301
|
||||
- 2.2.300
|
||||
- 2.2.207
|
||||
- 2.2.206
|
||||
- 2.2.205
|
||||
- 2.2.204
|
||||
- 2.2.203
|
||||
- 2.2.202
|
||||
- 2.2.110
|
||||
- 2.2.109
|
||||
- 2.2.108
|
||||
- 2.2.107
|
||||
- 2.2.106
|
||||
- 2.2.105
|
||||
- 2.2.104
|
||||
- 2.2.103
|
||||
- 2.2.102
|
||||
- 2.2.101
|
||||
- 2.2.100
|
||||
- 2.1.805
|
||||
- 2.1.804
|
||||
- 2.1.803
|
||||
- 2.1.802
|
||||
- 2.1.801
|
||||
- 2.1.701
|
||||
- 2.1.700
|
||||
- 2.1.610
|
||||
- 2.1.609
|
||||
- 2.1.608
|
||||
- 2.1.607
|
||||
- 2.1.606
|
||||
- 2.1.605
|
||||
- 2.1.604
|
||||
- 2.1.603
|
||||
- 2.1.602
|
||||
- 2.1.513
|
||||
- 2.1.512
|
||||
- 2.1.511
|
||||
- 2.1.510
|
||||
- 2.1.509
|
||||
- 2.1.508
|
||||
- 2.1.507
|
||||
- 2.1.506
|
||||
- 2.1.505
|
||||
- 2.1.504
|
||||
- 2.1.503
|
||||
- 2.1.502
|
||||
- 2.1.500
|
||||
- 2.1.403
|
||||
- 2.1.402
|
||||
- 2.1.401
|
||||
- 2.1.302
|
||||
- 2.1.301
|
||||
- 2.1.300
|
||||
- Erlang (Erlang (SMP,ASYNC_THREADS,HIPE) (BEAM) emulator version 10.7.1)
|
||||
- Firefox (Mozilla Firefox 75.0)
|
||||
- Geckodriver (0.26.0); Gecko Driver is available via GECKOWEBDRIVER environment variable
|
||||
- GNU C++ 7.5.0
|
||||
- GNU C++ 8.4.0
|
||||
- GNU C++ 9.3.0
|
||||
- GNU Fortran 8.4.0
|
||||
- GNU Fortran 9.3.0
|
||||
- Git (2.26.2)
|
||||
- Git Large File Storage (LFS) (2.10.0)
|
||||
- Hub CLI (2.14.2)
|
||||
- Google Chrome (Google Chrome 81.0.4044.129 )
|
||||
- ChromeDriver 81.0.4044.69 (6813546031a4bc83f717a2ef7cd4ac6ec1199132-refs/branch-heads/4044@{#776}); Chrome Driver is available via CHROMEWEBDRIVER environment variable
|
||||
- Google Cloud SDK (290.0.1)
|
||||
- Haskell Cabal (cabal-install version 2.0.0.1
|
||||
compiled using version 2.0.1.1 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 2.2.0.0
|
||||
compiled using version 2.2.0.1 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 2.4.1.0
|
||||
compiled using version 2.4.1.0 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 3.0.1.0
|
||||
compiled using version 3.0.2.0 of the Cabal library )
|
||||
- Haskell Cabal (cabal-install version 3.2.0.0
|
||||
compiled using version 3.2.0.0 of the Cabal library )
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.0.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.2.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.4.4)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.3)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.4)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.6.5)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.8.1)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.8.2)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.8.3)
|
||||
- GHC (The Glorious Glasgow Haskell Compilation System, version 8.10.1)
|
||||
- Haskell Stack (Version 2.1.3, Git revision 636e3a759d51127df2b62f90772def126cdf6d1f (7735 commits) x86_64 hpack-0.31.2)
|
||||
- Heroku (heroku/7.39.5 linux-x64 node-v12.16.2)
|
||||
- HHVM (HipHop VM 4.55.0 (rel))
|
||||
- ImageMagick
|
||||
- Azul Zulu OpenJDK:
|
||||
- 7 (openjdk version "1.7.0_262")
|
||||
- 8 (openjdk version "1.8.0_252") (default)
|
||||
- 11 (openjdk version "11.0.7" 2020-04-14 LTS)
|
||||
- 12 (openjdk version "12.0.2" 2019-07-16)
|
||||
- Ant (Apache Ant(TM) version 1.10.5 compiled on March 28 2019)
|
||||
- Gradle 6.3
|
||||
- Maven (Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f))
|
||||
- Kind (kind v0.7.0 go1.13.6 linux/amd64)
|
||||
- kubectl (error: Missing or incomplete configuration info. Please point to an existing, complete config file:)
|
||||
- helm (v3.2.0+ge11b7ce)
|
||||
- Leiningen (Leiningen 2.9.3 on Java 1.8.0_252 OpenJDK 64-Bit Server VM)
|
||||
- Mercurial (Mercurial Distributed SCM (version 4.5.3))
|
||||
- Miniconda (conda 4.8.2)
|
||||
- Mono (Mono JIT compiler version 6.8.0.105 (tarball Tue Feb 4 21:20:20 UTC 2020))
|
||||
- MySQL (mysql Ver 14.14 Distrib 5.7.29, for Linux (x86_64) using EditLine wrapper)
|
||||
- MySQL Server (user:root password:root)
|
||||
- MS SQL Server Client Tools
|
||||
- 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'
|
||||
- nvm (0.35.3)
|
||||
- Node.js (v12.16.2)
|
||||
- Grunt (grunt-cli v1.2.0)
|
||||
- Gulp (CLI version: 2.2.0
|
||||
Local version: Unknown)
|
||||
- n (6.5.1)
|
||||
- Parcel (1.12.4)
|
||||
- TypeScript (Version 3.8.3)
|
||||
- Webpack (4.43.0)
|
||||
- Webpack CLI (3.3.11)
|
||||
- Yarn (1.22.4)
|
||||
- Bazel (bazel 3.1.0)
|
||||
- Bazelisk (1.4.0)
|
||||
- PhantomJS (2.1.1)
|
||||
- PHP 7.1 (PHP 7.1.33-15+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Apr 19 2020 07:59:27) ( NTS ))
|
||||
- PHP 7.2 (PHP 7.2.30-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Apr 19 2020 07:50:50) ( NTS ))
|
||||
- PHP 7.3 (PHP 7.3.17-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Apr 19 2020 07:44:26) ( NTS ))
|
||||
- PHP 7.4 (PHP 7.4.5 (cli) (built: Apr 19 2020 07:36:30) ( NTS ))
|
||||
- Composer (Composer version 1.10.5 2020-04-10 11:44:22)
|
||||
- PHPUnit (PHPUnit 7.5.20 by Sebastian Bergmann and contributors.)
|
||||
- Pollinate
|
||||
- psql (PostgreSQL) 10.12
|
||||
- Powershell (PowerShell 7.0.0)
|
||||
- ruby (2.5.1p57)
|
||||
- gem (3.1.2)
|
||||
- rustup (1.21.1)
|
||||
- rust (1.43.0)
|
||||
- cargo (1.43.0)
|
||||
- rustfmt (1.4.12-stable)
|
||||
- clippy (0.0.212)
|
||||
- rustdoc (1.43.0)
|
||||
- bindgen (0.53.2)
|
||||
- cbindgen (0.14.1)
|
||||
- Julia (julia version 1.4.1)
|
||||
- sbt (1.3.10)
|
||||
- Selenium server standalone (available via SELENIUM_JAR_PATH environment variable)
|
||||
- Sphinx Open Source Search Server
|
||||
- Subversion (svn, version 1.9.7 (r1800392))
|
||||
- Terraform (Terraform v0.12.24)
|
||||
- Packer (1.5.5)
|
||||
- Vcpkg 2020.02.04-unknownhash
|
||||
- Zeit Now CLI (18.0.0)
|
||||
- MongoDB on Linux (2020-04-28T11:21:15.762+0000 I CONTROL [initandlisten] db version v4.2.6
|
||||
2020-04-28T11:21:15.762+0000 I CONTROL [initandlisten] git version: 20364840b8f1af16917e4c23c1b5f5efd8b352f8
|
||||
2020-04-28T11:21:15.762+0000 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.1.1g 21 Apr 2020)
|
||||
- Haveged 1.9.1-6
|
||||
- AWS SAM CLI, version 0.47.0
|
||||
- Go 1.11 (go version go1.11.13 linux/amd64)
|
||||
- Go 1.12 (go version go1.12.17 linux/amd64)
|
||||
- Go 1.13 (go version go1.13.10 linux/amd64)
|
||||
- Go 1.14 (go version go1.14.2 linux/amd64)
|
||||
- Google Repository 58
|
||||
- Google Play services 49
|
||||
- Google APIs 24
|
||||
- Google APIs 23
|
||||
- Google APIs 22
|
||||
- Google APIs 21
|
||||
- CMake 3.10.2.4988404
|
||||
3.6.4111459
|
||||
- Android Support Repository 47.0.0
|
||||
- Android SDK Platform-Tools 30.0.0
|
||||
- Android SDK Platform 29
|
||||
- Android SDK Platform 28
|
||||
- Android SDK Platform 27
|
||||
- Android SDK Platform 26
|
||||
- Android SDK Platform 25
|
||||
- Android SDK Platform 24
|
||||
- Android SDK Platform 23
|
||||
- Android SDK Platform 22
|
||||
- Android SDK Platform 21
|
||||
- Android SDK Platform 19
|
||||
- Android SDK Platform 17
|
||||
- Android SDK Patch Applier v4
|
||||
- Android SDK Build-Tools 29.0.3
|
||||
- Android SDK Build-Tools 29.0.2
|
||||
- Android SDK Build-Tools 29.0.0
|
||||
- Android SDK Build-Tools 28.0.3
|
||||
- Android SDK Build-Tools 28.0.2
|
||||
- Android SDK Build-Tools 28.0.1
|
||||
- Android SDK Build-Tools 28.0.0
|
||||
- Android SDK Build-Tools 27.0.3
|
||||
- Android SDK Build-Tools 27.0.2
|
||||
- Android SDK Build-Tools 27.0.1
|
||||
- Android SDK Build-Tools 27.0.0
|
||||
- Android SDK Build-Tools 26.0.3
|
||||
- Android SDK Build-Tools 26.0.2
|
||||
- Android SDK Build-Tools 26.0.1
|
||||
- Android SDK Build-Tools 26.0.0
|
||||
- Android SDK Build-Tools 25.0.3
|
||||
- Android SDK Build-Tools 25.0.2
|
||||
- Android SDK Build-Tools 25.0.1
|
||||
- Android SDK Build-Tools 25.0.0
|
||||
- Android SDK Build-Tools 24.0.3
|
||||
- Android SDK Build-Tools 24.0.2
|
||||
- Android SDK Build-Tools 24.0.1
|
||||
- Android SDK Build-Tools 24.0.0
|
||||
- Android SDK Build-Tools 23.0.3
|
||||
- Android SDK Build-Tools 23.0.2
|
||||
- Android SDK Build-Tools 23.0.1
|
||||
- Android SDK Build-Tools 22.0.1
|
||||
- Android SDK Build-Tools 21.1.2
|
||||
- Android SDK Build-Tools 20.0.0
|
||||
- Android SDK Build-Tools 19.1.0
|
||||
- Android SDK Build-Tools 17.0.0
|
||||
- Android NDK 21.1.6352462
|
||||
- Az Module (1.0.0)
|
||||
- Az Module (1.6.0)
|
||||
- Az Module (2.3.2)
|
||||
- Az Module (2.6.0)
|
||||
- Az Module (2.8.0)
|
||||
- Az Module (3.1.0)
|
||||
- Az Module (3.5.0)
|
||||
- Cached container images
|
||||
- node:10-alpine (Digest: sha256:b2db1b026f1b01a6a91f39d8204b89c5c13d2b40a017dd4d9253ab066c9cf2cb)
|
||||
- node:12-alpine (Digest: sha256:f48e315114006f6d94f7661b317156178fadd5a2b6aab060cb2eb2759fe3a65a)
|
||||
- alpine:3.9 (Digest: sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899e1e056fcd349011)
|
||||
- alpine:3.10 (Digest: sha256:f0e9534a598e501320957059cb2a23774b4d4072e37c7b2cf7e95b241f019e35)
|
||||
- jekyll/builder:latest (Digest: sha256:89c952121d275ba475b6611060e2d917434ebe8e125049d647ffefdbef9ca859)
|
||||
- node:10 (Digest: sha256:e0c512c52c3ca8797fc60ada3698004f5cba13af4a8a5968041edac1b5d98577)
|
||||
- node:12 (Digest: sha256:0976c3ee45443edd77ad2c8105debafe4d444767dc1f9cd2efba8fe58c7718cb)
|
||||
- buildpack-deps:stretch (Digest: sha256:38353fdc334fa612f3dedb3d1e8172c3e63de706ca80260856e5708d0fd1146a)
|
||||
- debian:9 (Digest: sha256:666ffd4fbcdff07edcbd65f0ec1dc86ed294f5e3be93eb26280575f77427df46)
|
||||
- debian:8 (Digest: sha256:734728c8e411698485ae644fc988dad06f757565e292b5b85edc084befa37bbb)
|
||||
- alpine:3.8 (Digest: sha256:2bb501e6173d9d006e56de5bce2720eb06396803300fe1687b58a7ff32bf4c14)
|
||||
- ubuntu:14.04 (Digest: sha256:ffc76f71dd8be8c9e222d420dc96901a07b61616689a44c7b3ef6a10b7213de4)
|
||||
- alpine:3.7 (Digest: sha256:8421d9a84432575381bfabd248f1eb56f3aa21d9d7cd2511583c68c9b7511d10)
|
||||
- mcr.microsoft.com/azure-pipelines/node8-typescript:latest (Digest: sha256:e52e60b9f71183969830a3664279b5d8c799b4b0ec2c25a0686f7c02f6a9669a)
|
||||
- Python:
|
||||
- Python 2.7.17
|
||||
- Python 3.5.9
|
||||
- Python 3.6.10
|
||||
- Python 3.7.6
|
||||
- Python 3.8.2
|
||||
- Ruby:
|
||||
- Ruby 2.4.10
|
||||
- Ruby 2.5.8
|
||||
- Ruby 2.6.6
|
||||
- Ruby 2.7.1
|
||||
- PyPy:
|
||||
- PyPy 2.7.17
|
||||
- PyPy 3.6.9
|
||||
- Python (Python 2.7.17)
|
||||
- pip (pip 9.0.1 from /usr/lib/python2.7/dist-packages (python 2.7))
|
||||
- Python3 (Python 3.6.9)
|
||||
- pip3 (pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6))
|
||||
- Boost C++ Libraries 1.69.0
|
||||
- Boost C++ Libraries 1.72.0
|
||||
@@ -0,0 +1,2 @@
|
||||
# Name of the pool supported by this image
|
||||
POOL_NAME="Ubuntu 1604"
|
||||
@@ -1,2 +1,2 @@
|
||||
# Name of pool supported by this image
|
||||
POOL_NAME="Ubuntu 2204"
|
||||
POOL_NAME="Ubuntu 1804"
|
||||
+5
-3
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: Install-TortoiseSvn.ps1
|
||||
## Desc: Install TortoiseSvn
|
||||
## File: reboot.sh
|
||||
## Desc: Reboot VM
|
||||
################################################################################
|
||||
|
||||
Install-ChocoPackage tortoisesvn
|
||||
echo "Reboot VM"
|
||||
sudo reboot
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: repos.sh
|
||||
## Desc: Installs official Microsoft package repos for the distribution
|
||||
################################################################################
|
||||
|
||||
LSB_RELEASE=$(lsb_release -rs)
|
||||
|
||||
# Install Microsoft repository
|
||||
wget https://packages.microsoft.com/config/ubuntu/$LSB_RELEASE/packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common
|
||||
|
||||
# Install Microsoft GPG public key
|
||||
curl -L https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
||||
|
||||
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
|
||||
mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
|
||||
apt-get update
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: apt.sh
|
||||
## Desc: This script contains helper functions for using dpkg and apt
|
||||
################################################################################
|
||||
|
||||
## Use dpkg to figure out if a package has already been installed
|
||||
## Example use:
|
||||
## if ! IsInstalled packageName; then
|
||||
## echo "packageName is not installed!"
|
||||
## fi
|
||||
function IsInstalled {
|
||||
dpkg -S $1 &> /dev/null
|
||||
}
|
||||
|
||||
# Configure apt to always assume Y
|
||||
echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes
|
||||
|
||||
# Use apt-fast for parallel downloads
|
||||
apt-get install aria2
|
||||
add-apt-repository -y ppa:apt-fast/stable
|
||||
apt-get update
|
||||
apt-get -y install apt-fast
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: containercache.sh
|
||||
## Desc: Prepulls Docker images used in build tasks and templates
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
# Check prereqs
|
||||
echo "Checking prereqs for image pulls"
|
||||
if ! command -v docker; then
|
||||
echo "Docker is not installed, cant pull images"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Information output
|
||||
systemctl status docker --no-pager
|
||||
|
||||
# Pull images
|
||||
images=(
|
||||
docker.io/jekyll/builder
|
||||
mcr.microsoft.com/azure-pipelines/node8-typescript
|
||||
)
|
||||
|
||||
for image in "${images[@]}"; do
|
||||
docker pull "$image"
|
||||
done
|
||||
|
||||
## Add container information to the metadata file
|
||||
DocumentInstalledItem "Cached container images"
|
||||
|
||||
while read -r line; do
|
||||
DocumentInstalledItemIndent "$line"
|
||||
done <<< "$(docker images --digests --format '{{.Repository}}:{{.Tag}} (Digest: {{.Digest}})')"
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: document.sh
|
||||
## Desc: Helper functions for writing information to the metadata document
|
||||
################################################################################
|
||||
|
||||
function WriteItem {
|
||||
if [ -z "$METADATA_FILE" ]; then
|
||||
echo "METADATA_FILE environment variable must be set to output to Metadata Document!"
|
||||
return 1;
|
||||
else
|
||||
echo -e "$1" | sudo tee -a "$METADATA_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
function AddTitle {
|
||||
WriteItem "# $1"
|
||||
}
|
||||
|
||||
function AddSubTitle {
|
||||
WriteItem "## $1"
|
||||
}
|
||||
|
||||
function DocumentInstalledItem {
|
||||
WriteItem "- $1"
|
||||
}
|
||||
|
||||
function DocumentInstalledItemIndent {
|
||||
WriteItem " - $1"
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: etc-environment.sh
|
||||
## Desc: Helper functions for source and modify /etc/environment
|
||||
################################################################################
|
||||
|
||||
# NB: sed expression use '%' as a delimiter in order to simplify handling
|
||||
# values containg slashes (i.e. directory path)
|
||||
# The values containing '%' will break the functions
|
||||
|
||||
function getEtcEnvironmentVariable {
|
||||
variable_name="$1"
|
||||
# remove `variable_name=` and possible quotes from the line
|
||||
grep "^${variable_name}=" /etc/environment |sed -E "s%^${variable_name}=\"?([^\"]+)\"?.*$%\1%"
|
||||
}
|
||||
|
||||
function addEtcEnvironmentVariable {
|
||||
variable_name="$1"
|
||||
variable_value="$2"
|
||||
|
||||
echo "$variable_name=\"$variable_value\"" | sudo tee -a /etc/environment
|
||||
}
|
||||
|
||||
function replaceEtcEnvironmentVariable {
|
||||
variable_name="$1"
|
||||
variable_value="$2"
|
||||
|
||||
# modify /etc/environemnt in place by replacing a string that begins with variable_name
|
||||
sudo sed -ie "s%^${variable_name}=.*$%${variable_name}=\"${variable_value}\"%" /etc/environment
|
||||
}
|
||||
|
||||
function setEtcEnvironmentVariable {
|
||||
variable_name="$1"
|
||||
variable_value="$2"
|
||||
|
||||
if grep "$variable_name" /etc/environment > /dev/null; then
|
||||
replaceEtcEnvironmentVariable $variable_name $variable_value
|
||||
else
|
||||
addEtcEnvironmentVariable $variable_name $variable_value
|
||||
fi
|
||||
}
|
||||
|
||||
function prependEtcEnvironmentVariable {
|
||||
variable_name="$1"
|
||||
element="$2"
|
||||
# TODO: handle the case if the variable does not exist
|
||||
existing_value=$(getEtcEnvironmentVariable "${variable_name}")
|
||||
setEtcEnvironmentVariable "${variable_name}" "${element}:${existing_value}"
|
||||
}
|
||||
|
||||
function appendEtcEnvironmentVariable {
|
||||
variable_name="$1"
|
||||
element="$2"
|
||||
# TODO: handle the case if the variable does not exist
|
||||
existing_value=$(getEtcEnvironmentVariable "${variable_name}")
|
||||
setEtcEnvironmentVariable "${variable_name}" "${existing_value}:${element}"
|
||||
}
|
||||
|
||||
function prependEtcEnvironmentPath {
|
||||
element="$1"
|
||||
prependEtcEnvironmentVariable PATH "${element}"
|
||||
}
|
||||
|
||||
function appendEtcEnvironmentPath {
|
||||
element="$1"
|
||||
appendEtcEnvironmentVariable PATH "${element}"
|
||||
}
|
||||
|
||||
# Process /etc/environment as if it were shell script with `export VAR=...` expressions
|
||||
# The PATH variable is handled specially in order to do not override the existing PATH
|
||||
# variable. The value of PATH variable read from /etc/environment is added to the end
|
||||
# of value of the exiting PATH variable exactly as it would happen with real PAM app read
|
||||
# /etc/environment
|
||||
#
|
||||
# TODO: there might be the others variables to be processed in the same way as "PATH" variable
|
||||
# ie MANPATH, INFOPATH, LD_*, etc. In the current implementation the values from /etc/evironments
|
||||
# replace the values of the current environment
|
||||
function reloadEtcEnvironment {
|
||||
# add `export ` to every variable of /etc/environemnt except PATH and eval the result shell script
|
||||
eval $(grep -v '^PATH=' /etc/environment | sed -e 's%^%export %')
|
||||
# handle PATH specially
|
||||
etc_path=$(getEtcEnvironmentVariable PATH)
|
||||
export PATH="$PATH:$etc_path"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: install-helpers.sh
|
||||
## Desc: Helper functions for installing tools
|
||||
################################################################################
|
||||
|
||||
function isUbuntu16
|
||||
{
|
||||
lsb_release -d|grep 'Ubuntu 16' > /dev/null
|
||||
}
|
||||
|
||||
function isUbuntu18
|
||||
{
|
||||
lsb_release -d|grep 'Ubuntu 18' > /dev/null
|
||||
}
|
||||
|
||||
function getOSVersionLabel
|
||||
{
|
||||
isUbuntu16 && echo xenial || echo bionic
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: android.sh
|
||||
## Desc: Installs Android SDK
|
||||
################################################################################
|
||||
|
||||
set -e
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
# Set env variable for SDK Root (https://developer.android.com/studio/command-line/variables)
|
||||
ANDROID_ROOT=/usr/local/lib/android
|
||||
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
|
||||
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" | tee -a /etc/environment
|
||||
|
||||
# ANDROID_HOME is deprecated, but older versions of Gradle rely on it
|
||||
echo "ANDROID_HOME=${ANDROID_SDK_ROOT}" | tee -a /etc/environment
|
||||
|
||||
# Create android sdk directory
|
||||
mkdir -p ${ANDROID_SDK_ROOT}
|
||||
|
||||
# Download the latest command line tools so that we can accept all of the licenses.
|
||||
# See https://developer.android.com/studio/#command-tools
|
||||
wget -O android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip
|
||||
unzip android-sdk.zip -d ${ANDROID_SDK_ROOT}
|
||||
rm -f android-sdk.zip
|
||||
|
||||
# Check sdk manager installation
|
||||
/usr/local/lib/android/sdk/tools/bin/sdkmanager --list 1>/dev/null
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo "Android SDK manager was installed"
|
||||
else
|
||||
echo "Android SDK manager was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Add required permissions
|
||||
chmod -R a+X ${ANDROID_SDK_ROOT}
|
||||
|
||||
# Install the following SDKs and build tools, passing in "y" to accept licenses.
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/tools/bin/sdkmanager \
|
||||
"ndk-bundle" \
|
||||
"platform-tools" \
|
||||
"platforms;android-29" \
|
||||
"platforms;android-28" \
|
||||
"platforms;android-27" \
|
||||
"platforms;android-26" \
|
||||
"platforms;android-25" \
|
||||
"platforms;android-24" \
|
||||
"platforms;android-23" \
|
||||
"platforms;android-22" \
|
||||
"platforms;android-21" \
|
||||
"platforms;android-19" \
|
||||
"platforms;android-17" \
|
||||
"platforms;android-15" \
|
||||
"platforms;android-10" \
|
||||
"build-tools;29.0.3" \
|
||||
"build-tools;29.0.2" \
|
||||
"build-tools;29.0.0" \
|
||||
"build-tools;28.0.3" \
|
||||
"build-tools;28.0.2" \
|
||||
"build-tools;28.0.1" \
|
||||
"build-tools;28.0.0" \
|
||||
"build-tools;27.0.3" \
|
||||
"build-tools;27.0.2" \
|
||||
"build-tools;27.0.1" \
|
||||
"build-tools;27.0.0" \
|
||||
"build-tools;26.0.3" \
|
||||
"build-tools;26.0.2" \
|
||||
"build-tools;26.0.1" \
|
||||
"build-tools;26.0.0" \
|
||||
"build-tools;25.0.3" \
|
||||
"build-tools;25.0.2" \
|
||||
"build-tools;25.0.1" \
|
||||
"build-tools;25.0.0" \
|
||||
"build-tools;24.0.3" \
|
||||
"build-tools;24.0.2" \
|
||||
"build-tools;24.0.1" \
|
||||
"build-tools;24.0.0" \
|
||||
"build-tools;23.0.3" \
|
||||
"build-tools;23.0.2" \
|
||||
"build-tools;23.0.1" \
|
||||
"build-tools;22.0.1" \
|
||||
"build-tools;21.1.2" \
|
||||
"build-tools;20.0.0" \
|
||||
"build-tools;19.1.0" \
|
||||
"build-tools;17.0.0" \
|
||||
"extras;android;m2repository" \
|
||||
"extras;google;m2repository" \
|
||||
"extras;google;google_play_services" \
|
||||
"extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.2" \
|
||||
"extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.1" \
|
||||
"extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2" \
|
||||
"extras;m2repository;com;android;support;constraint;constraint-layout;1.0.1" \
|
||||
"add-ons;addon-google_apis-google-24" \
|
||||
"add-ons;addon-google_apis-google-23" \
|
||||
"add-ons;addon-google_apis-google-22" \
|
||||
"add-ons;addon-google_apis-google-21" \
|
||||
"cmake;3.6.4111459" \
|
||||
"cmake;3.10.2.4988404" \
|
||||
"patcher;v4"
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, document what was added to the metadata file"
|
||||
DocumentInstalledItem "Google Repository $(cat ${ANDROID_SDK_ROOT}/extras/google/m2repository/source.properties 2>&1 | grep Pkg.Revision | cut -d '=' -f 2)"
|
||||
DocumentInstalledItem "Google Play services $(cat ${ANDROID_SDK_ROOT}/extras/google/google_play_services/source.properties 2>&1 | grep Pkg.Revision | cut -d '=' -f 2)"
|
||||
DocumentInstalledItem "Google APIs 24"
|
||||
DocumentInstalledItem "Google APIs 23"
|
||||
DocumentInstalledItem "Google APIs 22"
|
||||
DocumentInstalledItem "Google APIs 21"
|
||||
DocumentInstalledItem "CMake $(ls ${ANDROID_SDK_ROOT}/cmake 2>&1)"
|
||||
DocumentInstalledItem "Android Support Repository 47.0.0"
|
||||
DocumentInstalledItem "Android Solver for ConstraintLayout 1.0.2"
|
||||
DocumentInstalledItem "Android Solver for ConstraintLayout 1.0.1"
|
||||
DocumentInstalledItem "Android SDK Platform-Tools $(cat ${ANDROID_SDK_ROOT}/platform-tools/source.properties 2>&1 | grep Pkg.Revision | cut -d '=' -f 2)"
|
||||
DocumentInstalledItem "Android SDK Platform 29"
|
||||
DocumentInstalledItem "Android SDK Platform 28"
|
||||
DocumentInstalledItem "Android SDK Platform 27"
|
||||
DocumentInstalledItem "Android SDK Platform 26"
|
||||
DocumentInstalledItem "Android SDK Platform 25"
|
||||
DocumentInstalledItem "Android SDK Platform 24"
|
||||
DocumentInstalledItem "Android SDK Platform 23"
|
||||
DocumentInstalledItem "Android SDK Platform 22"
|
||||
DocumentInstalledItem "Android SDK Platform 21"
|
||||
DocumentInstalledItem "Android SDK Platform 19"
|
||||
DocumentInstalledItem "Android SDK Platform 17"
|
||||
DocumentInstalledItem "Android SDK Platform 15"
|
||||
DocumentInstalledItem "Android SDK Platform 10"
|
||||
DocumentInstalledItem "Android SDK Patch Applier v4"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 29.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 29.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 29.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 23.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 23.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 23.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 22.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 21.1.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 20.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 19.1.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 17.0.0"
|
||||
DocumentInstalledItem "Android NDK $(cat ${ANDROID_SDK_ROOT}/ndk-bundle/source.properties 2>&1 | grep Pkg.Revision | cut -d ' ' -f 3)"
|
||||
DocumentInstalledItem "Android ConstraintLayout 1.0.2"
|
||||
DocumentInstalledItem "Android ConstraintLayout 1.0.1"
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: basic.sh
|
||||
## Desc: Installs basic command line utilities and dev packages
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Install basic command-line utilities
|
||||
apt-fast install -y --no-install-recommends \
|
||||
curl \
|
||||
dnsutils \
|
||||
file \
|
||||
ftp \
|
||||
iproute2 \
|
||||
iputils-ping \
|
||||
jq \
|
||||
libc++-dev \
|
||||
libc++abi-dev \
|
||||
libcurl3 \
|
||||
libicu55 \
|
||||
libunwind8 \
|
||||
locales \
|
||||
netcat \
|
||||
openssh-client \
|
||||
parallel \
|
||||
rsync \
|
||||
shellcheck \
|
||||
sudo \
|
||||
telnet \
|
||||
time \
|
||||
tzdata \
|
||||
unzip \
|
||||
upx \
|
||||
wget \
|
||||
zip \
|
||||
zstd
|
||||
|
||||
# Electron / VSCode / GitHub Desktop prereqs
|
||||
apt-fast install -y --no-install-recommends \
|
||||
libxkbfile-dev \
|
||||
pkg-config \
|
||||
libsecret-1-dev \
|
||||
libxss1 \
|
||||
libgconf-2-4 \
|
||||
dbus \
|
||||
xvfb \
|
||||
libgbm-dev \
|
||||
libgtk-3-0 \
|
||||
tk \
|
||||
fakeroot \
|
||||
dpkg \
|
||||
rpm \
|
||||
xz-utils \
|
||||
xorriso \
|
||||
zsync
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
for cmd in curl file ftp jq netcat ssh parallel rsync shellcheck sudo telnet time unzip upx wget zip; do
|
||||
if ! command -v $cmd; then
|
||||
echo "$cmd was not installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "Basic CLI:"
|
||||
DocumentInstalledItemIndent "curl"
|
||||
DocumentInstalledItemIndent "dnsutils"
|
||||
DocumentInstalledItemIndent "file"
|
||||
DocumentInstalledItemIndent "ftp"
|
||||
DocumentInstalledItemIndent "iproute2"
|
||||
DocumentInstalledItemIndent "iputils-ping"
|
||||
DocumentInstalledItemIndent "jq"
|
||||
DocumentInstalledItemIndent "libc++-dev"
|
||||
DocumentInstalledItemIndent "libc++abi-dev"
|
||||
DocumentInstalledItemIndent "libcurl3"
|
||||
DocumentInstalledItemIndent "libgbm-dev"
|
||||
DocumentInstalledItemIndent "libicu55"
|
||||
DocumentInstalledItemIndent "libunwind8"
|
||||
DocumentInstalledItemIndent "locales"
|
||||
DocumentInstalledItemIndent "netcat"
|
||||
DocumentInstalledItemIndent "openssh-client"
|
||||
DocumentInstalledItemIndent "parallel"
|
||||
DocumentInstalledItemIndent "rsync"
|
||||
DocumentInstalledItemIndent "shellcheck"
|
||||
DocumentInstalledItemIndent "sudo"
|
||||
DocumentInstalledItemIndent "telnet"
|
||||
DocumentInstalledItemIndent "time"
|
||||
DocumentInstalledItemIndent "tzdata"
|
||||
DocumentInstalledItemIndent "unzip"
|
||||
DocumentInstalledItemIndent "upx"
|
||||
DocumentInstalledItemIndent "wget"
|
||||
DocumentInstalledItemIndent "zip"
|
||||
DocumentInstalledItemIndent "zstd"
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: kubernetes-tools.sh
|
||||
## Desc: Installs kubectl, helm
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
## Install kubectl
|
||||
apt-get install -y apt-transport-https
|
||||
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
touch /etc/apt/sources.list.d/kubernetes.list
|
||||
echo "deb http://apt.kubernetes.io/ kubernetes-$(lsb_release -cs) main" | tee -a /etc/apt/sources.list.d/kubernetes.list
|
||||
apt-get update
|
||||
apt-get install -y kubectl
|
||||
|
||||
# Install Helm
|
||||
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
if ! command -v kubectl; then
|
||||
echo "kubectl was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v helm; then
|
||||
echo "helm was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Initializing helm"
|
||||
helm init --client-only
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "kubectl ($(kubectl version --short |& head -n 1))"
|
||||
DocumentInstalledItem "helm ($(helm version --short |& head -n 1))"
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: mercurial.sh
|
||||
## Desc: Installs Mercurial
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
# Install Mercurial from the mercurial-ppa/releases repository for the latest version.
|
||||
# https://www.mercurial-scm.org/wiki/Download
|
||||
add-apt-repository ppa:mercurial-ppa/releases -y
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends mercurial
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
if ! command -v hg; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "Mercurial ($(hg --version | head -n 1))"
|
||||
@@ -0,0 +1,310 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: php.sh
|
||||
## Desc: Installs php
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/etc-environment.sh
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
LSB_RELEASE=$(lsb_release -rs)
|
||||
|
||||
set -e
|
||||
|
||||
apt-add-repository ppa:ondrej/php -y
|
||||
|
||||
# Install php5.6
|
||||
apt-get update
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php5.6 \
|
||||
php5.6-bcmath \
|
||||
php5.6-bz2 \
|
||||
php5.6-cgi \
|
||||
php5.6-cli \
|
||||
php5.6-common \
|
||||
php5.6-curl \
|
||||
php5.6-dba \
|
||||
php5.6-dev \
|
||||
php5.6-enchant \
|
||||
php5.6-fpm \
|
||||
php5.6-gd \
|
||||
php5.6-gmp \
|
||||
php5.6-imap \
|
||||
php5.6-interbase \
|
||||
php5.6-intl \
|
||||
php5.6-json \
|
||||
php5.6-ldap \
|
||||
php5.6-mbstring \
|
||||
php5.6-mcrypt \
|
||||
php5.6-mysql \
|
||||
php5.6-odbc \
|
||||
php5.6-opcache \
|
||||
php5.6-pgsql \
|
||||
php5.6-phpdbg \
|
||||
php5.6-pspell \
|
||||
php5.6-readline \
|
||||
php5.6-recode \
|
||||
php5.6-snmp \
|
||||
php5.6-soap \
|
||||
php5.6-sqlite3 \
|
||||
php5.6-sybase \
|
||||
php5.6-tidy \
|
||||
php5.6-xml \
|
||||
php5.6-xmlrpc \
|
||||
php5.6-xsl \
|
||||
php5.6-zip
|
||||
apt-get remove --purge -yq php5.6-dev
|
||||
|
||||
# Install php7.0
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.0 \
|
||||
php7.0-bcmath \
|
||||
php7.0-bz2 \
|
||||
php7.0-cgi \
|
||||
php7.0-cli \
|
||||
php7.0-common \
|
||||
php7.0-curl \
|
||||
php7.0-dba \
|
||||
php7.0-dev \
|
||||
php7.0-enchant \
|
||||
php7.0-fpm \
|
||||
php7.0-gd \
|
||||
php7.0-gmp \
|
||||
php7.0-imap \
|
||||
php7.0-interbase \
|
||||
php7.0-intl \
|
||||
php7.0-json \
|
||||
php7.0-ldap \
|
||||
php7.0-mbstring \
|
||||
php7.0-mcrypt \
|
||||
php7.0-mysql \
|
||||
php7.0-odbc \
|
||||
php7.0-opcache \
|
||||
php7.0-pgsql \
|
||||
php7.0-phpdbg \
|
||||
php7.0-pspell \
|
||||
php7.0-readline \
|
||||
php7.0-recode \
|
||||
php7.0-snmp \
|
||||
php7.0-soap \
|
||||
php7.0-sqlite3 \
|
||||
php7.0-sybase \
|
||||
php7.0-tidy \
|
||||
php7.0-xml \
|
||||
php7.0-xmlrpc \
|
||||
php7.0-xsl \
|
||||
php7.0-zip
|
||||
apt-get remove --purge -yq php7.0-dev
|
||||
|
||||
# Install php7.1
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.1 \
|
||||
php7.1-bcmath \
|
||||
php7.1-bz2 \
|
||||
php7.1-cgi \
|
||||
php7.1-cli \
|
||||
php7.1-common \
|
||||
php7.1-curl \
|
||||
php7.1-dba \
|
||||
php7.1-dev \
|
||||
php7.1-enchant \
|
||||
php7.1-fpm \
|
||||
php7.1-gd \
|
||||
php7.1-gmp \
|
||||
php7.1-imap \
|
||||
php7.1-interbase \
|
||||
php7.1-intl \
|
||||
php7.1-json \
|
||||
php7.1-ldap \
|
||||
php7.1-mbstring \
|
||||
php7.1-mcrypt \
|
||||
php7.1-mysql \
|
||||
php7.1-odbc \
|
||||
php7.1-opcache \
|
||||
php7.1-pgsql \
|
||||
php7.1-phpdbg \
|
||||
php7.1-pspell \
|
||||
php7.1-readline \
|
||||
php7.1-recode \
|
||||
php7.1-snmp \
|
||||
php7.1-soap \
|
||||
php7.1-sqlite3 \
|
||||
php7.1-sybase \
|
||||
php7.1-tidy \
|
||||
php7.1-xml \
|
||||
php7.1-xmlrpc \
|
||||
php7.1-xsl \
|
||||
php7.1-zip
|
||||
apt-get remove --purge -yq php7.1-dev
|
||||
|
||||
# Install php7.2
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.2 \
|
||||
php7.2-bcmath \
|
||||
php7.2-bz2 \
|
||||
php7.2-cgi \
|
||||
php7.2-cli \
|
||||
php7.2-common \
|
||||
php7.2-curl \
|
||||
php7.2-dba \
|
||||
php7.2-dev \
|
||||
php7.2-enchant \
|
||||
php7.2-fpm \
|
||||
php7.2-gd \
|
||||
php7.2-gmp \
|
||||
php7.2-imap \
|
||||
php7.2-interbase \
|
||||
php7.2-intl \
|
||||
php7.2-json \
|
||||
php7.2-ldap \
|
||||
php7.2-mbstring \
|
||||
php7.2-mysql \
|
||||
php7.2-odbc \
|
||||
php7.2-opcache \
|
||||
php7.2-pgsql \
|
||||
php7.2-phpdbg \
|
||||
php7.2-pspell \
|
||||
php7.2-readline \
|
||||
php7.2-recode \
|
||||
php7.2-snmp \
|
||||
php7.2-soap \
|
||||
php7.2-sqlite3 \
|
||||
php7.2-sybase \
|
||||
php7.2-tidy \
|
||||
php7.2-xml \
|
||||
php7.2-xmlrpc \
|
||||
php7.2-xsl \
|
||||
php7.2-zip
|
||||
|
||||
# Install php7.3
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.3 \
|
||||
php7.3-bcmath \
|
||||
php7.3-bz2 \
|
||||
php7.3-cgi \
|
||||
php7.3-cli \
|
||||
php7.3-common \
|
||||
php7.3-curl \
|
||||
php7.3-dba \
|
||||
php7.3-dev \
|
||||
php7.3-enchant \
|
||||
php7.3-fpm \
|
||||
php7.3-gd \
|
||||
php7.3-gmp \
|
||||
php7.3-imap \
|
||||
php7.3-interbase \
|
||||
php7.3-intl \
|
||||
php7.3-json \
|
||||
php7.3-ldap \
|
||||
php7.3-mbstring \
|
||||
php7.3-mysql \
|
||||
php7.3-odbc \
|
||||
php7.3-opcache \
|
||||
php7.3-pgsql \
|
||||
php7.3-phpdbg \
|
||||
php7.3-pspell \
|
||||
php7.3-readline \
|
||||
php7.3-recode \
|
||||
php7.3-snmp \
|
||||
php7.3-soap \
|
||||
php7.3-sqlite3 \
|
||||
php7.3-sybase \
|
||||
php7.3-tidy \
|
||||
php7.3-xml \
|
||||
php7.3-xmlrpc \
|
||||
php7.3-xsl \
|
||||
php7.3-zip
|
||||
|
||||
# Install php7.4
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.4 \
|
||||
php7.4-bcmath \
|
||||
php7.4-bz2 \
|
||||
php7.4-cgi \
|
||||
php7.4-cli \
|
||||
php7.4-common \
|
||||
php7.4-curl \
|
||||
php7.4-dba \
|
||||
php7.4-dev \
|
||||
php7.4-enchant \
|
||||
php7.4-fpm \
|
||||
php7.4-gd \
|
||||
php7.4-gmp \
|
||||
php7.4-imap \
|
||||
php7.4-interbase \
|
||||
php7.4-intl \
|
||||
php7.4-json \
|
||||
php7.4-ldap \
|
||||
php7.4-mbstring \
|
||||
php7.4-mysql \
|
||||
php7.4-odbc \
|
||||
php7.4-opcache \
|
||||
php7.4-pgsql \
|
||||
php7.4-phpdbg \
|
||||
php7.4-pspell \
|
||||
php7.4-readline \
|
||||
php7.4-snmp \
|
||||
php7.4-soap \
|
||||
php7.4-sqlite3 \
|
||||
php7.4-sybase \
|
||||
php7.4-tidy \
|
||||
php7.4-xml \
|
||||
php7.4-xmlrpc \
|
||||
php7.4-xsl \
|
||||
php7.4-zip
|
||||
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php-amqp \
|
||||
php-apcu \
|
||||
php-igbinary \
|
||||
php-memcache \
|
||||
php-memcached \
|
||||
php-mongodb \
|
||||
php-redis \
|
||||
php-xdebug \
|
||||
php-yaml \
|
||||
php-zmq
|
||||
|
||||
apt-get remove --purge -yq php7.2-dev
|
||||
|
||||
apt-fast install -y --no-install-recommends snmp
|
||||
|
||||
# Install composer
|
||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
php -r "if (hash_file('sha384', 'composer-setup.php') === file_get_contents('https://composer.github.io/installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
|
||||
php composer-setup.php
|
||||
sudo mv composer.phar /usr/bin/composer
|
||||
php -r "unlink('composer-setup.php');"
|
||||
|
||||
# Update /etc/environment
|
||||
prependEtcEnvironmentPath /home/runner/.config/composer/vendor/bin
|
||||
|
||||
# Add composer bin folder to path
|
||||
echo 'export PATH="$PATH:$HOME/.config/composer/vendor/bin"' >> /etc/skel/.bashrc
|
||||
|
||||
# Install phpunit (for PHP)
|
||||
wget -q -O phpunit https://phar.phpunit.de/phpunit-7.phar
|
||||
chmod +x phpunit
|
||||
mv phpunit /usr/local/bin/phpunit
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
for cmd in php php5.6 php7.0 php7.1 php7.2 php7.3 php7.4 composer phpunit; do
|
||||
if ! command -v $cmd; then
|
||||
echo "$cmd was not installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "PHP 5.6 ($(php5.6 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.0 ($(php7.0 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.1 ($(php7.1 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.2 ($(php7.2 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.3 ($(php7.3 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.4 ($(php7.4 --version | head -n 1))"
|
||||
DocumentInstalledItem "Composer ($(composer --version))"
|
||||
DocumentInstalledItem "PHPUnit ($(phpunit --version))"
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: preparemetadata.sh
|
||||
## Desc: This script adds a image title information to the metadata
|
||||
## document
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
AddTitle "$(lsb_release -ds)"
|
||||
WriteItem "The following software is installed on machines with the $IMAGE_VERSION update."
|
||||
WriteItem "***"
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: android.sh
|
||||
## Desc: Installs Android SDK
|
||||
################################################################################
|
||||
|
||||
set -e
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
# Set env variable for SDK Root (https://developer.android.com/studio/command-line/variables)
|
||||
ANDROID_ROOT=/usr/local/lib/android
|
||||
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
|
||||
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" | tee -a /etc/environment
|
||||
|
||||
# ANDROID_HOME is deprecated, but older versions of Gradle rely on it
|
||||
echo "ANDROID_HOME=${ANDROID_SDK_ROOT}" | tee -a /etc/environment
|
||||
|
||||
# Create android sdk directory
|
||||
mkdir -p ${ANDROID_SDK_ROOT}
|
||||
|
||||
# Download the latest command line tools so that we can accept all of the licenses.
|
||||
# See https://developer.android.com/studio/#command-tools
|
||||
wget -O android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip
|
||||
unzip android-sdk.zip -d ${ANDROID_SDK_ROOT}
|
||||
rm -f android-sdk.zip
|
||||
|
||||
# Add required permissions
|
||||
chmod -R a+X ${ANDROID_SDK_ROOT}
|
||||
|
||||
# Check sdk manager installation
|
||||
/usr/local/lib/android/sdk/tools/bin/sdkmanager --list 1>/dev/null
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo "Android SDK manager was installed"
|
||||
else
|
||||
echo "Android SDK manager was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install the following SDKs and build tools, passing in "y" to accept licenses.
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/tools/bin/sdkmanager \
|
||||
"ndk-bundle" \
|
||||
"platform-tools" \
|
||||
"platforms;android-29" \
|
||||
"platforms;android-28" \
|
||||
"platforms;android-27" \
|
||||
"platforms;android-26" \
|
||||
"platforms;android-25" \
|
||||
"platforms;android-24" \
|
||||
"platforms;android-23" \
|
||||
"platforms;android-22" \
|
||||
"platforms;android-21" \
|
||||
"platforms;android-19" \
|
||||
"platforms;android-17" \
|
||||
"build-tools;29.0.3" \
|
||||
"build-tools;29.0.2" \
|
||||
"build-tools;29.0.0" \
|
||||
"build-tools;28.0.3" \
|
||||
"build-tools;28.0.2" \
|
||||
"build-tools;28.0.1" \
|
||||
"build-tools;28.0.0" \
|
||||
"build-tools;27.0.3" \
|
||||
"build-tools;27.0.2" \
|
||||
"build-tools;27.0.1" \
|
||||
"build-tools;27.0.0" \
|
||||
"build-tools;26.0.3" \
|
||||
"build-tools;26.0.2" \
|
||||
"build-tools;26.0.1" \
|
||||
"build-tools;26.0.0" \
|
||||
"build-tools;25.0.3" \
|
||||
"build-tools;25.0.2" \
|
||||
"build-tools;25.0.1" \
|
||||
"build-tools;25.0.0" \
|
||||
"build-tools;24.0.3" \
|
||||
"build-tools;24.0.2" \
|
||||
"build-tools;24.0.1" \
|
||||
"build-tools;24.0.0" \
|
||||
"build-tools;23.0.3" \
|
||||
"build-tools;23.0.2" \
|
||||
"build-tools;23.0.1" \
|
||||
"build-tools;22.0.1" \
|
||||
"build-tools;21.1.2" \
|
||||
"build-tools;20.0.0" \
|
||||
"build-tools;19.1.0" \
|
||||
"build-tools;17.0.0" \
|
||||
"extras;android;m2repository" \
|
||||
"extras;google;m2repository" \
|
||||
"extras;google;google_play_services" \
|
||||
"add-ons;addon-google_apis-google-24" \
|
||||
"add-ons;addon-google_apis-google-23" \
|
||||
"add-ons;addon-google_apis-google-22" \
|
||||
"add-ons;addon-google_apis-google-21" \
|
||||
"cmake;3.6.4111459" \
|
||||
"cmake;3.10.2.4988404" \
|
||||
"patcher;v4"
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, document what was added to the metadata file"
|
||||
DocumentInstalledItem "Google Repository $(cat ${ANDROID_SDK_ROOT}/extras/google/m2repository/source.properties 2>&1 | grep Pkg.Revision | cut -d '=' -f 2)"
|
||||
DocumentInstalledItem "Google Play services $(cat ${ANDROID_SDK_ROOT}/extras/google/google_play_services/source.properties 2>&1 | grep Pkg.Revision | cut -d '=' -f 2)"
|
||||
DocumentInstalledItem "Google APIs 24"
|
||||
DocumentInstalledItem "Google APIs 23"
|
||||
DocumentInstalledItem "Google APIs 22"
|
||||
DocumentInstalledItem "Google APIs 21"
|
||||
DocumentInstalledItem "CMake $(ls ${ANDROID_SDK_ROOT}/cmake 2>&1)"
|
||||
DocumentInstalledItem "Android Support Repository 47.0.0"
|
||||
DocumentInstalledItem "Android SDK Platform-Tools $(cat ${ANDROID_SDK_ROOT}/platform-tools/source.properties 2>&1 | grep Pkg.Revision | cut -d '=' -f 2)"
|
||||
DocumentInstalledItem "Android SDK Platform 29"
|
||||
DocumentInstalledItem "Android SDK Platform 28"
|
||||
DocumentInstalledItem "Android SDK Platform 27"
|
||||
DocumentInstalledItem "Android SDK Platform 26"
|
||||
DocumentInstalledItem "Android SDK Platform 25"
|
||||
DocumentInstalledItem "Android SDK Platform 24"
|
||||
DocumentInstalledItem "Android SDK Platform 23"
|
||||
DocumentInstalledItem "Android SDK Platform 22"
|
||||
DocumentInstalledItem "Android SDK Platform 21"
|
||||
DocumentInstalledItem "Android SDK Platform 19"
|
||||
DocumentInstalledItem "Android SDK Platform 17"
|
||||
DocumentInstalledItem "Android SDK Patch Applier v4"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 29.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 29.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 29.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 28.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 27.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 26.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 25.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 24.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 23.0.3"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 23.0.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 23.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 22.0.1"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 21.1.2"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 20.0.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 19.1.0"
|
||||
DocumentInstalledItem "Android SDK Build-Tools 17.0.0"
|
||||
DocumentInstalledItem "Android NDK $(cat ${ANDROID_SDK_ROOT}/ndk-bundle/source.properties 2>&1 | grep Pkg.Revision | cut -d ' ' -f 3)"
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: basic.sh
|
||||
## Desc: Installs basic command line utilities and dev packages
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Install dnsutils"
|
||||
apt-get install -y --no-install-recommends dnsutils
|
||||
|
||||
echo "Install file"
|
||||
apt-get install -y --no-install-recommends file
|
||||
|
||||
echo "Install ftp"
|
||||
apt-get install -y --no-install-recommends ftp
|
||||
|
||||
echo "Install iproute2"
|
||||
apt-get install -y --no-install-recommends iproute2
|
||||
|
||||
echo "Install iputils-ping"
|
||||
apt-get install -y --no-install-recommends iputils-ping
|
||||
|
||||
echo "Install jq"
|
||||
apt-get install -y --no-install-recommends jq
|
||||
|
||||
echo "Install libcurl3"
|
||||
apt-get install -y --no-install-recommends libcurl3
|
||||
|
||||
echo "Install libunwind8"
|
||||
apt-get install -y --no-install-recommends libunwind8
|
||||
|
||||
echo "Install locales"
|
||||
apt-get install -y --no-install-recommends locales
|
||||
|
||||
echo "Install netcat"
|
||||
apt-get install -y --no-install-recommends netcat
|
||||
|
||||
echo "Install openssh-client"
|
||||
apt-get install -y --no-install-recommends openssh-client
|
||||
|
||||
echo "Install rsync"
|
||||
apt-get install -y --no-install-recommends rsync
|
||||
|
||||
echo "Install shellcheck"
|
||||
apt-get install -y --no-install-recommends shellcheck
|
||||
|
||||
echo "Install sudo"
|
||||
apt-get install -y --no-install-recommends sudo
|
||||
|
||||
echo "Install telnet"
|
||||
apt-get install -y --no-install-recommends telnet
|
||||
|
||||
echo "Install time"
|
||||
apt-get install -y --no-install-recommends time
|
||||
|
||||
echo "Install tzdata"
|
||||
apt-get install -y --no-install-recommends tzdata
|
||||
|
||||
echo "Install unzip"
|
||||
apt-get install -y --no-install-recommends unzip
|
||||
|
||||
echo "Install upx"
|
||||
apt-get install -y --no-install-recommends upx
|
||||
|
||||
echo "Install wget"
|
||||
apt-get install -y --no-install-recommends wget
|
||||
|
||||
echo "Install zip"
|
||||
apt-get install -y --no-install-recommends zip
|
||||
|
||||
echo "Install zstd"
|
||||
apt-get install -y --no-install-recommends zstd
|
||||
|
||||
echo "Install libxkbfile"
|
||||
apt-get install -y --no-install-recommends libxkbfile-dev
|
||||
|
||||
echo "Install pkg-config"
|
||||
apt-get install -y --no-install-recommends pkg-config
|
||||
|
||||
echo "Install libsecret-1-dev"
|
||||
apt-get install -y --no-install-recommends libsecret-1-dev
|
||||
|
||||
echo "Install libxss1"
|
||||
apt-get install -y --no-install-recommends libxss1
|
||||
|
||||
echo "Install libgconf-2-4"
|
||||
apt-get install -y --no-install-recommends libgconf-2-4
|
||||
|
||||
echo "Install dbus"
|
||||
apt-get install -y --no-install-recommends dbus
|
||||
|
||||
echo "Install xvfb"
|
||||
apt-get install -y --no-install-recommends xvfb
|
||||
|
||||
echo "Install libgbm-dev"
|
||||
apt-get install -y --no-install-recommends libgbm-dev
|
||||
|
||||
echo "Install libgtk"
|
||||
apt-get install -y --no-install-recommends libgtk-3-0
|
||||
|
||||
echo "Install tk"
|
||||
apt install -y tk
|
||||
|
||||
echo "Install fakeroot"
|
||||
apt-get install -y --no-install-recommends fakeroot
|
||||
|
||||
echo "Install dpkg"
|
||||
apt-get install -y --no-install-recommends dpkg
|
||||
|
||||
echo "Install rpm"
|
||||
apt-get install -y --no-install-recommends rpm
|
||||
|
||||
echo "Install xz-utils"
|
||||
apt-get install -y --no-install-recommends xz-utils
|
||||
|
||||
echo "Install xorriso"
|
||||
apt-get install -y --no-install-recommends xorriso
|
||||
|
||||
echo "Install zsync"
|
||||
apt-get install -y --no-install-recommends zsync
|
||||
|
||||
echo "Install curl"
|
||||
apt-get install -y --no-install-recommends curl
|
||||
|
||||
echo "Install parallel"
|
||||
apt-get install -y --no-install-recommends parallel
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
for cmd in curl file ftp jq netcat ssh parallel rsync shellcheck sudo telnet time unzip wget zip; do
|
||||
if ! command -v $cmd; then
|
||||
echo "$cmd was not installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "Basic CLI:"
|
||||
DocumentInstalledItemIndent "curl"
|
||||
DocumentInstalledItemIndent "dnsutils"
|
||||
DocumentInstalledItemIndent "file"
|
||||
DocumentInstalledItemIndent "ftp"
|
||||
DocumentInstalledItemIndent "iproute2"
|
||||
DocumentInstalledItemIndent "iputils-ping"
|
||||
DocumentInstalledItemIndent "jq"
|
||||
DocumentInstalledItemIndent "libcurl3"
|
||||
DocumentInstalledItemIndent "libgbm-dev"
|
||||
DocumentInstalledItemIndent "libicu55"
|
||||
DocumentInstalledItemIndent "libunwind8"
|
||||
DocumentInstalledItemIndent "locales"
|
||||
DocumentInstalledItemIndent "netcat"
|
||||
DocumentInstalledItemIndent "openssh-client"
|
||||
DocumentInstalledItemIndent "parallel"
|
||||
DocumentInstalledItemIndent "rsync"
|
||||
DocumentInstalledItemIndent "shellcheck"
|
||||
DocumentInstalledItemIndent "sudo"
|
||||
DocumentInstalledItemIndent "telnet"
|
||||
DocumentInstalledItemIndent "time"
|
||||
DocumentInstalledItemIndent "tzdata"
|
||||
DocumentInstalledItemIndent "unzip"
|
||||
DocumentInstalledItemIndent "upx"
|
||||
DocumentInstalledItemIndent "wget"
|
||||
DocumentInstalledItemIndent "zip"
|
||||
DocumentInstalledItemIndent "zstd"
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: containers.sh
|
||||
## Desc: Installs container tools: podman, buildah and skopeo onto the image
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
source /etc/os-release
|
||||
sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/x${NAME}_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/x${NAME}_${VERSION_ID}/Release.key -O Release.key
|
||||
apt-key add - < Release.key
|
||||
apt-get update -qq
|
||||
apt-get -qq -y install podman buildah skopeo
|
||||
mkdir -p /etc/containers
|
||||
echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | tee /etc/containers/registries.conf
|
||||
|
||||
## Add version information to the metadata file
|
||||
echo "Documenting container tools version"
|
||||
PODMAN_VERSION='podman --version'
|
||||
BUILDAH_VERSION='buildah --version'
|
||||
SKOPEO_VERSION='skopeo --version'
|
||||
DocumentInstalledItem "Podman ($PODMAN_VERSION)\nBuildah ($BUILDAH_VERSION)\nSkopeo ($SKOPEO_VERSION)"
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: kubernetes-tools.sh
|
||||
## Desc: Installs kubectl, helm
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
## Install kubectl
|
||||
apt-get install -y apt-transport-https
|
||||
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
touch /etc/apt/sources.list.d/kubernetes.list
|
||||
|
||||
# Based on https://kubernetes.io/docs/tasks/tools/install-kubectl/, package is still called xenial
|
||||
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list
|
||||
apt-get update
|
||||
apt-get install -y kubectl
|
||||
|
||||
# Install Helm
|
||||
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
if ! command -v kubectl; then
|
||||
echo "kubectl was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v helm; then
|
||||
echo "helm was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Initializing helm"
|
||||
helm init --client-only
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "kubectl ($(kubectl version --short |& head -n 1))"
|
||||
DocumentInstalledItem "helm ($(helm version --short |& head -n 1))"
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: mercurial.sh
|
||||
## Desc: Installs Mercurial
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
source $HELPER_SCRIPTS/apt.sh
|
||||
|
||||
apt-get install -y --no-install-recommends mercurial
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
if ! command -v hg; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "Mercurial ($(hg --version | head -n 1))"
|
||||
@@ -0,0 +1,224 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: php.sh
|
||||
## Desc: Installs php
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/etc-environment.sh
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
LSB_RELEASE=$(lsb_release -rs)
|
||||
|
||||
set -e
|
||||
|
||||
apt-add-repository ppa:ondrej/php -y
|
||||
|
||||
# Install php7.1
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.1 \
|
||||
php7.1-bcmath \
|
||||
php7.1-bz2 \
|
||||
php7.1-cgi \
|
||||
php7.1-cli \
|
||||
php7.1-common \
|
||||
php7.1-curl \
|
||||
php7.1-dba \
|
||||
php7.1-dev \
|
||||
php7.1-enchant \
|
||||
php7.1-fpm \
|
||||
php7.1-gd \
|
||||
php7.1-gmp \
|
||||
php7.1-imap \
|
||||
php7.1-interbase \
|
||||
php7.1-intl \
|
||||
php7.1-json \
|
||||
php7.1-ldap \
|
||||
php7.1-mbstring \
|
||||
php7.1-mcrypt \
|
||||
php7.1-mysql \
|
||||
php7.1-odbc \
|
||||
php7.1-opcache \
|
||||
php7.1-pgsql \
|
||||
php7.1-phpdbg \
|
||||
php7.1-pspell \
|
||||
php7.1-readline \
|
||||
php7.1-recode \
|
||||
php7.1-snmp \
|
||||
php7.1-soap \
|
||||
php7.1-sqlite3 \
|
||||
php7.1-sybase \
|
||||
php7.1-tidy \
|
||||
php7.1-xml \
|
||||
php7.1-xmlrpc \
|
||||
php7.1-xsl \
|
||||
php7.1-zip
|
||||
apt-get remove --purge -yq php7.1-dev
|
||||
|
||||
# Install php7.2
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.2 \
|
||||
php7.2-bcmath \
|
||||
php7.2-bz2 \
|
||||
php7.2-cgi \
|
||||
php7.2-cli \
|
||||
php7.2-common \
|
||||
php7.2-curl \
|
||||
php7.2-dba \
|
||||
php7.2-dev \
|
||||
php7.2-enchant \
|
||||
php7.2-fpm \
|
||||
php7.2-gd \
|
||||
php7.2-gmp \
|
||||
php7.2-imap \
|
||||
php7.2-interbase \
|
||||
php7.2-intl \
|
||||
php7.2-json \
|
||||
php7.2-ldap \
|
||||
php7.2-mbstring \
|
||||
php7.2-mysql \
|
||||
php7.2-odbc \
|
||||
php7.2-opcache \
|
||||
php7.2-pgsql \
|
||||
php7.2-phpdbg \
|
||||
php7.2-pspell \
|
||||
php7.2-readline \
|
||||
php7.2-recode \
|
||||
php7.2-snmp \
|
||||
php7.2-soap \
|
||||
php7.2-sqlite3 \
|
||||
php7.2-sybase \
|
||||
php7.2-tidy \
|
||||
php7.2-xml \
|
||||
php7.2-xmlrpc \
|
||||
php7.2-xsl \
|
||||
php7.2-zip
|
||||
|
||||
# Install php7.3
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.3 \
|
||||
php7.3-bcmath \
|
||||
php7.3-bz2 \
|
||||
php7.3-cgi \
|
||||
php7.3-cli \
|
||||
php7.3-common \
|
||||
php7.3-curl \
|
||||
php7.3-dba \
|
||||
php7.3-dev \
|
||||
php7.3-enchant \
|
||||
php7.3-fpm \
|
||||
php7.3-gd \
|
||||
php7.3-gmp \
|
||||
php7.3-imap \
|
||||
php7.3-interbase \
|
||||
php7.3-intl \
|
||||
php7.3-json \
|
||||
php7.3-ldap \
|
||||
php7.3-mbstring \
|
||||
php7.3-mysql \
|
||||
php7.3-odbc \
|
||||
php7.3-opcache \
|
||||
php7.3-pgsql \
|
||||
php7.3-phpdbg \
|
||||
php7.3-pspell \
|
||||
php7.3-readline \
|
||||
php7.3-recode \
|
||||
php7.3-snmp \
|
||||
php7.3-soap \
|
||||
php7.3-sqlite3 \
|
||||
php7.3-sybase \
|
||||
php7.3-tidy \
|
||||
php7.3-xml \
|
||||
php7.3-xmlrpc \
|
||||
php7.3-xsl \
|
||||
php7.3-zip
|
||||
|
||||
# Install php7.4
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php7.4 \
|
||||
php7.4-bcmath \
|
||||
php7.4-bz2 \
|
||||
php7.4-cgi \
|
||||
php7.4-cli \
|
||||
php7.4-common \
|
||||
php7.4-curl \
|
||||
php7.4-dba \
|
||||
php7.4-dev \
|
||||
php7.4-enchant \
|
||||
php7.4-fpm \
|
||||
php7.4-gd \
|
||||
php7.4-gmp \
|
||||
php7.4-imap \
|
||||
php7.4-interbase \
|
||||
php7.4-intl \
|
||||
php7.4-json \
|
||||
php7.4-ldap \
|
||||
php7.4-mbstring \
|
||||
php7.4-mysql \
|
||||
php7.4-odbc \
|
||||
php7.4-opcache \
|
||||
php7.4-pgsql \
|
||||
php7.4-phpdbg \
|
||||
php7.4-pspell \
|
||||
php7.4-readline \
|
||||
php7.4-snmp \
|
||||
php7.4-soap \
|
||||
php7.4-sqlite3 \
|
||||
php7.4-sybase \
|
||||
php7.4-tidy \
|
||||
php7.4-xml \
|
||||
php7.4-xmlrpc \
|
||||
php7.4-xsl \
|
||||
php7.4-zip
|
||||
|
||||
apt-fast install -y --no-install-recommends \
|
||||
php-amqp \
|
||||
php-apcu \
|
||||
php-igbinary \
|
||||
php-memcache \
|
||||
php-memcached \
|
||||
php-mongodb \
|
||||
php-redis \
|
||||
php-xdebug \
|
||||
php-yaml \
|
||||
php-zmq
|
||||
|
||||
apt-get remove --purge -yq php7.2-dev
|
||||
|
||||
apt-fast install -y --no-install-recommends snmp
|
||||
|
||||
# Install composer
|
||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
php -r "if (hash_file('sha384', 'composer-setup.php') === file_get_contents('https://composer.github.io/installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
|
||||
php composer-setup.php
|
||||
sudo mv composer.phar /usr/bin/composer
|
||||
php -r "unlink('composer-setup.php');"
|
||||
|
||||
# Update /etc/environment
|
||||
prependEtcEnvironmentPath /home/runner/.config/composer/vendor/bin
|
||||
|
||||
# Add composer bin folder to path
|
||||
echo 'export PATH="$PATH:$HOME/.config/composer/vendor/bin"' >> /etc/skel/.bashrc
|
||||
|
||||
# Install phpunit (for PHP)
|
||||
wget -q -O phpunit https://phar.phpunit.de/phpunit-7.phar
|
||||
chmod +x phpunit
|
||||
mv phpunit /usr/local/bin/phpunit
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
for cmd in php php7.1 php7.2 php7.3 php7.4 composer phpunit; do
|
||||
if ! command -v $cmd; then
|
||||
echo "$cmd was not installed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "PHP 7.1 ($(php7.1 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.2 ($(php7.2 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.3 ($(php7.3 --version | head -n 1))"
|
||||
DocumentInstalledItem "PHP 7.4 ($(php7.4 --version | head -n 1))"
|
||||
DocumentInstalledItem "Composer ($(composer --version))"
|
||||
DocumentInstalledItem "PHPUnit ($(phpunit --version))"
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: preparemetadata.sh
|
||||
## Desc: This script adds a image title information to the metadata
|
||||
## document
|
||||
################################################################################
|
||||
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
AddTitle "$(lsb_release -ds)"
|
||||
WriteItem "The following software is installed on machines with the $IMAGE_VERSION update."
|
||||
WriteItem "***"
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
## File: 7-zip.sh
|
||||
## Desc: Installs 7-zip
|
||||
################################################################################
|
||||
|
||||
# Source the helpers for use with the script
|
||||
source $HELPER_SCRIPTS/document.sh
|
||||
|
||||
# Install 7-Zip
|
||||
apt-get update -y
|
||||
apt-get install -y p7zip p7zip-full p7zip-rar
|
||||
|
||||
# Run tests to determine that the software installed as expected
|
||||
echo "Testing to make sure that script performed as expected, and basic scenarios work"
|
||||
if ! command -v 7z; then
|
||||
echo "7-Zip was not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Document what was added to the image
|
||||
echo "Lastly, documenting what we added to the metadata file"
|
||||
DocumentInstalledItem "7-Zip $(7z i | head --lines=2 | cut -d ' ' -f 3 | tr -d '\n')"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user