Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions .devops/update-versions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# update-versions.yml
#
# Azure DevOps equivalent of the "Update JDK Versions" GitHub Actions workflow.
# It refreshes versions.json from the upstream release sources and opens (or
# refreshes) a pull request on github.com/microsoft/openjdk-docker when a newer
# patch version is found.
#
# Authentication: git pushes and the PR REST call are authenticated with the
# GitHub App service connection (Github-Java-Engineering), not a PAT. The
# ms_openjdk_token_src repository resource is checked out with persistCredentials
# to obtain a short-lived OAuth token, which is then scoped to github.com/microsoft/*.

name: "UpdateJdkVersions-$(Date:yyyyMMdd)$(Rev:.r)"

trigger: none
pr: none

schedules:
# Tuesdays and Thursdays at 12:00 AM PT so version-bump PRs land before the
# automated Monday/Wednesday/Friday image builds. Azure DevOps cron is UTC
# only and does not observe DST, so this is pinned to 08:00 UTC (midnight PST).
# always: true runs the pipeline even though the source branch has not changed.
- cron: "0 8 * * 2,4"
displayName: "Tue/Thu JDK version check"
branches:
include:
- main
always: true

parameters:
# dry-run only prints the detected changes; create-pull-request opens/updates a PR.
- name: mode
displayName: "Run mode"
type: string
default: dry-run
values:
- dry-run
- create-pull-request

resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

# Token source for GitHub App auth (Github-Java-Engineering service connection).
- repository: ms_openjdk_token_src
type: github
endpoint: Github-Java-Engineering
name: microsoft/openjdk-adoptium-marketplace-data

variables:
repoSlug: microsoft/openjdk-docker
targetBranch: main
bumpBranch: auto/update-jdk-versions

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
sdl:
sourceAnalysisPool:
name: JEG-windows-x64-release
os: windows
sourceRepositoriesToScan:
exclude:
- repository: ms_openjdk_token_src

pool:
name: JEG-pipeline-support
os: linux

stages:
- stage: update_versions
displayName: Update JDK versions
jobs:
- job: update_versions
displayName: Update versions.json and open PR
steps:
# Check out this repo (self) so the scripts and versions.json used at
# runtime come from the branch the pipeline runs on - not from a fresh
# clone of main (which may not yet contain new scripts).
- checkout: self
path: openjdk-docker
displayName: Checkout openjdk-docker (self)

# Obtain a short-lived GitHub App OAuth token via the token-source
# repo and scope it to github.com/microsoft/* for all git operations
# (this is what authorizes the push and PR creation against this repo).
- checkout: ms_openjdk_token_src
persistCredentials: true
path: ms-openjdk-token-src
fetchDepth: 1
fetchTags: false
displayName: Checkout microsoft repo to obtain GitHub App token

- bash: |
set -uo pipefail
repo_dir="$(Pipeline.Workspace)/ms-openjdk-token-src"

# persistCredentials stores the OAuth token as an http extraheader;
# the exact key name is agent-dependent, so discover it.
key=$(git -C "$repo_dir" config --local --name-only --get-regexp '^http\..*\.extraheader$' \
| grep -i 'github.com' | head -n1)

if [ -z "${key}" ]; then
echo "ERROR: Could not find a github.com extraheader in ${repo_dir} git config." >&2
git -C "$repo_dir" config --local --name-only --get-regexp '^http\.' >&2 || true
exit 1
fi

extraheader=$(git -C "$repo_dir" config --local --get "${key}")
if [ -z "${extraheader}" ]; then
echo "ERROR: Found key '${key}' but its value was empty." >&2
exit 1
fi

git config --global "http.https://github.com/microsoft/.extraheader" "${extraheader}"
echo "Configured GitHub App credentials for github.com/microsoft/*"
displayName: Configure GitHub App credentials for microsoft/*

# Configure the git identity used for the version-bump commit, and
# remove any per-repo auth header left by the self checkout so it does
# not stack with the global microsoft/* GitHub App credential (two
# Authorization headers on one request causes "Duplicate header").
- bash: |
set -uo pipefail
git config user.name "Java Platform Infrastructure"
git config user.email "javaplatinfra@microsoft.com"
for key in $(git config --local --name-only --get-regexp '^http\..*\.extraheader$' || true); do
git config --local --unset-all "$key" || true
done
displayName: Configure git identity
workingDirectory: $(Pipeline.Workspace)/openjdk-docker

# Rewrites versions.json in place with the latest patch versions from the
# upstream sources (Microsoft download page for msopenjdk, Adoptium API
# for temurin). No-op if everything is already current.
- bash: |
set -euo pipefail
./scripts/update-versions.sh versions.json
displayName: Update versions.json from upstream release sources
workingDirectory: $(Pipeline.Workspace)/openjdk-docker

# Determine whether the script changed anything and expose the result
# as an output variable (changed=true/false) for later steps. When
# versions changed, render a clean "vendor major: old -> new" summary
# and attach it to the run summary.
- bash: |
set -euo pipefail
if git diff --quiet -- versions.json; then
echo "No JDK version changes detected."
echo "##vso[task.setvariable variable=changed;isOutput=true]false"
exit 0
fi

summary="$(./scripts/summarize-version-changes.sh)"
echo "JDK version changes detected:"
echo "$summary"

# Attach the summary to the AzDO run (equivalent of a step summary).
summary_md="$(Agent.TempDirectory)/version-changes.md"
{
echo "### Detected JDK version changes"
echo ""
echo '```'
echo "$summary"
echo '```'
} > "$summary_md"
echo "##vso[task.uploadsummary]$summary_md"

echo "##vso[task.setvariable variable=changed;isOutput=true]true"
name: diff
displayName: Detect changes
workingDirectory: $(Pipeline.Workspace)/openjdk-docker

# Manual dry-run: changes were found but the user asked not to open a
# PR, so just report what would have happened.
- bash: echo "Changes detected but skipping pull request creation (mode=dry-run)."
displayName: Dry run (no pull request created)
condition: and(eq(variables['diff.changed'], 'true'), eq('${{ parameters.mode }}', 'dry-run'))

# Open (or refresh) the version-bump PR. Skipped in dry-run mode.
# The GitHub App credential scoped to microsoft/* authenticates both
# the git push and the PR REST call made by the script.
- bash: |
set -euo pipefail
auth_header="$(git config --global --get 'http.https://github.com/microsoft/.extraheader' || true)"
if [ -z "$auth_header" ]; then
echo "##vso[task.logissue type=error]Missing GitHub App credentials; auth step must run first."
exit 1
fi
GH_AUTH_HEADER="$auth_header" \
./scripts/open-version-pr.sh "$(repoSlug)" "$(targetBranch)" "$(bumpBranch)"
displayName: Create or update pull request
workingDirectory: $(Pipeline.Workspace)/openjdk-docker
condition: and(eq(variables['diff.changed'], 'true'), ne('${{ parameters.mode }}', 'dry-run'))
59 changes: 59 additions & 0 deletions scripts/open-version-pr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash
#
# Commits versions.json on a dedicated branch, force-pushes it, and opens a
# pull request on GitHub (or refreshes the existing open one). CI-agnostic:
# authentication is provided via the GH_AUTH_HEADER environment variable, which
# must contain a full HTTP Authorization header for github.com, e.g.:
#
# AUTHORIZATION: basic <base64-token>
#
# The same credential is expected to authorize `git push` to origin (e.g. set as
# an http extraheader in git config by the caller).
#
# Usage:
# GH_AUTH_HEADER="..." scripts/open-version-pr.sh <repo_slug> <base_branch> <bump_branch>
#
# Example:
# scripts/open-version-pr.sh microsoft/openjdk-docker main auto/update-jdk-versions

set -euo pipefail

REPO_SLUG="${1:?repo slug required (e.g. microsoft/openjdk-docker)}"
BASE_BRANCH="${2:?base branch required (e.g. main)}"
BUMP_BRANCH="${3:?bump branch required (e.g. auto/update-jdk-versions)}"

if [[ -z "${GH_AUTH_HEADER:-}" ]]; then
echo "error: GH_AUTH_HEADER environment variable is required" >&2
exit 1
fi

owner="${REPO_SLUG%%/*}"
api="https://api.github.com/repos/${REPO_SLUG}"

# Reuse the same branch every run: -C resets it to the current commit so an
# existing (stale) branch is force-updated with the latest versions.
git switch -C "${BUMP_BRANCH}"
git add versions.json
git commit -m "Update JDK versions from upstream release sources"
git push -f origin "HEAD:refs/heads/${BUMP_BRANCH}"

# Check if there is already an open PR for this branch. If none is open, create
# one; if one exists, the force-push above already updated it.
open_count="$(curl -fsSL -H "${GH_AUTH_HEADER}" -H "Accept: application/vnd.github+json" \
"${api}/pulls?state=open&head=${owner}:${BUMP_BRANCH}" | jq 'length')"

if [[ "${open_count}" != "0" ]]; then
echo "An open PR already exists for ${BUMP_BRANCH}; updated it with a force-push."
exit 0
fi

body="Automated update of versions.json from upstream release sources: the Microsoft Build of OpenJDK download page (msopenjdk) and the Adoptium API (temurin). Generated by the Update JDK Versions pipeline."
payload="$(jq -n \
--arg title "Update JDK versions" \
--arg head "${BUMP_BRANCH}" \
--arg base "${BASE_BRANCH}" \
--arg body "${body}" \
'{title: $title, head: $head, base: $base, body: $body}')"

curl -fsSL -X POST -H "${GH_AUTH_HEADER}" -H "Accept: application/vnd.github+json" \
"${api}/pulls" -d "${payload}" | jq -r '"Created PR: " + .html_url'
43 changes: 43 additions & 0 deletions scripts/summarize-version-changes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
#
# Prints a human-readable summary of the JDK version changes between two
# versions.json states, one line per changed entry:
#
# vendor major: old -> new
#
# By default it compares the committed versions.json (git HEAD) against the
# current working-tree versions.json. Both refs can be overridden for testing.
#
# Usage:
# scripts/summarize-version-changes.sh [current_file] [old_git_ref]
#
# Prints nothing (exit 0) when there are no version changes.

set -euo pipefail

CURRENT_FILE="${1:-versions.json}"
OLD_REF="${2:-HEAD}"

if ! command -v jq >/dev/null 2>&1; then
echo "error: jq is required but not installed" >&2
exit 1
fi

# Load both files into jq: `old` = the previous versions.json (from git), `new`
# = the current one. --slurpfile reads each JSON file into a single-element array,
# so $old[0] / $new[0] are the actual objects.
#
# The filter walks every "vendor -> { major: version }" entry in the new file,
# pairs each with the matching version in the old file, keeps only the ones whose
# version actually changed, and prints one line per change:
# " <vendor> <major>: <old> -> <new>" (old shown as "(new)" for a brand-new major)
jq -rn \
--slurpfile old <(git show "${OLD_REF}:${CURRENT_FILE}") \
--slurpfile new "${CURRENT_FILE}" '
($old[0]) as $o | ($new[0]) as $n
| [ $n | to_entries[] as $vendor | $vendor.value | to_entries[] as $major
| { vendor: $vendor.key, major: $major.key,
new: $major.value, old: ($o[$vendor.key][$major.key]) } ]
| map(select(.new != .old))
| .[] | " \(.vendor) \(.major): \(.old // "(new)") -> \(.new)"
'
10 changes: 5 additions & 5 deletions versions.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"temurin": {
"8": "1.8.0_472"
"8": "1.8.0_492"
},
"msopenjdk": {
"11": "11.0.31",
"17": "17.0.19",
"21": "21.0.11",
"25": "25.0.3"
"11": "11.0.32",
"17": "17.0.20",
"21": "21.0.12",
"25": "25.0.4"
}
}
Loading