# The contents of this file are subject to the terms of the Common Development and
|
# Distribution License (the License). You may not use this file except in compliance with the
|
# License.
|
#
|
# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
|
# specific language governing permission and limitations under the License.
|
#
|
# When distributing Covered Software, include this CDDL Header Notice in each file and include
|
# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
|
# Header, with the fields enclosed by brackets [] replaced by your own identifying
|
# information: "Portions copyright [year] [name of copyright owner]".
|
#
|
# Copyright 2021-2026 3A Systems, LLC.
|
|
name: Build
|
|
on:
|
push:
|
branches: [ 'sustaining/4.10.x','master', 'issues/**', 'features/**' ]
|
pull_request:
|
|
concurrency:
|
group: ${{ github.workflow }}-${{ github.ref }}
|
cancel-in-progress: true
|
|
# Nothing in this workflow writes back to the repository: the docker jobs push to
|
# the local registry service, not to a remote one, and the other jobs only
|
# publish artifacts through the actions API. The docker jobs additionally get
|
# security-events: write to upload Trivy scan results to code scanning.
|
permissions:
|
contents: read
|
|
jobs:
|
build-maven:
|
runs-on: ${{ matrix.os }}
|
strategy:
|
# The full Java sweep runs on ubuntu only; macOS and Windows build with the
|
# minimum supported (11) and the latest (26) Java.
|
matrix:
|
os: [ 'ubuntu-latest' ]
|
java: [ '11','17','21','25','26']
|
include:
|
- { os: 'macos-latest', java: '11' }
|
- { os: 'macos-latest', java: '26' }
|
- { os: 'windows-latest', java: '11' }
|
- { os: 'windows-latest', java: '26' }
|
fail-fast: false
|
steps:
|
- name: Install rpm for distribution
|
if: runner.os == 'Linux'
|
shell: bash
|
run: |
|
sudo apt-get update
|
sudo apt-get install -y rpm
|
- uses: actions/checkout@v6
|
with:
|
fetch-depth: 0
|
submodules: recursive
|
- name: Java ${{ matrix.Java }} (${{ matrix.os }})
|
uses: actions/setup-java@v5
|
with:
|
java-version: ${{ matrix.java }}
|
distribution: 'zulu'
|
- name: Cache Maven packages
|
uses: actions/cache@v5
|
with:
|
path: ~/.m2/repository
|
key: ${{ runner.os }}-m2-repository-${{ hashFiles('**/pom.xml') }}
|
restore-keys: ${{ runner.os }}-m2-repository
|
- name: Setup MSVC Developer Command Prompt (x86)
|
if: runner.os == 'Windows'
|
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
env:
|
# Opt in to Node.js 24 for this action, which still ships on Node.js 20.
|
# See https://github.com/ilammy/msvc-dev-cmd/issues/99
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
with:
|
arch: x86
|
- name: Build Windows native executables
|
if: runner.os == 'Windows'
|
shell: cmd
|
run: |
|
cd opendj-server-legacy\src\build-tools\windows
|
nmake all || exit /b 1
|
xcopy /Y *.exe ..\..\..\lib\ || exit /b 1
|
git status
|
# Also the source of truth for the committed opendj-server-legacy/lib/*.exe: on a
|
# successful push build, deploy.yml downloads windows-exe-11 from this very run and
|
# commits its contents back to the branch. Nothing here compares them with what is
|
# committed - an MSVC toolchain bump on the runner image changes the bytes on its own,
|
# so a byte-for-byte gate would fire without a source change.
|
- name: Upload Windows exe artifacts
|
if: runner.os == 'Windows'
|
uses: actions/upload-artifact@v7
|
with:
|
name: windows-exe-${{ matrix.java }}
|
retention-days: 5
|
path: opendj-server-legacy/src/build-tools/windows/*.exe
|
- name: Set Integration Test Environment
|
id: failsafe
|
if: runner.os == 'Linux'
|
run: |
|
echo "MAVEN_PROFILE_FLAG=-P precommit" >> $GITHUB_OUTPUT
|
|
- name: Setup WiX (.NET tool) for MSI
|
# Only the java 11 job's MSI is consumed downstream (test-msi*, deploy.yml); without
|
# wix installed the distribution-windows-msi profile stays inactive, so the other
|
# Windows jobs skip the MSI build entirely instead of producing an artifact nothing
|
# uses.
|
if: runner.os == 'Windows' && matrix.java == '11'
|
shell: bash
|
run: |
|
# The MSI builds on Windows only (WiX cannot author MSIs on Linux/macOS). WiX 5 ships as a
|
# net6.0 tool; allow it to run on the newer .NET runtime present on the runner.
|
echo "DOTNET_ROLL_FORWARD=Major" >> "$GITHUB_ENV"
|
export DOTNET_ROLL_FORWARD=Major
|
dotnet tool install --global wix --version 5.0.2 || dotnet tool update --global wix --version 5.0.2
|
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
|
export PATH="$HOME/.dotnet/tools:$PATH"
|
wix --version
|
wix extension add -g WixToolset.UI.wixext/5.0.2 || true
|
|
# The per-module javadoc:jar that runs during verify only ever sees one
|
# module's sources, so it cannot report a package declared by two modules
|
# at once. Only the aggregate on the root reactor can, and it used to run
|
# nowhere but deploy.yml, after the merge and under continue-on-error, so a
|
# broken aggregate blocked nothing and went unnoticed. This is the gate for
|
# it. Appending the goal to the same Maven invocation keeps it in the
|
# reactor that just built; one cell is enough, and this is the platform and
|
# Java version deploy.yml aggregates with.
|
- name: Set Aggregate Javadoc Goal
|
id: javadoc
|
if: runner.os == 'Linux' && matrix.java == '11'
|
run: |
|
echo "MAVEN_JAVADOC_GOAL=javadoc:aggregate" >> $GITHUB_OUTPUT
|
|
- name: Build with Maven
|
timeout-minutes: 180
|
env:
|
MAVEN_OPTS: -Dhttps.protocols=TLSv1.2 -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 -Dmaven.wagon.http.retryHandler.requestSentEnabled=true -Dmaven.wagon.http.retryHandler.count=10
|
run: mvn --batch-mode --errors --update-snapshots verify ${{ steps.javadoc.outputs.MAVEN_JAVADOC_GOAL }} --file pom.xml ${{ steps.failsafe.outputs.MAVEN_PROFILE_FLAG }}
|
- name: Validate the MSI (ICE)
|
# wix build runs no ICE validation (only MSBuild projects or an explicit validate
|
# do), so a green build alone proves the authoring compiles, not that it validates -
|
# e.g. the ICE63 rule about script-generating actions sequenced before
|
# RemoveExistingProducts would go unnoticed without this step.
|
if: runner.os == 'Windows' && matrix.java == '11'
|
shell: bash
|
run: |
|
msi=$(ls opendj-packages/opendj-msi/opendj-msi-standard/target/*.msi)
|
# ICE61 fires by design: AllowSameVersionUpgrades authors an UpgradeVersion row
|
# whose range includes the product's own version, which is exactly what makes a
|
# rebuilt hotfix at the same 3-part version upgrade rather than install alongside.
|
# Left unsuppressed it is permanent noise this step could not tell from a real
|
# regression.
|
wix msi validate -sice ICE61 "$msi"
|
- name: Test on Unix
|
if: runner.os == 'Linux'
|
run: |
|
export OPENDJ_JAVA_ARGS="-server -Xmx512m"
|
opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt
|
opendj-server-legacy/target/package/opendj/bin/status --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000
|
# --- Load-testing tools smoke tests (server online, 5000 sample users) ---
|
echo "===== searchrate ====="
|
opendj-server-legacy/target/package/opendj/bin/searchrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "(uid=user.%d)" 1.1
|
echo "===== authrate ====="
|
opendj-server-legacy/target/package/opendj/bin/authrate --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -g "rand(0,4999)"
|
echo "===== modrate ====="
|
opendj-server-legacy/target/package/opendj/bin/modrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "description:modrate-load-test"
|
echo "===== addrate ====="
|
cat > /tmp/addrate.template <<'EOF'
|
define suffix=dc=example,dc=com
|
|
branch: ou=People,[suffix]
|
subordinateTemplate: person
|
|
template: person
|
rdnAttr: uid
|
objectClass: top
|
objectClass: person
|
objectClass: organizationalPerson
|
objectClass: inetOrgPerson
|
uid: addrate.<sequential:0>
|
cn: AddRate {uid}
|
sn: AddRate
|
userPassword: password
|
EOF
|
opendj-server-legacy/target/package/opendj/bin/addrate --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -C fifo -s 100 /tmp/addrate.template
|
opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll
|
opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template
|
opendj-server-legacy/target/package/opendj/bin/stop-ds
|
echo "4.9.9.0" > opendj-server-legacy/target/package/opendj/config/buildinfo
|
opendj-server-legacy/target/package/opendj/upgrade -n
|
opendj-server-legacy/target/package/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2
|
opendj-server-legacy/target/package/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll
|
opendj-server-legacy/target/package/opendj/bin/start-ds
|
opendj-server-legacy/target/package/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000
|
opendj-server-legacy/target/package/opendj/bin/stop-ds
|
rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs,tmp}
|
|
- name: Test on Unix FIPS
|
if: runner.os == 'Linux'
|
run: |
|
export OPENDJ_JAVA_ARGS="-server -Xmx512m"
|
echo password > /tmp/opendj.keystore.pin
|
|
keytool -genkey -alias server-cert -keyalg rsa \
|
-dname "CN=example.com,O=OpenDJ RSA Self-Signed Certificate" \
|
-keystore /tmp/opendj.bcfks -storetype BCFKS -validity 3650 -providername BCFIPS \
|
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
|
-providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \
|
-keypass:file /tmp/opendj.keystore.pin -storepass:file /tmp/opendj.keystore.pin -keysize 2048 -sigalg SHA256WITHRSA
|
|
keytool -selfcert -alias server-cert -keystore /tmp/opendj.bcfks \
|
-storetype BCFKS -validity 3650 -providername BCFIPS \
|
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
|
-providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \
|
-storepass:file /tmp/opendj.keystore.pin
|
|
keytool -genkey -alias admin-cert -keyalg rsa \
|
-dname "CN=example.com,O=Administration Connector RSA Self-Signed Certificate" \
|
-keystore /tmp/opendj.bcfks -storetype BCFKS -validity 3650 -providername BCFIPS \
|
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
|
-providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \
|
-keypass:file /tmp/opendj.keystore.pin -storepass:file /tmp/opendj.keystore.pin -keysize 2048 -sigalg SHA256WITHRSA
|
|
keytool -selfcert -alias admin-cert -keystore /tmp/opendj.bcfks \
|
-storetype BCFKS -validity 3650 -providername BCFIPS \
|
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
|
-providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \
|
-storepass:file /tmp/opendj.keystore.pin
|
|
echo "useBcfksKeystore=/tmp/opendj.bcfks
|
keyStorePasswordFile=/tmp/opendj.keystore.pin" > /tmp/opendj-setup.properties.bcfks
|
|
opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 \
|
--enableStartTLS --certNickname admin-cert --rootUserDN "cn=Directory Manager" --rootUserPassword password \
|
--baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt \
|
--propertiesFilePath /tmp/opendj-setup.properties.bcfks --doNotStart
|
|
opendj-server-legacy/target/package/opendj/bin/start-ds
|
|
opendj-server-legacy/target/package/opendj/bin/status --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000
|
opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll
|
opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template
|
opendj-server-legacy/target/package/opendj/bin/stop-ds
|
echo "4.9.9.0" > opendj-server-legacy/target/package/opendj/config/buildinfo
|
opendj-server-legacy/target/package/opendj/upgrade -n
|
opendj-server-legacy/target/package/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2
|
opendj-server-legacy/target/package/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll
|
opendj-server-legacy/target/package/opendj/bin/start-ds
|
opendj-server-legacy/target/package/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000
|
opendj-server-legacy/target/package/opendj/bin/stop-ds
|
rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs,tmp}
|
- name: Test LDAP in Cassandra
|
if: runner.os == 'Linux'
|
run: |
|
docker run --rm -it -d -p 9042:9042 --name cassandra cassandra
|
timeout 5m bash -c 'until docker logs cassandra | grep -q "Created default superuser role"; do sleep 5; done'
|
export OPENDJ_JAVA_ARGS="-server -Xmx512m -Ddatastax-java-driver.basic.contact-points.0=localhost:9042 -Ddatastax-java-driver.basic.load-balancing-policy.local-datacenter=datacenter1"
|
opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --cli --acceptLicense --no-prompt
|
opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=userRoot --type cas --set base-dn:dc=example,dc=com --set db-directory:keyspace_name --set enabled:true --no-prompt --trustAll
|
opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template
|
opendj-server-legacy/target/package/opendj/bin/import-ldif --ldifFile /tmp/test.ldif --backendID=userRoot -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --trustAll
|
opendj-server-legacy/target/package/opendj/bin/status --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000
|
opendj-server-legacy/target/package/opendj/bin/stop-ds
|
rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs,tmp}
|
- name: Test LDAP in Postgres
|
if: runner.os == 'Linux'
|
run: |
|
docker run --rm -it -d -p 5432:5432 -e POSTGRES_DB=database_name -e POSTGRES_PASSWORD=password --name postgres postgres
|
timeout 5m bash -c 'until docker logs postgres | grep -q "database system is ready to accept connections"; do sleep 5; done'
|
export OPENDJ_JAVA_ARGS="-server -Xmx512m"
|
opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --cli --acceptLicense --no-prompt
|
opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=userRoot --type jdbc --set base-dn:dc=example,dc=com --set db-directory:jdbc:postgresql://localhost:5432/database_name?user=postgres\&password=password --set enabled:true --no-prompt --trustAll
|
opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template
|
opendj-server-legacy/target/package/opendj/bin/ldapmodify --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll -f /tmp/test.ldif -a 1> /dev/null
|
opendj-server-legacy/target/package/opendj/bin/status --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000
|
opendj-server-legacy/target/package/opendj/bin/stop-ds
|
rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs,tmp}
|
- name: Test replication
|
if: runner.os == 'Linux'
|
# dsreplication enable can hang indefinitely; without a timeout a hang
|
# holds the runner until the 6-hour job limit. Normal duration is ~4 min.
|
timeout-minutes: 30
|
run: |
|
cp -r ./opendj-server-legacy/target/package/opendj ./opendj-server-legacy/target/package/opendj1
|
cp -r ./opendj-server-legacy/target/package/opendj ./opendj-server-legacy/target/package/opendj2
|
cp -r ./opendj-server-legacy/target/package/opendj ./opendj-server-legacy/target/package/opendj3
|
|
echo "Setup OpenDJ-1"
|
|
opendj-server-legacy/target/package/opendj1/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS \
|
--generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com \
|
--sampleData 100000 --cli --acceptLicense --no-prompt
|
|
echo "Setup OpenDJ-2 with replication"
|
|
opendj-server-legacy/target/package/opendj2/setup -h localhost -p 2389 --ldapsPort 2636 --adminConnectorPort 24444 --enableStartTLS \
|
--generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com \
|
--addBaseEntry --cli --acceptLicense --no-prompt
|
|
opendj-server-legacy/target/package/opendj2/bin/dsreplication enable --no-prompt --host1 localhost --port1 4444 --bindDN1 "cn=Directory Manager" --bindPassword1 password --replicationPort1 8989 \
|
--host2 localhost --port2 24444 --bindDN2 "cn=Directory Manager" --bindPassword2 password --replicationPort2 28989 \
|
--adminUID admin --adminPassword password --baseDN dc=example,dc=com --trustAll --noPropertiesFile
|
|
opendj-server-legacy/target/package/opendj2/bin/dsreplication initialize --baseDN dc=example,dc=com --adminUID admin --adminPassword password --hostSource localhost \
|
--portSource 4444 --hostDestination localhost --portDestination 24444 -X -n
|
|
opendj-server-legacy/target/package/opendj2/bin/ldapsearch --port 2636 --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll \
|
--baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 100000
|
|
echo "Setup OpenDJ-3 with replication"
|
|
opendj-server-legacy/target/package/opendj3/setup -h localhost -p 3389 --ldapsPort 3636 --adminConnectorPort 34444 --enableStartTLS \
|
--generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com \
|
--addBaseEntry --cli --acceptLicense --no-prompt
|
|
opendj-server-legacy/target/package/opendj3/bin/dsreplication enable --no-prompt --host1 localhost --port1 24444 --bindDN1 "cn=Directory Manager" --bindPassword1 password --replicationPort1 28989 \
|
--host2 localhost --port2 34444 --bindDN2 "cn=Directory Manager" --bindPassword2 password --replicationPort2 38989 \
|
--adminUID admin --adminPassword password --baseDN dc=example,dc=com --trustAll --noPropertiesFile
|
|
opendj-server-legacy/target/package/opendj3/bin/dsreplication initialize --baseDN dc=example,dc=com --adminUID admin --adminPassword password --hostSource localhost \
|
--portSource 24444 --hostDestination localhost --portDestination 34444 -X -n
|
|
opendj-server-legacy/target/package/opendj2/bin/ldapsearch --port 3636 --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll \
|
--baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 100000
|
|
echo "Stopping and removing OpenDJ instances"
|
|
opendj-server-legacy/target/package/opendj1/bin/stop-ds
|
opendj-server-legacy/target/package/opendj2/bin/stop-ds
|
opendj-server-legacy/target/package/opendj3/bin/stop-ds
|
|
rm -rf ./opendj-server-legacy/target/package/opendj1
|
rm -rf ./opendj-server-legacy/target/package/opendj2
|
rm -rf ./opendj-server-legacy/target/package/opendj3
|
|
- name: Test on Windows
|
if: runner.os == 'Windows'
|
run: |
|
. .github\scripts\wait-server-stopped.ps1
|
set OPENDJ_JAVA_ARGS="-server -Xmx512m"
|
opendj-server-legacy\target\package\opendj\setup.bat -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt
|
opendj-server-legacy\target\package\opendj\bat\status.bat --hostname localhost --bindDN "cn=Directory Manager" --bindPassword password --trustAll
|
opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "5000"
|
# --- Load-testing tools smoke tests (server online, 5000 sample users) ---
|
Write-Host "===== searchrate ====="
|
opendj-server-legacy\target\package\opendj\bat\searchrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "ou=people,dc=example,dc=com" --searchScope sub -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "(uid=user.%d)" 1.1
|
if ($LASTEXITCODE -ne 0) { throw "searchrate failed with exit code $LASTEXITCODE" }
|
Write-Host "===== authrate ====="
|
opendj-server-legacy\target\package\opendj\bat\authrate.bat --hostname localhost --port 1636 --useSSL --trustAll -D "uid=user.%d,ou=people,dc=example,dc=com" -w password -c 4 -m 2000 -i 1 -g "rand(0,4999)"
|
if ($LASTEXITCODE -ne 0) { throw "authrate failed with exit code $LASTEXITCODE" }
|
Write-Host "===== modrate ====="
|
opendj-server-legacy\target\package\opendj\bat\modrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -b "uid=user.%d,ou=people,dc=example,dc=com" -c 4 -t 1 -m 2000 -i 1 -g "rand(0,4999)" "description:modrate-load-test"
|
if ($LASTEXITCODE -ne 0) { throw "modrate failed with exit code $LASTEXITCODE" }
|
Write-Host "===== addrate ====="
|
@'
|
define suffix=dc=example,dc=com
|
|
branch: ou=People,[suffix]
|
subordinateTemplate: person
|
|
template: person
|
rdnAttr: uid
|
objectClass: top
|
objectClass: person
|
objectClass: organizationalPerson
|
objectClass: inetOrgPerson
|
uid: addrate.<sequential:0>
|
cn: AddRate {uid}
|
sn: AddRate
|
userPassword: password
|
'@ | Set-Content -Encoding ascii addrate.template
|
opendj-server-legacy\target\package\opendj\bat\addrate.bat --hostname localhost --port 1636 --useSSL --trustAll --bindDN "cn=Directory Manager" --bindPassword password -c 4 -t 1 -m 2000 -i 1 -C fifo -s 100 addrate.template
|
if ($LASTEXITCODE -ne 0) { throw "addrate failed with exit code $LASTEXITCODE" }
|
opendj-server-legacy\target\package\opendj\bat\dsconfig.bat create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll
|
opendj-server-legacy\target\package\opendj\bat\makeldif.bat -o test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy\target\package\opendj\config\MakeLDIF\example.template
|
opendj-server-legacy\target\package\opendj\bat\stop-ds.bat
|
if ($LASTEXITCODE -ne 0) { throw "stop-ds.bat failed with exit code $LASTEXITCODE" }
|
Wait-ServerStopped 'opendj-server-legacy\target\package\opendj\locks\server.lock'
|
echo "4.9.9.0" > opendj-server-legacy\target\package\opendj\config\buildinfo
|
opendj-server-legacy\target\package\opendj\upgrade.bat
|
opendj-server-legacy\target\package\opendj\bat\import-ldif.bat --offline --ldifFile test.ldif --backendID=example2
|
opendj-server-legacy\target\package\opendj\bat\rebuild-index.bat --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll
|
opendj-server-legacy\target\package\opendj\bat\start-ds.bat
|
opendj-server-legacy\target\package\opendj\bat\rebuild-index.bat --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll
|
opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "10000"
|
opendj-server-legacy\target\package\opendj\bat\stop-ds.bat
|
if ($LASTEXITCODE -ne 0) { throw "stop-ds.bat failed with exit code $LASTEXITCODE" }
|
Wait-ServerStopped 'opendj-server-legacy\target\package\opendj\locks\server.lock'
|
opendj-server-legacy\target\package\opendj\bat\windows-service.bat --enableService
|
net start "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net start 'OpenDJ Server' failed with exit code $LASTEXITCODE" }
|
for ($i=0; $i -lt 12; $i++) { try { $c = New-Object System.Net.Sockets.TcpClient('localhost', 1636); $c.Close(); break } catch { Start-Sleep -Seconds 5 } }
|
opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "10000"
|
net stop "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net stop 'OpenDJ Server' failed with exit code $LASTEXITCODE" }
|
opendj-server-legacy\target\package\opendj\bat\windows-service.bat --disableService
|
|
- name: Upload artifacts OpenDJ Server
|
uses: actions/upload-artifact@v7
|
with:
|
name: ${{ matrix.os }}-${{ matrix.java }}
|
retention-days: 5
|
path: |
|
opendj-server-legacy/target/package/*.zip
|
opendj-ldap-toolkit/target/*.zip
|
opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb
|
opendj-packages/opendj-rpm/opendj-rpm-standard/target/rpm/opendj/RPMS/noarch/*.rpm
|
opendj-packages/opendj-msi/opendj-msi-standard/target/*.msi
|
opendj-packages/opendj-docker/target/Dockerfile.zip
|
opendj-packages/opendj-openshift-template/*.yaml
|
opendj-doc-generated-ref/target/*.zip
|
opendj-dsml-servlet/target/*.war
|
opendj-rest2ldap-servlet/target/*.war
|
build-docker:
|
needs: build-maven
|
runs-on: 'ubuntu-latest'
|
permissions:
|
contents: read
|
security-events: write
|
services:
|
registry:
|
image: registry:3
|
ports:
|
- 5000:5000
|
steps:
|
- uses: actions/checkout@v6
|
with:
|
sparse-checkout: .github/benchmark
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: ubuntu-latest-11
|
- name: Get latest release version
|
shell: bash
|
run: |
|
export git_version_last="$(curl -i -o - --silent https://api.github.com/repos/OpenIdentityPlatform/OpenDJ/releases/latest | grep -m1 "\"name\"" | cut -d\" -f4)" ; echo "last release: $git_version_last"
|
echo "release_version=$git_version_last" >> $GITHUB_ENV
|
echo "image_repository=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
- name: Docker meta
|
id: meta
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
with:
|
images: |
|
localhost:5000/${{ github.repository }}
|
tags: |
|
type=raw,value=${{ env.release_version }}
|
- name: Set up QEMU
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
- name: Set up Docker Buildx
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
with:
|
driver-opts: network=host
|
- name: Prepare Dockerfile
|
shell: bash
|
run: |
|
unzip -d ./opendj-packages/opendj-docker ./opendj-packages/opendj-docker/target/Dockerfile.zip
|
cp ./opendj-server-legacy/target/package/opendj-*.zip ./opendj-packages/opendj-docker
|
sed -i -E '/^#COPY opendj/s/^#//' ./opendj-packages/opendj-docker/Dockerfile
|
- name: Build image (default)
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
with:
|
context: ./opendj-packages/opendj-docker
|
file: ./opendj-packages/opendj-docker/Dockerfile
|
build-args: |
|
VERSION=${{ env.release_version }}
|
platforms: linux/amd64, linux/arm64/8, linux/ppc64le, linux/s390x, linux/riscv64 #, linux/arm/v7
|
push: true
|
tags: ${{ steps.meta.outputs.tags }}
|
labels: ${{ steps.meta.outputs.labels }}
|
- name: Docker test
|
shell: bash
|
run: |
|
trap 'code=$?; echo "::group::container logs (test)"; docker logs test 2>&1 || true; echo "::endgroup::"; exit $code' ERR
|
docker run --rm -it -d --memory="512m" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
|
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com /opt/opendj/data/config/MakeLDIF/example.template'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/stop-ds'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/start-ds'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000'
|
docker kill test
|
- name: Docker test custom password
|
shell: bash
|
run: |
|
trap 'code=$?; echo "::group::container logs (test_custom)"; docker logs test_custom 2>&1 || true; echo "::endgroup::"; exit $code' ERR
|
docker run --rm -it -d --memory="512m" -e ADD_BASE_ENTRY="--addBaseEntry" -e ROOT_PASSWORD=custom_password --name=test_custom localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
|
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_custom | grep -q \"healthy\"; do sleep 10; done'
|
docker exec test_custom 'sh' '-c' '/opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword custom_password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1'
|
docker kill test_custom
|
- name: Scan image for vulnerabilities (Trivy)
|
# trivy resolves the image from the local Docker daemon, so only the runner's
|
# linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from
|
# evicting the m2-repository caches out of the repo's 10GB actions-cache quota
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
with:
|
image-ref: localhost:5000/${{ env.image_repository }}:${{ env.release_version }}
|
format: sarif
|
output: trivy-results.sarif
|
severity: CRITICAL,HIGH
|
limit-severities-for-sarif: true
|
ignore-unfixed: true
|
scanners: vuln
|
cache: false
|
- name: Upload Trivy report to GitHub Security
|
uses: github/codeql-action/upload-sarif@v4
|
# upload even if a preceding step failed, but not without a report to upload
|
if: ${{ always() && hashFiles('trivy-results.sarif') != '' }}
|
with:
|
sarif_file: trivy-results.sarif
|
# distinct from the docker-scan.yml categories, which track the published images
|
category: trivy-build-default
|
- name: Cache JMeter
|
uses: actions/cache@v5
|
with:
|
path: ~/jmeter
|
key: jmeter-5.6.3
|
- name: Benchmark PDB vs JE
|
shell: bash
|
run: |
|
A_BACKEND=je B_BACKEND=pdb THREADS=200 DURATION=150 bash .github/benchmark/compare-opendj.sh \
|
"JE" localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }} \
|
"PDB" localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
|
- name: Upload benchmark artifacts (PDB vs JE)
|
if: always()
|
uses: actions/upload-artifact@v7
|
with:
|
name: benchmark-pdb-vs-je
|
path: |
|
a/
|
b/
|
*.jtl
|
*.docker.log
|
*.jmeter.out
|
if-no-files-found: warn
|
retention-days: 3
|
- name: Benchmark Build vs Release
|
shell: bash
|
run: |
|
THREADS=200 DURATION=150 bash .github/benchmark/compare-opendj.sh \
|
"Build" localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }} \
|
"Release" openidentityplatform/opendj:latest
|
- name: Upload benchmark artifacts
|
if: always()
|
uses: actions/upload-artifact@v7
|
with:
|
name: benchmark-build-vs-release
|
path: |
|
a/
|
b/
|
*.jtl
|
*.docker.log
|
*.jmeter.out
|
if-no-files-found: warn
|
retention-days: 3
|
|
build-docker-alpine:
|
needs: build-maven
|
runs-on: 'ubuntu-latest'
|
permissions:
|
contents: read
|
security-events: write
|
services:
|
registry:
|
image: registry:3
|
ports:
|
- 5000:5000
|
steps:
|
- uses: actions/checkout@v6
|
with:
|
sparse-checkout: .github/benchmark
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: ubuntu-latest-11
|
- name: Get latest release version
|
shell: bash
|
run: |
|
export git_version_last="$(curl -i -o - --silent https://api.github.com/repos/OpenIdentityPlatform/OpenDJ/releases/latest | grep -m1 "\"name\"" | cut -d\" -f4)" ; echo "last release: $git_version_last"
|
echo "release_version=$git_version_last" >> $GITHUB_ENV
|
echo "image_repository=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
- name: Docker meta
|
id: meta
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
with:
|
images: |
|
localhost:5000/${{ github.repository }}
|
tags: |
|
type=raw,value=alpine
|
type=raw,value=${{ env.release_version }}-alpine
|
- name: Set up QEMU
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
- name: Set up Docker Buildx
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
with:
|
driver-opts: network=host
|
- name: Prepare Dockerfile
|
shell: bash
|
run: |
|
unzip -d ./opendj-packages/opendj-docker ./opendj-packages/opendj-docker/target/Dockerfile.zip
|
cp ./opendj-server-legacy/target/package/opendj-*.zip ./opendj-packages/opendj-docker
|
sed -i -E '/^#COPY opendj/s/^#//' ./opendj-packages/opendj-docker/Dockerfile-alpine
|
- name: Build image
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
with:
|
context: ./opendj-packages/opendj-docker
|
file: ./opendj-packages/opendj-docker/Dockerfile-alpine
|
build-args: |
|
VERSION=${{ env.release_version }}
|
platforms: linux/amd64, linux/arm64/8, linux/s390x, linux/ppc64le, linux/386, linux/riscv64
|
push: true
|
tags: ${{ steps.meta.outputs.tags }}
|
labels: ${{ steps.meta.outputs.labels }}
|
- name: Docker test
|
shell: bash
|
run: |
|
trap 'code=$?; echo "::group::container logs (test)"; docker logs test 2>&1 || true; echo "::endgroup::"; exit $code' ERR
|
docker run --rm -it -d --memory="1g" --name=test localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine
|
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test | grep -q \"healthy\"; do sleep 10; done'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com /opt/opendj/data/config/MakeLDIF/example.template'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/stop-ds'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/start-ds'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll'
|
docker exec test 'sh' '-c' '/opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000'
|
docker kill test
|
- name: Docker test custom password
|
shell: bash
|
run: |
|
trap 'code=$?; echo "::group::container logs (test_custom)"; docker logs test_custom 2>&1 || true; echo "::endgroup::"; exit $code' ERR
|
docker run --rm -it -d --memory="1g" -e ADD_BASE_ENTRY="--addBaseEntry" -e ROOT_PASSWORD=custom_password --name=test_custom localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine
|
timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_custom | grep -q \"healthy\"; do sleep 10; done'
|
docker exec test_custom 'sh' '-c' '/opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword custom_password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1'
|
docker kill test_custom
|
- name: Scan image for vulnerabilities (Trivy)
|
# trivy resolves the image from the local Docker daemon, so only the runner's
|
# linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from
|
# evicting the m2-repository caches out of the repo's 10GB actions-cache quota
|
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
with:
|
image-ref: localhost:5000/${{ env.image_repository }}:${{ env.release_version }}-alpine
|
format: sarif
|
output: trivy-results.sarif
|
severity: CRITICAL,HIGH
|
limit-severities-for-sarif: true
|
ignore-unfixed: true
|
scanners: vuln
|
cache: false
|
- name: Upload Trivy report to GitHub Security
|
uses: github/codeql-action/upload-sarif@v4
|
# upload even if a preceding step failed, but not without a report to upload
|
if: ${{ always() && hashFiles('trivy-results.sarif') != '' }}
|
with:
|
sarif_file: trivy-results.sarif
|
# distinct from the docker-scan.yml categories, which track the published images
|
category: trivy-build-alpine
|
- name: Cache JMeter
|
uses: actions/cache@v5
|
with:
|
path: ~/jmeter
|
key: jmeter-5.6.3
|
- name: Benchmark Build-alpine vs Release-alpine
|
shell: bash
|
run: |
|
THREADS=200 DURATION=150 bash .github/benchmark/compare-opendj.sh \
|
"Build-alpine" localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine \
|
"Release-alpine" openidentityplatform/opendj:alpine
|
- name: Upload benchmark artifacts
|
if: always()
|
uses: actions/upload-artifact@v7
|
with:
|
name: benchmark-build-vs-release-alpine
|
path: |
|
a/
|
b/
|
*.jtl
|
*.docker.log
|
*.jmeter.out
|
if-no-files-found: warn
|
retention-days: 90
|
|
test-deb:
|
needs: build-maven
|
runs-on: 'ubuntu-latest'
|
steps:
|
- uses: actions/checkout@v6
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: ubuntu-latest-11
|
- name: Clean-room install + SysV start/stop (debian:12 container)
|
shell: bash
|
run: |
|
docker run --rm -v "$PWD:/work" -w /work debian:12 bash -c '
|
set -e
|
export DEBIAN_FRONTEND=noninteractive
|
DEB=$(ls opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb | head -1)
|
echo "Found $DEB"
|
apt-get update
|
apt-get install -y lintian
|
lintian --info --no-tag-display-limit "$DEB" || true
|
dpkg-deb -I "$DEB"
|
# Both service files must be present (two greps: a single alternation
|
# would pass with either one missing).
|
dpkg-deb -c "$DEB" | grep "lib/systemd/system/opendj.service"
|
dpkg-deb -c "$DEB" | grep "etc/init.d/opendj"
|
# /etc/default/opendj must be a real conffile, so admin edits survive
|
# upgrades and the file survives "apt remove".
|
dpkg-deb -e "$DEB" /tmp/ctrl
|
grep -qx "/etc/default/opendj" /tmp/ctrl/conffiles
|
apt-get install -y "./$DEB"
|
id opendj
|
test "$(stat -c %U /opt/opendj)" = opendj
|
# No JAVA_HOME in this clean container: Java resolves from the PATH
|
# java that the package JRE dependency pulled in.
|
runuser -u opendj -- /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
|
--rootUserDN "cn=Directory Manager" --rootUserPassword password \
|
--hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
|
--baseDN dc=example,dc=com --addBaseEntry
|
/etc/init.d/opendj start
|
bash .github/scripts/wait-for-ldap.sh 1389
|
/etc/init.d/opendj status
|
/etc/init.d/opendj stop
|
apt-get purge -y opendj
|
'
|
- name: Live systemd install + start/stop (runner)
|
shell: bash
|
run: |
|
DEB=$(ls opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb | head -1)
|
sudo apt-get update
|
sudo apt-get install -y "$PWD/$DEB"
|
test "$(stat -c '%U' /opt/opendj)" = opendj
|
# postinst must have enabled the unit; do not enable by hand here or
|
# the postinst registration would go untested.
|
sudo systemctl is-enabled --quiet opendj
|
# Before setup the unit's start condition must keep it from
|
# start-bursting: "start" succeeds but no process may appear.
|
sudo systemctl start opendj
|
sleep 2
|
if sudo systemctl is-failed --quiet opendj; then echo "unit failed before setup"; exit 1; fi
|
# sudo/runuser/systemd strip JAVA_HOME -> the service uses the PATH java.
|
# Privileged port 389: only AmbientCapabilities=CAP_NET_BIND_SERVICE lets
|
# the non-root service bind it - the very reason it can drop root. setup
|
# itself runs via runuser WITHOUT that capability, so it must skip its
|
# own bind probe; the systemd start below performs the real 389 bind.
|
sudo runuser -u opendj -- /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
|
--rootUserDN "cn=Directory Manager" --rootUserPassword password \
|
--hostname localhost --ldapPort 389 --adminConnectorPort 4444 \
|
--baseDN dc=example,dc=com --addBaseEntry --skipPortCheck
|
sudo systemctl start opendj
|
bash .github/scripts/wait-for-ldap.sh 389
|
sudo systemctl is-active --quiet opendj
|
echo "OpenDJ is active under systemd"
|
sudo systemctl stop opendj
|
sleep 3
|
if sudo systemctl is-active --quiet opendj; then echo "still active"; exit 1; fi
|
sudo apt-get purge -y opendj
|
|
# Known coverage limit: the container has no booted systemd (/run/systemd/system
|
# is absent), so only the SysV/chkconfig path of the scriptlets is exercised
|
# here; start/stop through systemd is covered by the deb live-systemd jobs on
|
# the runner. The enable registration IS asserted below - systemctl enable is
|
# a pure symlink operation and works without a booted systemd.
|
test-rpm:
|
needs: build-maven
|
runs-on: 'ubuntu-latest'
|
steps:
|
- uses: actions/checkout@v6
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: ubuntu-latest-11
|
- name: Install and start/stop in Rocky Linux 9
|
shell: bash
|
run: |
|
docker run --rm -v "$PWD:/work" -w /work rockylinux:9 bash -c '
|
set -e
|
RPM=$(ls opendj-packages/opendj-rpm/opendj-rpm-standard/target/rpm/opendj/RPMS/noarch/*.rpm | head -1)
|
echo "Found $RPM"
|
# Both service files must be shipped.
|
rpm -qlp "$RPM" | grep "^/usr/lib/systemd/system/opendj.service$"
|
rpm -qlp "$RPM" | grep "^/etc/init.d/opendj$"
|
# The %pre user-creation dependency must be declared for minimal images.
|
rpm -qp --requires "$RPM" | grep "shadow-utils"
|
# A real EL9 host always has systemd, but the container image ships no
|
# systemctl at all - %post then (correctly) skips the enable in silence.
|
# Preinstall it as host infrastructure, deliberately NOT a package
|
# Requires, so the is-enabled assertion below tests the enable path.
|
dnf install -y systemd
|
# No further manual dependencies: the package must pull everything else
|
# itself (Requires).
|
dnf install -y "$RPM"
|
id opendj
|
test "$(stat -c %U /opt/opendj)" = opendj
|
# %post must have enabled the unit on initial install.
|
systemctl is-enabled opendj.service
|
# No JAVA_HOME here: Java resolves from the PATH java pulled in by
|
# the java-headless dependency.
|
runuser -u opendj -- /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
|
--rootUserDN "cn=Directory Manager" --rootUserPassword password \
|
--hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
|
--baseDN dc=example,dc=com --addBaseEntry
|
/etc/init.d/opendj start
|
bash .github/scripts/wait-for-ldap.sh 1389
|
/etc/init.d/opendj status
|
/etc/init.d/opendj stop
|
rpm -e opendj
|
'
|
|
# Upgrade path: released 5.1.2 deb (root-owned, SysV) -> this build's deb, on the
|
# runner's LIVE systemd. The new package must stop the running server, create the
|
# opendj user, migrate ownership, run the upgrade tool and restart the server with
|
# the old data - through deb-systemd-invoke and the native unit, the path a real
|
# systemd host takes (docs: chap-upgrade).
|
test-deb-upgrade:
|
needs: build-maven
|
runs-on: 'ubuntu-latest'
|
steps:
|
- uses: actions/checkout@v6
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: ubuntu-latest-11
|
- name: Download released 5.1.2 deb
|
shell: bash
|
run: |
|
curl -fsSL -o opendj-5.1.2.deb https://github.com/OpenIdentityPlatform/OpenDJ/releases/download/5.1.2/opendj_5.1.2-1_all.deb
|
# Pin the upgrade source: a retagged release asset must fail loudly here,
|
# not surface as an unrelated upgrade-job failure.
|
echo "8f7f8bdd526b2d63eaef0621545be6aa55749cb60200d3bc2a2849d9ab69eb59 opendj-5.1.2.deb" | sha256sum -c -
|
- name: Upgrade 5.1.2 -> new deb (live systemd on the runner)
|
shell: bash
|
run: |
|
NEW=$(ls opendj-packages/opendj-deb/opendj-deb-standard/target/*.deb | head -1)
|
echo "New deb: $NEW"
|
sudo apt-get update
|
# No manual dependencies: even the released 5.1.2 deb declares a JRE dependency.
|
sudo apt-get install -y "$PWD/opendj-5.1.2.deb"
|
# 5.1.2 model: no dedicated user, root-owned tree, SysV only - on this
|
# booted host its unit comes from the systemd-sysv-generator.
|
sudo /opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
|
--rootUserDN "cn=Directory Manager" --rootUserPassword password \
|
--hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
|
--baseDN dc=example,dc=com --addBaseEntry
|
sudo systemctl daemon-reload
|
sudo /etc/init.d/opendj start
|
bash .github/scripts/wait-for-ldap.sh 1389
|
# Leave the server RUNNING: the new package must stop it, upgrade and
|
# restart it - through deb-systemd-invoke and the native unit this time.
|
sudo apt-get install -y "$PWD/$NEW"
|
id opendj
|
test "$(stat -c %U /opt/opendj)" = opendj
|
test -f /opt/opendj/config/config.ldif
|
# The systemd-hosted upgrade must leave the unit enabled and active.
|
sudo systemctl is-enabled --quiet opendj
|
sudo systemctl is-active --quiet opendj
|
# The pre-upgrade data must be served again, now by the dedicated user.
|
bash .github/scripts/wait-for-ldap.sh 1389
|
test "$(stat -c %U /proc/$(cat /opt/opendj/logs/server.pid))" = opendj
|
sudo systemctl stop opendj
|
sleep 3
|
if sudo systemctl is-active --quiet opendj; then echo "still active"; exit 1; fi
|
sudo apt-get purge -y opendj
|
|
test-rpm-upgrade:
|
needs: build-maven
|
runs-on: 'ubuntu-latest'
|
steps:
|
- uses: actions/checkout@v6
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: ubuntu-latest-11
|
- name: Download released 5.1.2 rpm
|
shell: bash
|
run: |
|
curl -fsSL -o opendj-5.1.2.rpm https://github.com/OpenIdentityPlatform/OpenDJ/releases/download/5.1.2/opendj-5.1.2-1.noarch.rpm
|
# Pin the upgrade source: a retagged release asset must fail loudly here,
|
# not surface as an unrelated upgrade-job failure.
|
echo "9622ef7c3292f0c156c96029a647ca704064aa93b567e5608734fe07d224ec58 opendj-5.1.2.rpm" | sha256sum -c -
|
- name: Upgrade 5.1.2 -> new rpm (Rocky Linux 9 container)
|
shell: bash
|
run: |
|
docker run --rm -v "$PWD:/work" -w /work rockylinux:9 bash -c '
|
set -e
|
NEW=$(ls opendj-packages/opendj-rpm/opendj-rpm-standard/target/rpm/opendj/RPMS/noarch/*.rpm | head -1)
|
echo "New rpm: $NEW"
|
# No manual dependencies: the released 5.1.2 rpm already declares its runtime
|
# requirements (#677), and everything the NEW package needs must come from its
|
# own Requires.
|
dnf install -y ./opendj-5.1.2.rpm
|
# 5.1.2 model: no dedicated user, root-owned tree, SysV only
|
/opt/opendj/setup --cli --no-prompt --acceptLicense --doNotStart \
|
--rootUserDN "cn=Directory Manager" --rootUserPassword password \
|
--hostname localhost --ldapPort 1389 --adminConnectorPort 4444 \
|
--baseDN dc=example,dc=com --addBaseEntry
|
/etc/init.d/opendj start
|
bash .github/scripts/wait-for-ldap.sh 1389
|
# Leave the server RUNNING: the new package must stop it, upgrade and restart it.
|
dnf install -y "./$NEW"
|
id opendj
|
test "$(stat -c %U /opt/opendj)" = opendj
|
test -f /opt/opendj/config/config.ldif
|
# 5.1.2 was chkconfig-enabled and the native unit now shadows the
|
# sysv-generator unit: %post must have migrated that enable state
|
# (a pure symlink operation, asserted fine without booted systemd).
|
systemctl is-enabled opendj.service
|
# The package restarted the server; the pre-upgrade data must be served again,
|
# now by the dedicated user.
|
bash .github/scripts/wait-for-ldap.sh 1389
|
test "$(stat -c %U /proc/$(cat /opt/opendj/logs/server.pid))" = opendj
|
/etc/init.d/opendj stop
|
rpm -e opendj
|
'
|
|
# The gate both MSI jobs wait on. Deliberately not "needs: build-maven": that waits for
|
# the whole matrix, whose ubuntu legs run for about two hours, so any push landing inside
|
# that window cancels the run before those two-minute jobs have started - which is how the
|
# MSI work reached its eighth review round with no completed run behind it. The only input
|
# they have is the windows-latest-11 artifact, so wait for exactly that.
|
#
|
# On ubuntu, and in a job of its own, for two reasons. A waiter on windows-latest holds a
|
# Windows runner from t=0 for the whole wait, competing for the capacity the leg it is
|
# waiting for needs - in run 31578978374 the windows-latest-11 leg sat in the queue for 38
|
# minutes while windows-latest-26 started within one. And the wait was duplicated
|
# verbatim in both jobs, with a 45-minute budget measured from t=0 that covered the leg's
|
# runtime but not its queue time: in that same run the artifact appeared at +52 minutes,
|
# so both jobs would have failed a perfectly healthy build.
|
wait-msi-artifact:
|
runs-on: 'ubuntu-latest'
|
# Well past any queue seen so far, and only reached if the Windows leg neither publishes
|
# nor finishes; the usual exits are the artifact appearing or the leg failing.
|
timeout-minutes: 130
|
permissions:
|
contents: read
|
# Listing the run's artifacts and jobs, which the wait below polls.
|
actions: read
|
steps:
|
- name: Wait for the Windows build artifact
|
shell: bash
|
env:
|
GH_TOKEN: ${{ github.token }}
|
run: |
|
deadline=$(( $(date +%s) + 120 * 60 ))
|
while true; do
|
if gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts" \
|
--jq '.artifacts[].name' | grep -qx 'windows-latest-11'; then
|
echo 'windows-latest-11 is available'
|
exit 0
|
fi
|
# Stop waiting the moment the leg that would publish it has finished without
|
# doing so, instead of sitting out the deadline.
|
# || true: a transient API error is a reason to poll again, not to fail the run.
|
conclusion=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs?per_page=100" \
|
--jq '.jobs[] | select(.name | startswith("build-maven (windows-latest, 11)")) | .conclusion' || true)
|
if [ -n "$conclusion" ] && [ "$conclusion" != "null" ]; then
|
echo "the Windows build leg finished as '$conclusion' without publishing windows-latest-11" >&2
|
exit 1
|
fi
|
if [ "$(date +%s)" -ge "$deadline" ]; then
|
echo 'windows-latest-11 was not published within 120 minutes' >&2
|
exit 1
|
fi
|
sleep 30
|
done
|
|
test-msi:
|
needs: wait-msi-artifact
|
runs-on: 'windows-latest'
|
permissions:
|
contents: read
|
steps:
|
# Only for .github/scripts/wait-server-stopped.ps1 and the MSI authoring the guard
|
# step below reads, and it has to come first: checkout cleans the workspace the
|
# artifact is unpacked into. Sparse because those two are the entire reason for it.
|
- uses: actions/checkout@v6
|
with:
|
sparse-checkout: |
|
.github/scripts
|
opendj-packages/opendj-msi/opendj-msi-standard/resources/msi
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: windows-latest-11
|
- name: Set up Java
|
uses: actions/setup-java@v5
|
with:
|
java-version: '25'
|
distribution: 'zulu'
|
- name: The upgrade guards must hold up on their own
|
shell: pwsh
|
run: |
|
# Two things no install scenario can see, both of which have already gone wrong here.
|
# The first is what a guard is allowed to READ: the execute sequence is processed in
|
# the installer service, so a private property set in the UI sequence is empty by the
|
# time the guard evaluates - which inverts it in exactly the full-UI sessions that no
|
# /qn scenario runs. The second is the inline PowerShell itself: the scenarios reach
|
# it only through a ten-minute install and can then only look at msiexec's exit code,
|
# so a guard that always exits 0 looks the same as one that never has to refuse.
|
# Read from the authoring rather than from the built MSI's tables: wix copies both
|
# the sequence conditions and ExeCommand across verbatim, and it is test-msi-upgrade
|
# that judges the artifact - those scenarios now assert the refusal's own 1603 and
|
# "Return value 3" rather than any non-zero exit code.
|
$wxs = Get-Content -Raw opendj-packages/opendj-msi/opendj-msi-standard/resources/msi/package.wxs
|
$sequence = [regex]::Match($wxs, '(?s)<InstallExecuteSequence>(.*?)</InstallExecuteSequence>')
|
if (-not $sequence.Success) { throw "package.wxs has no InstallExecuteSequence" }
|
foreach ($action in @('RequireDirOnCustomUpgrade', 'RefuseRelocatingUpgrade')) {
|
$scheduled = [regex]::Match($sequence.Groups[1].Value, '(?s)<Custom Action="' + $action + '"(.*?)/>')
|
if (-not $scheduled.Success) { throw "$action is not scheduled in InstallExecuteSequence" }
|
$found = [regex]::Match($scheduled.Groups[1].Value, '(?s)Condition="([^"]*)"')
|
if (-not $found.Success) { throw "$action is scheduled without a condition" }
|
$condition = [System.Net.WebUtility]::HtmlDecode($found.Groups[1].Value)
|
# Property names in these conditions are upper case, and so are AND/NOT: a
|
# lower-case letter is a private property, which reads as empty in the service.
|
if ($condition -cmatch '[a-z]') { throw "$action reads a private property, which never reaches the installer service: $condition" }
|
Write-Host "$action : $condition"
|
}
|
foreach ($property in @('OPENDJ', 'OPENDJ_GIVEN')) {
|
if ($wxs -notmatch ('<Property Id="' + $property + '"[^>]*Secure="yes"')) { throw "$property must be Secure: the guards read it in the service, where a non-administrator's value is dropped otherwise" }
|
}
|
# Sequence="first" is what keeps OPENDJ_GIVEN meaning "the directory was named":
|
# without it the action re-runs in the execute sequence of a full-UI install, where
|
# OPENDJ has long been resolved, and the guards' prefix test would be comparing a
|
# value with itself. Every msiexec call in this workflow is /qn, which runs no UI
|
# sequence, so nothing else here would notice it going.
|
if ($wxs -notmatch '<SetProperty Id="OPENDJ_GIVEN"[^>]*Sequence="first"') { throw 'OPENDJ_GIVEN must be captured with Sequence="first", or it stops meaning "named" in a full-UI install' }
|
# CheckServerNotRunning, run exactly as msiexec runs it: the Formatted field
|
# resolved - [\[] and [\]] are its escapes for literal brackets, [property]
|
# references become their values - and handed to cmd.exe. Windows PowerShell 5.1 is
|
# what the command names, so that is what this exercises.
|
$found = [regex]::Match($wxs, '(?s)<CustomAction Id="CheckServerNotRunning".*?ExeCommand="([^"]*)"')
|
if (-not $found.Success) { throw "CheckServerNotRunning has no ExeCommand" }
|
$target = [System.Net.WebUtility]::HtmlDecode($found.Groups[1].Value)
|
$probeRoot = Join-Path $PWD 'guard-probe'
|
New-Item -ItemType Directory -Force (Join-Path $probeRoot 'locks') | Out-Null
|
$command = $target.Replace('[\[]', '[').Replace('[\]]', ']').Replace('[System64Folder]', "$env:SystemRoot\System32\").Replace('[OPENDJ]', "$probeRoot\")
|
Set-Content -Encoding Ascii -Path guard-probe.cmd -Value $command
|
Write-Host $command
|
function Invoke-Guard { (Start-Process cmd.exe -Wait -PassThru -ArgumentList '/c', 'guard-probe.cmd' -WorkingDirectory $PWD).ExitCode }
|
$lock = Join-Path $probeRoot 'locks\server.lock'
|
Remove-Item $lock -Force -ErrorAction SilentlyContinue
|
$rc = Invoke-Guard
|
if ($rc -ne 0) { throw "no lock file at all must let the upgrade proceed, got $rc" }
|
Set-Content -Path $lock -Value ''
|
$rc = Invoke-Guard
|
if ($rc -ne 0) { throw "an unlocked server.lock must let the upgrade proceed, got $rc" }
|
# A running server holds a mandatory byte-range lock over the whole file
|
# (LockFileManager: RandomAccessFile "rw" plus tryLock) and opens it shared, so the
|
# open succeeds by design and only the Lock call raises IOException - the exception
|
# PowerShell hands to the catch wrapped in a MethodInvocationException.
|
Set-Content -Path guard-holder.ps1 -Value @(
|
'$fs = [System.IO.File]::Open($env:GUARD_PROBE_LOCK, ''Open'', ''ReadWrite'', ''ReadWrite'')',
|
'$fs.Lock(0, [Int64]::MaxValue)',
|
'New-Item -ItemType File -Force -Path "$env:GUARD_PROBE_LOCK.held" | Out-Null',
|
'Start-Sleep -Seconds 300'
|
)
|
$env:GUARD_PROBE_LOCK = $lock
|
Remove-Item "$lock.held" -Force -ErrorAction SilentlyContinue
|
$holder = Start-Process pwsh -PassThru -ArgumentList '-NoProfile', '-File', 'guard-holder.ps1' -WorkingDirectory $PWD
|
for ($i = 0; $i -lt 30 -and -not (Test-Path "$lock.held"); $i++) { Start-Sleep -Seconds 1 }
|
if (-not (Test-Path "$lock.held")) { throw "the holder process never took the lock" }
|
$started = Get-Date
|
$rc = Invoke-Guard
|
$elapsed = [int]((Get-Date) - $started).TotalSeconds
|
Stop-Process -Id $holder.Id -Force -ErrorAction SilentlyContinue
|
if ($rc -ne 1) { throw "a held server.lock must refuse the upgrade (exit 1), got $rc after ${elapsed}s" }
|
# Refusing before the grace is up would mean the loop exited for some other reason.
|
if ($elapsed -lt 55) { throw "the refusal came after ${elapsed}s, so the 60 s grace was not polled through" }
|
Write-Host "CheckServerNotRunning: 0 with no lock file, 0 unlocked, 1 while held (after ${elapsed}s)"
|
Remove-Item -Recurse -Force $probeRoot, guard-probe.cmd, guard-holder.ps1 -ErrorAction SilentlyContinue
|
- name: Install MSI (silent)
|
shell: pwsh
|
run: |
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
if (-not $msi) { throw "MSI not found in the windows-latest-11 artifact" }
|
Write-Host "MSI: $msi"
|
# No OPENDJ property: exercise the x64 default C:\Program Files\OpenDJ (a path with
|
# spaces, which the server scripts must handle).
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v install.log"
|
if ($p.ExitCode -ne 0) { Get-Content install.log -Tail 80; throw "msiexec /i failed: $($p.ExitCode)" }
|
$root = "C:\Program Files\OpenDJ"
|
if (-not (Test-Path "$root\setup.bat")) { Get-Content install.log -Tail 80; throw "OpenDJ not installed into the x64 default $root" }
|
Write-Host "Installed to $root"
|
# The package lays the files down and registers no service: enabling one stays
|
# the administrator's explicit step (windows-service.bat / setup), exactly as
|
# for the zip distribution, so start-ds.bat keeps starting the server directly.
|
if (Get-Service OpenDJ -ErrorAction SilentlyContinue) { throw "the package must not register a service of its own" }
|
if (Get-Service "OpenDJ Server" -ErrorAction SilentlyContinue) { throw "the package must not register a service of its own" }
|
# The SCM wrapper is harvested with the rest of lib; windows-service.bat needs it.
|
if (-not (Test-Path "$root\lib\opendj_service.exe")) { throw "lib\opendj_service.exe missing from the MSI install" }
|
# Custom extension jars go into lib\extensions; the server warns on startup
|
# (WARN_ADMIN_NO_EXTENSIONS_DIR) when it is missing.
|
if (-not (Test-Path "$root\lib\extensions")) { throw "lib\extensions missing from the MSI install" }
|
"OPENDJ_ROOT=$root" | Out-File -FilePath $env:GITHUB_ENV -Append
|
- name: Setup, then start and stop the server without a service
|
shell: pwsh
|
run: |
|
# An MSI install that was never asked for service mode must behave exactly like
|
# a zip one: start-ds.bat starts the server in this very session rather than
|
# dispatching to the SCM, and needs no elevation to do it.
|
$root = $env:OPENDJ_ROOT
|
$env:OPENDJ_JAVA_ARGS = "-server -Xmx512m"
|
& "$root\setup.bat" -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --addBaseEntry --cli --acceptLicense --no-prompt --doNotStart
|
if ($LASTEXITCODE -ne 0) { throw "setup.bat failed: $LASTEXITCODE" }
|
& "$root\bat\start-ds.bat"
|
if ($LASTEXITCODE -ne 0) { throw "start-ds.bat failed: $LASTEXITCODE" }
|
for ($i=0; $i -lt 12; $i++) { try { $c = New-Object System.Net.Sockets.TcpClient('localhost', 1636); $c.Close(); break } catch { Start-Sleep -Seconds 5 } }
|
& "$root\bat\ldapsearch.bat" --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
if ($LASTEXITCODE -ne 0) { throw "ldapsearch failed: $LASTEXITCODE" }
|
& "$root\bat\stop-ds.bat"
|
if ($LASTEXITCODE -ne 0) { throw "stop-ds.bat failed: $LASTEXITCODE" }
|
# A zero exit code from stop-ds does not mean the JVM let go of the lock (#768),
|
# and the next step registers and starts the service against this same instance.
|
. .github\scripts\wait-server-stopped.ps1
|
Wait-ServerStopped "$root\locks\server.lock"
|
- name: Enable, start, stop and disable the Windows service
|
shell: pwsh
|
run: |
|
# Service mode is opt-in and driven entirely by windows-service.bat, the same
|
# command the zip distribution uses. The service it registers takes the display
|
# name "OpenDJ Server" for the first instance on the host.
|
$root = $env:OPENDJ_ROOT
|
& "$root\bat\windows-service.bat" --enableService
|
if ($LASTEXITCODE -ne 0) { throw "--enableService failed: $LASTEXITCODE" }
|
if (-not (Get-Service -DisplayName "OpenDJ Server" -ErrorAction SilentlyContinue)) { sc.exe query; throw "--enableService did not register the service" }
|
net start "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net start failed: $LASTEXITCODE" }
|
for ($i=0; $i -lt 12; $i++) { try { $c = New-Object System.Net.Sockets.TcpClient('localhost', 1636); $c.Close(); break } catch { Start-Sleep -Seconds 5 } }
|
& "$root\bat\ldapsearch.bat" --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
if ($LASTEXITCODE -ne 0) { throw "ldapsearch (as a service) failed: $LASTEXITCODE" }
|
net stop "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net stop failed: $LASTEXITCODE" }
|
& "$root\bat\windows-service.bat" --disableService
|
if ($LASTEXITCODE -ne 0) { throw "--disableService failed: $LASTEXITCODE" }
|
if (Get-Service -DisplayName "OpenDJ Server" -ErrorAction SilentlyContinue) { throw "--disableService left the service registered" }
|
- name: uninstall.bat disables the service it finds and removes the instance
|
shell: pwsh
|
run: |
|
# Re-enable the service so the uninstaller exercises its disable path: nothing
|
# in the package owns the service, so removing it is the uninstaller's job.
|
$root = $env:OPENDJ_ROOT
|
& "$root\bat\windows-service.bat" --enableService
|
if ($LASTEXITCODE -ne 0) { throw "--enableService failed: $LASTEXITCODE" }
|
& "$root\uninstall.bat" --cli --remove-all --no-prompt --forceOnError --quiet
|
if ($LASTEXITCODE -ne 0) { throw "uninstall.bat failed: $LASTEXITCODE" }
|
if (Test-Path "$root\config\config.ldif") { throw "uninstall.bat did not remove the instance files" }
|
if (Get-Service -DisplayName "OpenDJ Server" -ErrorAction SilentlyContinue) { throw "uninstall.bat left the service registered" }
|
Write-Host "uninstall.bat removed the instance and disabled the service"
|
exit 0
|
- name: Uninstall MSI
|
shell: pwsh
|
run: |
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall.log -Tail 80; throw "msiexec /x failed: $($p.ExitCode)" }
|
if (Test-Path "$env:OPENDJ_ROOT\lib\opendj_service.exe") { throw "msiexec /x left the payload behind" }
|
Write-Host "Uninstalled OK"
|
|
# Upgrade path: released 5.1.2 x86 MSI (wine-built, WiX3) -> this build's x64 MSI.
|
# Verifies the new installer detects the legacy Program Files (x86) install, keeps the
|
# instance data in place, stops the running service for the file replacement and leaves
|
# its registration alone, and that the upgraded server starts with the old data.
|
test-msi-upgrade:
|
needs: wait-msi-artifact
|
runs-on: 'windows-latest'
|
permissions:
|
contents: read
|
steps:
|
- name: Download artifacts
|
uses: actions/download-artifact@v8
|
with:
|
name: windows-latest-11
|
- name: Set up Java
|
uses: actions/setup-java@v5
|
with:
|
java-version: '25'
|
distribution: 'zulu'
|
- name: Install released 5.1.2 MSI and configure an instance
|
shell: pwsh
|
run: |
|
$uri = "https://github.com/OpenIdentityPlatform/OpenDJ/releases/download/5.1.2/opendj-5.1.2.msi"
|
for ($i = 1; $i -le 5; $i++) {
|
try { Invoke-WebRequest -Uri $uri -OutFile opendj-5.1.2.msi; break }
|
catch { if ($i -eq 5) { throw }; Write-Host "download attempt $i failed, retrying"; Start-Sleep -Seconds (10 * $i) }
|
}
|
# 5.1.2 already contains the script-quoting fixes from #671 (the tag post-dates
|
# the merge), so install it into its own x86 default - spaces and parentheses
|
# included - to reproduce the real upgrade starting point.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart /l*v install-old.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old.log -Tail 80; throw "msiexec /i (5.1.2) failed: $($p.ExitCode)" }
|
$root = "C:\Program Files (x86)\OpenDJ"
|
if (-not (Test-Path "$root\setup.bat")) { Get-Content install-old.log -Tail 80; throw "5.1.2 install root not found at $root" }
|
$env:OPENDJ_JAVA_ARGS = "-server -Xmx512m"
|
& "$root\setup.bat" -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --addBaseEntry --cli --acceptLicense --no-prompt --doNotStart
|
if ($LASTEXITCODE -ne 0) { throw "setup.bat (5.1.2) failed: $LASTEXITCODE" }
|
# Register the service the pre-MSI way, prove it works, and LEAVE IT RUNNING:
|
# the upgrade itself must stop it (StopServiceBeforeUpgrade runs elevated here)
|
# before CheckServiceStopped would otherwise refuse.
|
& "$root\bat\windows-service.bat" --enableService
|
if ($LASTEXITCODE -ne 0) { throw "windows-service --enableService failed: $LASTEXITCODE" }
|
net start "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net start (5.1.2) failed: $LASTEXITCODE" }
|
- name: Upgrade with the newly built MSI (no OPENDJ - location auto-detected)
|
shell: pwsh
|
run: |
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
if (-not $msi) { throw "MSI not found in the windows-latest-11 artifact" }
|
# The headline upgrade path: no OPENDJ property, the installer must find the
|
# legacy default directory on its own.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade.log"
|
if ($p.ExitCode -ne 0) { Get-Content upgrade.log -Tail 120; throw "msiexec /i (upgrade) failed: $($p.ExitCode)" }
|
$root = "C:\Program Files (x86)\OpenDJ"
|
# New package files landed in the old directory, not the x64 default
|
if (-not (Test-Path "$root\setup.bat")) { throw "upgrade did not keep the old install dir" }
|
if (Test-Path "C:\Program Files\OpenDJ") { throw "upgrade unexpectedly installed into the x64 default dir" }
|
# Instance data survived
|
if (-not (Test-Path "$root\config\config.ldif")) { throw "instance data (config\config.ldif) lost by the upgrade" }
|
# The service registration is the administrator's, not the package's: the upgrade
|
# stopped it to free the jars, and must have left it registered and pointing at
|
# the same tree - the wrapper it names has just been replaced in place.
|
$svc = Get-Service -DisplayName "OpenDJ Server" -ErrorAction SilentlyContinue
|
if (-not $svc) { sc.exe query; throw "the upgrade unregistered the administrator's service" }
|
if ($svc.Status -ne "Stopped") { throw "the upgrade left the service $($svc.Status), expected Stopped" }
|
if (Get-Service OpenDJ -ErrorAction SilentlyContinue) { sc.exe query; throw "the package registered a service of its own" }
|
sc.exe qc "$($svc.Name)"
|
"OPENDJ_ROOT=$root" | Out-File -FilePath $env:GITHUB_ENV -Append
|
- name: Run upgrade.bat and start the upgraded server through the service
|
shell: pwsh
|
run: |
|
$root = $env:OPENDJ_ROOT
|
$env:OPENDJ_JAVA_ARGS = "-server -Xmx512m"
|
& "$root\upgrade.bat" --no-prompt --acceptLicense --force
|
if ($LASTEXITCODE -ne 0) { throw "upgrade.bat failed: $LASTEXITCODE" }
|
# The service registered before the upgrade still drives the refreshed tree.
|
net start "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net start (upgraded) failed: $LASTEXITCODE" }
|
for ($i=0; $i -lt 12; $i++) { try { $c = New-Object System.Net.Sockets.TcpClient('localhost', 1636); $c.Close(); break } catch { Start-Sleep -Seconds 5 } }
|
# The pre-upgrade data must still be served
|
& "$root\bat\ldapsearch.bat" --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
if ($LASTEXITCODE -ne 0) { throw "ldapsearch after upgrade failed: $LASTEXITCODE" }
|
net stop "OpenDJ Server"
|
if ($LASTEXITCODE -ne 0) { throw "net stop (upgraded) failed: $LASTEXITCODE" }
|
- name: Repair must leave the service registration alone
|
shell: pwsh
|
run: |
|
# Nothing in the package controls a service, so a repair must not disturb the
|
# registration the administrator made - neither the one this upgrade inherited
|
# nor one belonging to an unrelated instance.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
$before = (Get-Service -DisplayName "OpenDJ Server").Name
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" REINSTALL=ALL REINSTALLMODE=vomus /quiet /qn /norestart /l*v repair.log"
|
if ($p.ExitCode -ne 0) { Get-Content repair.log -Tail 80; throw "repair failed: $($p.ExitCode)" }
|
$after = Get-Service -DisplayName "OpenDJ Server" -ErrorAction SilentlyContinue
|
if (-not $after) { throw "repair unregistered the service" }
|
if ($after.Name -ne $before) { throw "repair changed the service key name: $before -> $($after.Name)" }
|
Write-Host "Repair left the '$before' service in place"
|
- name: Disabling the service before uninstalling leaves no orphan
|
shell: pwsh
|
run: |
|
# msiexec /x removes the files it installed and nothing else - as the WiX3-era
|
# package did. Disabling the service is the administrator's step (the install
|
# guide says so, and uninstall.bat does it too); skipping it would leave an
|
# auto-start service pointing at a deleted tree.
|
$root = $env:OPENDJ_ROOT
|
& "$root\bat\windows-service.bat" --disableService
|
if ($LASTEXITCODE -ne 0) { throw "--disableService failed: $LASTEXITCODE" }
|
if (Get-Service -DisplayName "OpenDJ Server" -ErrorAction SilentlyContinue) { throw "--disableService left the service registered" }
|
- name: Auto-detect the legacy default directory on a fresh install
|
shell: pwsh
|
run: |
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
# Clean up the previous scenario first.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall1.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall1.log -Tail 80; throw "msiexec /x failed: $($p.ExitCode)" }
|
# An existing legacy default directory must be picked up when OPENDJ is not given.
|
New-Item -ItemType Directory -Force "C:\Program Files (x86)\OpenDJ" | Out-Null
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v install-autodetect.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-autodetect.log -Tail 80; throw "msiexec /i (autodetect) failed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\Program Files (x86)\OpenDJ\setup.bat")) { Get-Content install-autodetect.log -Tail 80; throw "installer did not auto-detect the legacy default dir" }
|
if (Test-Path "C:\Program Files\OpenDJ") { throw "installer used the x64 default dir despite an existing legacy dir" }
|
Write-Host "Legacy default directory auto-detected OK"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall2.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall2.log -Tail 80; throw "msiexec /x (cleanup) failed: $($p.ExitCode)" }
|
- name: Registry install-location detection on a fresh install
|
shell: pwsh
|
run: |
|
# The InstallDir registry value must be picked up when OPENDJ is not given, and it
|
# must beat the legacy Program Files (x86) directory (explicit SetProperty order).
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
New-Item -ItemType Directory -Force "C:\opendj-registry" | Out-Null
|
New-Item -ItemType Directory -Force "C:\Program Files (x86)\OpenDJ" | Out-Null
|
New-Item -Path HKLM:\SOFTWARE\OpenDJ -Force | Out-Null
|
Set-ItemProperty -Path HKLM:\SOFTWARE\OpenDJ -Name InstallDir -Value 'C:\opendj-registry\'
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v install-registry.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-registry.log -Tail 80; throw "msiexec /i (registry detect) failed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\opendj-registry\setup.bat")) { Get-Content install-registry.log -Tail 80; throw "installer did not use the registry InstallDir" }
|
if (Test-Path "C:\Program Files (x86)\OpenDJ\setup.bat") { throw "legacy directory beat the registry InstallDir" }
|
Write-Host "Registry install location detected OK"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall3.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall3.log -Tail 80; throw "msiexec /x (registry cleanup) failed: $($p.ExitCode)" }
|
- name: Silent upgrade from an undetectable directory must refuse with guidance
|
shell: pwsh
|
run: |
|
# A 5.1.x at a custom directory wrote no registry value: a /quiet upgrade
|
# without OPENDJ used to relocate to the default while RemoveExistingProducts
|
# emptied the old tree. The installer must refuse instead.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
# No detection signals at all - the guard under test is the one that fires when
|
# none resolves, so clear every one of them here rather than rely on the
|
# preceding step's uninstall having removed the registry value.
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart OPENDJ=C:\opendj-custom /l*v install-custom.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-custom.log -Tail 80; throw "msiexec /i (5.1.2 custom dir) failed: $($p.ExitCode)" }
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-custom.log"
|
if ($p.ExitCode -eq 0) { Get-Content upgrade-custom.log -Tail 80; throw "upgrade without OPENDJ must refuse when the old location cannot be determined" }
|
# Match on the part of the message that states the condition, not on the
|
# instruction: the wording of the guidance has already been reworded once.
|
if (-not (Select-String -Path upgrade-custom.log -Pattern "location could not be determined" -Quiet)) { Get-Content upgrade-custom.log -Tail 60; throw "expected the explicit-OPENDJ guidance message in the log" }
|
if (-not (Test-Path "C:\opendj-custom\setup.bat")) { throw "the refused upgrade damaged the original install" }
|
Write-Host "Upgrade refused with guidance, original install untouched (exit $($p.ExitCode))"
|
# A named directory that is not the installation strands exactly as much data as
|
# naming none. With no registry value and no server in the legacy default, a typo
|
# used to satisfy this guard - the resolved directory is not the default - and the
|
# relocation guard had nothing to compare it against, so RemoveExistingProducts
|
# emptied C:\opendj-custom while the new tree landed one letter away.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-custmo /quiet /qn /norestart /l*v upgrade-typo.log"
|
if ($p.ExitCode -ne 1603) { Get-Content upgrade-typo.log -Tail 120; throw "a named directory holding no server must be refused (expected 1603, got $($p.ExitCode))" }
|
if (-not (Select-String -Path upgrade-typo.log -Pattern "location could not be determined" -Quiet)) { Get-Content upgrade-typo.log -Tail 60; throw "expected the explicit-OPENDJ guidance message in the log" }
|
if (Test-Path "C:\opendj-custmo") { throw "the refused upgrade still created the mistyped directory" }
|
if (-not (Test-Path "C:\opendj-custom\setup.bat")) { throw "the refused upgrade damaged the original install" }
|
Write-Host "A mistyped target was refused, original install untouched (exit $($p.ExitCode))"
|
# ...and naming the directory makes the very same upgrade proceed. This is what
|
# a GUI administrator does by browsing to it in InstallDirDlg, which the refusal
|
# must leave reachable: it fires on the resolved directory, not on the absence
|
# of a command-line property.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-custom /quiet /qn /norestart /l*v upgrade-custom-ok.log"
|
if ($p.ExitCode -ne 0) { Get-Content upgrade-custom-ok.log -Tail 120; throw "upgrade with an explicit OPENDJ must succeed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\opendj-custom\lib\opendj_service.exe")) { throw "the upgrade did not land in C:\opendj-custom" }
|
if (Test-Path "C:\Program Files\OpenDJ") { throw "the upgrade installed into the default directory as well" }
|
Write-Host "Upgrade into the named directory succeeded"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall-custom.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-custom.log -Tail 80; throw "msiexec /x (custom cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\opendj-custom" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
- name: An empty legacy directory must not be adopted during an upgrade
|
shell: pwsh
|
run: |
|
# NOT Installed holds during a major upgrade too, so the legacy-directory
|
# fallback used to fire on a leftover EMPTY Program Files (x86)\OpenDJ: the new
|
# tree would land there while RemoveExistingProducts emptied the real install
|
# somewhere else. During an upgrade the directory must prove it holds a server.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart OPENDJ=C:\opendj-old /l*v install-old-custom.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-custom.log -Tail 80; throw "msiexec /i (5.1.2 at C:\opendj-old) failed: $($p.ExitCode)" }
|
New-Item -ItemType Directory -Force "C:\Program Files (x86)\OpenDJ" | Out-Null
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-emptylegacy.log"
|
if ($p.ExitCode -eq 0) { Get-Content upgrade-emptylegacy.log -Tail 120; throw "an upgrade with no determinable location must be refused, not routed to an empty legacy directory" }
|
# A non-zero exit code on its own only says msiexec failed; both neighbouring
|
# scenarios name the guard they are about, and so must this one.
|
if (-not (Select-String -Path upgrade-emptylegacy.log -Pattern "location could not be determined" -Quiet)) { Get-Content upgrade-emptylegacy.log -Tail 60; throw "expected the explicit-OPENDJ guidance message in the log" }
|
if (Test-Path "C:\Program Files (x86)\OpenDJ\setup.bat") { throw "the upgrade installed into the empty legacy directory" }
|
if (-not (Test-Path "C:\opendj-old\setup.bat")) { throw "the refused upgrade damaged the original install" }
|
Write-Host "Empty legacy directory not adopted, upgrade refused (exit $($p.ExitCode))"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x opendj-5.1.2.msi /quiet /qn /norestart /l*v uninstall-old-custom.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-old-custom.log -Tail 80; throw "msiexec /x (5.1.2 cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\opendj-old" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
- name: An upgrade that would relocate the installation must refuse
|
shell: pwsh
|
run: |
|
# Passing a different OPENDJ over a detected installation is not a move:
|
# RemoveExistingProducts would empty the old tree while the new one is installed
|
# elsewhere, stranding config/db/logs (and any service registration) behind.
|
#
|
# The older package has to be a different ProductCode for this to be an upgrade
|
# at all: reinstalling this very MSI over itself is maintenance mode, where
|
# FindRelatedProducts does not run, so WIX_UPGRADE_DETECTED would never be set
|
# and the guard could not fire. Hence the released 5.1.2 package, installed at
|
# its native legacy default so both branches of the guard have something to
|
# compare against.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
# Clear every location this scenario reasons about itself, rather than inheriting
|
# the previous step's teardown: the starting state is what the guard is judged
|
# against, so it belongs in the step that makes the judgement.
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\opendj-b" -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart /l*v install-old-legacy.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-legacy.log -Tail 80; throw "msiexec /i (5.1.2) failed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\Program Files (x86)\OpenDJ\setup.bat")) { throw "5.1.2 did not install into the legacy default" }
|
# (a) the recorded-location branch: a host that came through a 5.2.0-or-later
|
# package has its install directory in the registry.
|
New-Item -Path HKLM:\SOFTWARE\OpenDJ -Force | Out-Null
|
Set-ItemProperty -Path HKLM:\SOFTWARE\OpenDJ -Name InstallDir -Value 'C:\Program Files (x86)\OpenDJ\'
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-b /quiet /qn /norestart /l*v relocate-reg.log"
|
if ($p.ExitCode -eq 0) { Get-Content relocate-reg.log -Tail 120; throw "a relocating upgrade must be refused (recorded location)" }
|
if (-not (Select-String -Path relocate-reg.log -Pattern "cannot move an existing installation" -Quiet)) { Get-Content relocate-reg.log -Tail 60; throw "expected the relocation guidance message in the log" }
|
if (Test-Path "C:\opendj-b") { throw "the refused relocation still created C:\opendj-b" }
|
# (b) the legacy-directory branch: no registry value, the old install proven by
|
# the setup.bat in the legacy default.
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-b /quiet /qn /norestart /l*v relocate-legacy.log"
|
if ($p.ExitCode -eq 0) { Get-Content relocate-legacy.log -Tail 120; throw "a relocating upgrade must be refused (legacy directory)" }
|
if (-not (Select-String -Path relocate-legacy.log -Pattern "cannot move an existing installation" -Quiet)) { Get-Content relocate-legacy.log -Tail 60; throw "expected the relocation guidance message in the log" }
|
if (Test-Path "C:\opendj-b") { throw "the refused relocation still created C:\opendj-b" }
|
if (-not (Test-Path "C:\Program Files (x86)\OpenDJ\setup.bat")) { throw "the refused relocation damaged the original install" }
|
Write-Host "Relocating upgrade refused on both branches, original install untouched"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x opendj-5.1.2.msi /quiet /qn /norestart /l*v uninstall-old-legacy.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-old-legacy.log -Tail 80; throw "msiexec /x (5.1.2 cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
- name: A stray OpenDJ tree in the default directory must not be adopted
|
shell: pwsh
|
run: |
|
# The product being upgraded is a 5.1.x in a custom directory, which recorded
|
# nothing, while some unrelated OpenDJ tree - a zip install, a copy - sits in the
|
# x64 default. A setup.bat existence test cannot tell the two apart, so a guard
|
# keyed on it stood down: RemoveExistingProducts gutted the real installation
|
# while InstallFiles landed on the stranger, and msiexec exited 0.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart OPENDJ=C:\opendj-real /l*v install-old-real.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-real.log -Tail 80; throw "msiexec /i (5.1.2 at C:\opendj-real) failed: $($p.ExitCode)" }
|
# The decoy: everything the installer is able to ask about a directory.
|
New-Item -ItemType Directory -Force "C:\Program Files\OpenDJ\lib" | Out-Null
|
Set-Content "C:\Program Files\OpenDJ\setup.bat" '@echo off'
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-decoy.log"
|
if ($p.ExitCode -eq 0) { Get-Content upgrade-decoy.log -Tail 120; throw "an upgrade must not adopt a stray tree in the default directory" }
|
if (-not (Select-String -Path upgrade-decoy.log -Pattern "location could not be determined" -Quiet)) { Get-Content upgrade-decoy.log -Tail 60; throw "expected the explicit-OPENDJ guidance message in the log" }
|
if (Test-Path "C:\Program Files\OpenDJ\lib\opendj_service.exe") { throw "the refused upgrade installed into the stray tree" }
|
if (-not (Test-Path "C:\opendj-real\setup.bat")) { throw "the refused upgrade damaged the original install" }
|
# ...and naming the real directory gets the administrator through, decoy or not.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-real /quiet /qn /norestart /l*v upgrade-decoy-ok.log"
|
if ($p.ExitCode -ne 0) { Get-Content upgrade-decoy-ok.log -Tail 120; throw "upgrade with an explicit OPENDJ must succeed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\opendj-real\lib\opendj_service.exe")) { throw "the upgrade did not land in C:\opendj-real" }
|
if (Test-Path "C:\Program Files\OpenDJ\lib\opendj_service.exe") { throw "the upgrade also installed into the stray tree" }
|
Write-Host "Stray default-directory tree ignored: refused, then upgraded where told"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall-real.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-real.log -Tail 80; throw "msiexec /x (decoy cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\opendj-real" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
- name: An old server in the default directory upgrades once it is named
|
shell: pwsh
|
run: |
|
# The price of the scenario above: a 5.1.x that really does live in the x64
|
# default recorded nothing either, so the package cannot tell it from the decoy
|
# and refuses the silent upgrade that would have gone through before. What it
|
# must not do is dead-end - the directory is a configurable property, and naming
|
# it (which is also what browsing to it in the wizard amounts to) has to work.
|
#
|
# The old tree gets there by hand rather than through OPENDJ=: the released
|
# 5.1.x package is x86, and a 32-bit package cannot install into the 64-bit
|
# Program Files at all - Windows Installer resolves its [ProgramFilesFolder] to
|
# Program Files (x86) whatever the directory property says, so msiexec exits 0
|
# while the files land in the legacy default, which is a different scenario (one
|
# the legacy-directory search resolves on its own). Installing into a custom
|
# directory and moving the tree leaves exactly what this one needs: an
|
# upgradable 5.1.x registration, no recorded location, no legacy directory, and
|
# a real old server sitting in C:\Program Files\OpenDJ. That the registration is
|
# left pointing at the directory the move emptied costs nothing - no guard reads
|
# it, and RemoveExistingProducts tolerates the files being gone.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\opendj-x64src" -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart OPENDJ=C:\opendj-x64src /l*v install-old-x64.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-x64.log -Tail 80; throw "msiexec /i (5.1.2 at C:\opendj-x64src) failed: $($p.ExitCode)" }
|
# Exit code 0 says msiexec ran, not that it put the files where it was told, and
|
# a directory it silently declined to use is worth naming in the failure.
|
if (-not (Test-Path "C:\opendj-x64src\setup.bat")) { Get-ChildItem "C:\Program Files\OpenDJ","C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue | Select-Object -First 5 -ExpandProperty FullName; Get-Content install-old-x64.log -Tail 80; throw "5.1.2 did not install into C:\opendj-x64src" }
|
Move-Item "C:\opendj-x64src" "C:\Program Files\OpenDJ"
|
if (-not (Test-Path "C:\Program Files\OpenDJ\setup.bat")) { throw "the 5.1.2 tree did not move into C:\Program Files\OpenDJ" }
|
# 5.1.x ships lib\opendj_service.exe itself, so the file cannot say whose tree
|
# this is; its content can. Both halves below are judged on the two things only
|
# the new package produces: this payload, and the InstallDir registry value.
|
$oldWrapper = (Get-FileHash "C:\Program Files\OpenDJ\lib\opendj_service.exe").Hash
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-x64-silent.log"
|
if ($p.ExitCode -eq 0) { Get-Content upgrade-x64-silent.log -Tail 120; throw "a silent upgrade with nothing recording the location must refuse" }
|
if (-not (Select-String -Path upgrade-x64-silent.log -Pattern "location could not be determined" -Quiet)) { Get-Content upgrade-x64-silent.log -Tail 60; throw "expected the explicit-OPENDJ guidance message in the log" }
|
if (Test-Path HKLM:\SOFTWARE\OpenDJ) { throw "the refused upgrade registered an install location" }
|
if ((Get-FileHash "C:\Program Files\OpenDJ\lib\opendj_service.exe").Hash -ne $oldWrapper) { throw "the refused upgrade overwrote the old server" }
|
# The signal that says "this directory was named" has to be public - a private
|
# property set in the UI sequence never reaches the installer service, where the
|
# guards run - so it must not be usable as a switch. It holds the named path and
|
# the guard requires the resolved directory to START WITH it: a flag-shaped value
|
# disarms nothing, and a value that does pass has spelled out the directory, which
|
# is naming it. It is not the only conjunct either - the target still has to hold a
|
# server - so even a matching prefix cannot stand in for that evidence.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ_GIVEN=1 /quiet /qn /norestart /l*v upgrade-x64-switch.log"
|
if ($p.ExitCode -ne 1603) { Get-Content upgrade-x64-switch.log -Tail 120; throw "OPENDJ_GIVEN=1 must not switch the guard off (expected 1603, got $($p.ExitCode))" }
|
if (-not (Select-String -Path upgrade-x64-switch.log -Pattern "location could not be determined" -Quiet)) { Get-Content upgrade-x64-switch.log -Tail 60; throw "the refusal must still come from the same guard" }
|
if ((Get-FileHash "C:\Program Files\OpenDJ\lib\opendj_service.exe").Hash -ne $oldWrapper) { throw "the upgrade that OPENDJ_GIVEN=1 let through overwrote the old server" }
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=`"C:\Program Files\OpenDJ`" /quiet /qn /norestart /l*v upgrade-x64-named.log"
|
if ($p.ExitCode -ne 0) { Get-Content upgrade-x64-named.log -Tail 120; throw "the named upgrade into the default directory must succeed: $($p.ExitCode)" }
|
if ((Get-FileHash "C:\Program Files\OpenDJ\lib\opendj_service.exe").Hash -eq $oldWrapper) { Get-Content upgrade-x64-named.log -Tail 120; throw "the upgrade did not land in C:\Program Files\OpenDJ" }
|
# The only OPENDJ in this workflow whose value carries spaces: what the package
|
# recorded proves it survived the command line and the elevation intact.
|
$recorded = (Get-ItemProperty -Path HKLM:\SOFTWARE\OpenDJ -Name InstallDir -ErrorAction SilentlyContinue).InstallDir
|
if ($recorded -notlike "C:\Program Files\OpenDJ*") { throw "the upgrade recorded '$recorded', not the directory it was told to use" }
|
Write-Host "Default-directory upgrade refused silently, accepted when named"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall-x64.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-x64.log -Tail 80; throw "msiexec /x (x64 default cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\opendj-x64src" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
- name: Fresh install must not touch a service registered by another instance
|
shell: pwsh
|
run: |
|
# A leftover Program Files (x86)\OpenDJ plus an "OpenDJ Server" belonging to a
|
# zip instance elsewhere: installing to a third directory must leave that
|
# registration completely alone. The package controls no service at all, so this
|
# holds by construction - the scenario guards against reintroducing one.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
New-Item -ItemType Directory -Force "C:\Program Files (x86)\OpenDJ" | Out-Null
|
sc.exe create "OpenDJ Server" binPath= "C:\zip-instance\lib\opendj_service.exe start ""C:\zip-instance.""" start= demand
|
if ($LASTEXITCODE -ne 0) { throw "sc create failed: $LASTEXITCODE" }
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-elsewhere /quiet /qn /norestart /l*v install-elsewhere.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-elsewhere.log -Tail 80; throw "msiexec /i (elsewhere) failed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\opendj-elsewhere\setup.bat")) { throw "install did not land in C:\opendj-elsewhere" }
|
if (-not (Get-Service "OpenDJ Server" -ErrorAction SilentlyContinue)) { throw "fresh install elsewhere deleted an unrelated instance's service" }
|
sc.exe delete "OpenDJ Server"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall-elsewhere.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-elsewhere.log -Tail 80; throw "msiexec /x (elsewhere cleanup) failed: $($p.ExitCode)" }
|
Write-Host "Fresh install elsewhere left the unrelated 'OpenDJ Server' service in place"
|
- name: An upgrade must refuse while the service is still starting
|
shell: pwsh
|
run: |
|
# The SCM takes no controls in a pending state: StopServiceBeforeUpgrade's
|
# 'net stop' fails instantly with ERROR_SERVICE_CANNOT_ACCEPT_CTRL and
|
# Return="ignore" eats it. StartPending is not 'Running', so a check that
|
# sampled the state once waved the upgrade through with a JVM coming up on the
|
# tree being replaced - and the jars are unversioned, so the delete-on-reboot
|
# entries left behind by the nested uninstall name the paths the NEW jars
|
# occupy. Reproducible because the wrapper reports START_PENDING for as long as
|
# bat\start-ds.bat runs, which service.c gives 300 s.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart /l*v install-old-pending.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-pending.log -Tail 80; throw "msiexec /i (5.1.2) failed: $($p.ExitCode)" }
|
$root = "C:\Program Files (x86)\OpenDJ"
|
$env:OPENDJ_JAVA_ARGS = "-server -Xmx512m"
|
& "$root\setup.bat" -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --addBaseEntry --cli --acceptLicense --no-prompt --doNotStart
|
if ($LASTEXITCODE -ne 0) { throw "setup.bat (5.1.2) failed: $LASTEXITCODE" }
|
& "$root\bat\windows-service.bat" --enableService
|
if ($LASTEXITCODE -ne 0) { throw "windows-service --enableService failed: $LASTEXITCODE" }
|
# Hold the start open: the wrapper waits for this script, reporting START_PENDING
|
# the whole time. No JVM is needed - the guard is being asked about a service
|
# state, not about a lock.
|
Copy-Item "$root\bat\start-ds.bat" "$root\bat\start-ds.bat.orig"
|
Set-Content "$root\bat\start-ds.bat" "@echo off`r`nping -n 240 127.0.0.1 >nul"
|
sc.exe start "OpenDJ Server" | Out-Null
|
for ($i = 0; $i -lt 15; $i++) {
|
$st = (Get-Service "OpenDJ Server" -ErrorAction SilentlyContinue).Status
|
if ($st -eq 'StartPending') { break }
|
Start-Sleep -Seconds 1
|
}
|
$st = (Get-Service "OpenDJ Server" -ErrorAction SilentlyContinue).Status
|
if ($st -ne 'StartPending') { throw "expected the service to be StartPending, got '$st'" }
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-pending.log"
|
# A refusal from a Return="check" custom action is 1722 in the log and 1603 out of
|
# msiexec, and nothing else. "-ne 0" would also accept 3010 - which is the FAIL-OPEN
|
# outcome, the upgrade going through and leaving the files it could not replace to a
|
# reboot - so the exact code is what gets asserted. Same reasoning for the log: the
|
# action NAME appears whether it ran and passed, ran and failed, or was skipped by
|
# its condition, so the return value has to be part of the pattern.
|
if ($p.ExitCode -ne 1603) { Get-Content upgrade-pending.log -Tail 120; throw "the upgrade must refuse while the service is starting (expected 1603, got $($p.ExitCode))" }
|
if (-not (Select-String -Path upgrade-pending.log -Pattern "CheckServiceStopped\. Return value 3" -Quiet)) { Get-Content upgrade-pending.log -Tail 60; throw "the refusal must come from CheckServiceStopped" }
|
if (-not (Test-Path "$root\config\config.ldif")) { throw "the refused upgrade damaged the instance" }
|
if (-not (Test-Path "$root\setup.bat")) { throw "the refused upgrade damaged the installation" }
|
Write-Host "Upgrade refused while the service was StartPending (exit $($p.ExitCode))"
|
# Teardown: the wrapper is still sitting on the held-open start.
|
Stop-Process -Name opendj_service -Force -ErrorAction SilentlyContinue
|
Get-Process -Name PING -ErrorAction SilentlyContinue | Stop-Process -Force
|
Start-Sleep -Seconds 5
|
Move-Item -Force "$root\bat\start-ds.bat.orig" "$root\bat\start-ds.bat"
|
& "$root\bat\windows-service.bat" --disableService
|
if (Get-Service "OpenDJ Server" -ErrorAction SilentlyContinue) { sc.exe delete "OpenDJ Server" }
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x opendj-5.1.2.msi /quiet /qn /norestart /l*v uninstall-pending.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-pending.log -Tail 80; throw "msiexec /x (5.1.2 cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
- name: A decoy in the legacy default must not block the documented workaround
|
shell: pwsh
|
run: |
|
# The mirror of "a stray OpenDJ tree in the default directory must not be adopted",
|
# with the stray tree in the LEGACY default instead - where the install guide says
|
# to pass OPENDJ, and where the relocation guard used to refuse that very command:
|
# the legacy directory holds A server, the named directory is not it, refuse. The
|
# installation then had no upgrade path at all, silent or named.
|
# Both halves are asserted here, because the exception that fixes it is narrow: the
|
# named directory has to hold a server. Naming an empty one is still a relocation.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\opendj-b" -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart OPENDJ=C:\opendj-mine /l*v install-old-mine.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-mine.log -Tail 80; throw "msiexec /i (5.1.2 at C:\opendj-mine) failed: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\opendj-mine\setup.bat")) { Get-Content install-old-mine.log -Tail 80; throw "5.1.2 did not install into C:\opendj-mine" }
|
# The decoy: a zip installation, a copy, a decommissioned instance - anything a
|
# setup.bat search cannot tell from the product being upgraded.
|
New-Item -ItemType Directory -Force "C:\Program Files (x86)\OpenDJ\lib" | Out-Null
|
Set-Content "C:\Program Files (x86)\OpenDJ\setup.bat" '@echo off'
|
# Naming a directory that holds no server is still a relocation, decoy or not.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-b /quiet /qn /norestart /l*v relocate-decoy.log"
|
if ($p.ExitCode -eq 0) { Get-Content relocate-decoy.log -Tail 120; throw "naming an empty directory is a relocation and must be refused" }
|
if (-not (Select-String -Path relocate-decoy.log -Pattern "cannot move an existing installation" -Quiet)) { Get-Content relocate-decoy.log -Tail 60; throw "expected the relocation guidance message in the log" }
|
if (Test-Path "C:\opendj-b") { throw "the refused relocation still created C:\opendj-b" }
|
# ...and naming the real one is the workaround the install guide prescribes.
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" OPENDJ=C:\opendj-mine /quiet /qn /norestart /l*v upgrade-mine.log"
|
if ($p.ExitCode -ne 0) { Get-Content upgrade-mine.log -Tail 120; throw "the documented workaround must upgrade the named installation: $($p.ExitCode)" }
|
if (-not (Test-Path "C:\opendj-mine\lib\opendj_service.exe")) { throw "the upgrade did not land in C:\opendj-mine" }
|
if (Test-Path "C:\Program Files (x86)\OpenDJ\lib\opendj_service.exe") { throw "the upgrade also installed into the decoy" }
|
Write-Host "Legacy-default decoy: empty target refused, named installation upgraded"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall-mine.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-mine.log -Tail 80; throw "msiexec /x (decoy cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\opendj-mine" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
- name: An upgrade must refuse while a server runs without a service
|
shell: pwsh
|
run: |
|
# The mode this package ships by default: setup registers no service, so the
|
# server started by bat\start-ds.bat is a plain JVM holding lib\*.jar. There is no
|
# service key for the ImagePath-gated pair to match, and Restart Manager is not
|
# allowed to shut anything down, so CheckServerNotRunning - the byte-range lock on
|
# locks\server.lock - is the only thing standing between a running server and
|
# RemoveExistingProducts renaming its jars into delete-on-reboot entries.
|
$msi = (Get-ChildItem -Recurse -Filter *.msi -Path opendj-packages/opendj-msi | Select-Object -First 1).FullName
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Recurse -Force "C:\Program Files\OpenDJ" -ErrorAction SilentlyContinue
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i opendj-5.1.2.msi /quiet /qn /norestart /l*v install-old-running.log"
|
if ($p.ExitCode -ne 0) { Get-Content install-old-running.log -Tail 80; throw "msiexec /i (5.1.2) failed: $($p.ExitCode)" }
|
$root = "C:\Program Files (x86)\OpenDJ"
|
$env:OPENDJ_JAVA_ARGS = "-server -Xmx512m"
|
& "$root\setup.bat" -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --addBaseEntry --cli --acceptLicense --no-prompt --doNotStart
|
if ($LASTEXITCODE -ne 0) { throw "setup.bat (5.1.2) failed: $LASTEXITCODE" }
|
& "$root\bat\start-ds.bat"
|
if ($LASTEXITCODE -ne 0) { throw "start-ds.bat failed: $LASTEXITCODE" }
|
for ($i=0; $i -lt 12; $i++) { try { $c = New-Object System.Net.Sockets.TcpClient('localhost', 1636); $c.Close(); break } catch { Start-Sleep -Seconds 5 } }
|
if (Get-Service "OpenDJ Server" -ErrorAction SilentlyContinue) { throw "this scenario is about a server with NO service registered" }
|
# The headline auto-detected upgrade, which would otherwise proceed straight into
|
# the running server's tree. The refusal costs the full 60 s grace inside the
|
# check: a server that is genuinely up never releases the lock.
|
$pendingKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager"
|
$pendingBefore = @((Get-ItemProperty -Path $pendingKey -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations)
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-running.log"
|
# Exactly 1603 (custom action 1722), for the reason spelled out in the StartPending
|
# scenario above: 3010 is what a guard that fails open produces here, and "-ne 0"
|
# cannot tell the two apart. The action name alone cannot either - it is written to
|
# the log whether the action refused or waved the upgrade through.
|
if ($p.ExitCode -ne 1603) { Get-Content upgrade-running.log -Tail 120; throw "the upgrade must refuse while a server is running out of the tree (expected 1603, got $($p.ExitCode))" }
|
if (-not (Select-String -Path upgrade-running.log -Pattern "CheckServerNotRunning\. Return value 3" -Quiet)) { Get-Content upgrade-running.log -Tail 60; throw "the refusal must come from CheckServerNotRunning" }
|
# The signature of the fail-open, and the damage it does: RemoveExistingProducts
|
# cannot rename a jar the JVM holds, so it leaves a delete-on-reboot entry naming
|
# the path the new jar occupies. A refusal leaves none.
|
$pendingAfter = @((Get-ItemProperty -Path $pendingKey -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations)
|
$pendingNew = $pendingAfter | Where-Object { $_ -and $pendingBefore -notcontains $_ }
|
if ($pendingNew) { throw "the refused upgrade still scheduled files for delete-on-reboot: $($pendingNew -join '; ')" }
|
if (-not (Test-Path "$root\config\config.ldif")) { throw "the refused upgrade damaged the instance" }
|
& "$root\bat\ldapsearch.bat" --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
|
if ($LASTEXITCODE -ne 0) { throw "the refused upgrade disturbed the running server" }
|
Write-Host "Upgrade refused while a non-service server was running (exit $($p.ExitCode))"
|
# Stopping it makes the very same upgrade proceed - and the 60 s grace inside the
|
# check is what absorbs the gap between stop-ds returning and the JVM releasing
|
# the lock, so no wait is needed here to keep this half honest.
|
& "$root\bat\stop-ds.bat"
|
if ($LASTEXITCODE -ne 0) { throw "stop-ds.bat failed: $LASTEXITCODE" }
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/i `"$msi`" /quiet /qn /norestart /l*v upgrade-stopped.log"
|
if ($p.ExitCode -ne 0) { Get-Content upgrade-stopped.log -Tail 120; throw "the upgrade must proceed once the server is stopped: $($p.ExitCode)" }
|
if (-not (Test-Path "$root\lib\opendj_service.exe")) { throw "the upgrade did not land in $root" }
|
if (-not (Test-Path "$root\config\config.ldif")) { throw "the upgrade lost the instance data" }
|
Write-Host "The same upgrade proceeded once the server was stopped"
|
$p = Start-Process msiexec -Wait -PassThru -ArgumentList "/x `"$msi`" /quiet /qn /norestart /l*v uninstall-running.log"
|
if ($p.ExitCode -ne 0) { Get-Content uninstall-running.log -Tail 80; throw "msiexec /x (running-server cleanup) failed: $($p.ExitCode)" }
|
Remove-Item -Recurse -Force "C:\Program Files (x86)\OpenDJ" -ErrorAction SilentlyContinue
|
Remove-Item -Path HKLM:\SOFTWARE\OpenDJ -Recurse -Force -ErrorAction SilentlyContinue
|