Visitar URL original
Add Red Hat Build of OpenJDK support (#1241) · actions/setup-java@416c6d1 · GitHub
Skip to content

Commit 416c6d1

Browse files
brunoborgesCopilotCopilot
authored
Add Red Hat Build of OpenJDK support (#1241)
* Add Red Hat Build of OpenJDK support Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix Red Hat tests on Windows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update compiled Red Hat distribution Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 5f75b27 commit 416c6d1

16 files changed

Lines changed: 803 additions & 6 deletions

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ Deprecated aliases `jdkFile`, `server-username`, `server-password`, and `gpg-pas
211211
| `microsoft` | [Microsoft Build of OpenJDK](https://www.microsoft.com/openjdk) | [License](https://docs.microsoft.com/java/openjdk/faq) |
212212
| `oracle` | [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) | [License](https://java.com/freeuselicense) |
213213
| `oracle-openjdk` | [Oracle OpenJDK](https://jdk.java.net/) | [License](https://openjdk.org/legal/gplv2+ce.html) |
214+
| `redhat` | [Red Hat Build of OpenJDK](https://developers.redhat.com/products/openjdk/overview) | [License](https://openjdk.org/legal/gplv2+ce.html) |
214215
| `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [License](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE) |
215216
| `semeru` | [IBM Semeru Runtime Open Edition](https://developer.ibm.com/languages/java/semeru-runtimes/downloads/) | [License](https://openjdk.java.net/legal/gplv2+ce.html) |
216217
| `temurin` | [Eclipse Temurin](https://adoptium.net/) | [License](https://adoptium.net/about.html) |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"result": [
3+
{
4+
"filename": "java-21-openjdk-21.0.8.0.9-1.portable.jdk.x86_64.tar.xz",
5+
"direct_download_uri": "https://developers.redhat.com/content-gateway/file/pub/openjdk/java-21-openjdk-21.0.8.0.9-1.portable.jdk.x86_64.tar.xz",
6+
"checksum": "91a6f3284cbded9de17f65985a4fca48dd4fd0aa295162178631c23ade335aad",
7+
"checksum_type": "sha256"
8+
}
9+
],
10+
"message": ""
11+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"result": [
3+
{
4+
"id": "redhat-21.0.8-jdk-linux-x64",
5+
"archive_type": "tar.xz",
6+
"distribution": "redhat",
7+
"java_version": "21.0.8+9",
8+
"release_status": "ga",
9+
"operating_system": "linux",
10+
"architecture": "x64",
11+
"package_type": "jdk",
12+
"directly_downloadable": true
13+
},
14+
{
15+
"id": "redhat-21.0.7-jdk-linux-x64",
16+
"archive_type": "tar.xz",
17+
"distribution": "redhat",
18+
"java_version": "21.0.7+6",
19+
"release_status": "ga",
20+
"operating_system": "linux",
21+
"architecture": "x64",
22+
"package_type": "jdk",
23+
"directly_downloadable": true
24+
},
25+
{
26+
"id": "redhat-17.0.16-jdk-linux-x64",
27+
"archive_type": "tar.xz",
28+
"distribution": "redhat",
29+
"java_version": "17.0.16+8",
30+
"release_status": "ga",
31+
"operating_system": "linux",
32+
"architecture": "x64",
33+
"package_type": "jdk",
34+
"directly_downloadable": true
35+
},
36+
{
37+
"id": "redhat-9-jdk-linux-x64",
38+
"archive_type": "tar.xz",
39+
"distribution": "redhat",
40+
"java_version": "9+181",
41+
"release_status": "ga",
42+
"operating_system": "linux",
43+
"architecture": "x64",
44+
"package_type": "jdk",
45+
"directly_downloadable": true
46+
},
47+
{
48+
"id": "redhat-21.0.8-jre-linux-x64",
49+
"archive_type": "tar.xz",
50+
"distribution": "redhat",
51+
"java_version": "21.0.8+9",
52+
"release_status": "ga",
53+
"operating_system": "linux",
54+
"architecture": "x64",
55+
"package_type": "jre",
56+
"directly_downloadable": true
57+
},
58+
{
59+
"id": "redhat-17.0.16-jdk-windows-x64",
60+
"archive_type": "zip",
61+
"distribution": "redhat",
62+
"java_version": "17.0.16+8",
63+
"release_status": "ga",
64+
"operating_system": "windows",
65+
"architecture": "x64",
66+
"package_type": "jdk",
67+
"directly_downloadable": true
68+
}
69+
],
70+
"message": "6 package(s) found"
71+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {beforeEach, describe, expect, it, jest} from '@jest/globals';
2+
import path from 'path';
3+
4+
const mockReaddirSync = jest.fn();
5+
const realFs = await import('fs');
6+
jest.unstable_mockModule('fs', () => ({
7+
...realFs,
8+
default: {...realFs.default, readdirSync: mockReaddirSync},
9+
readdirSync: mockReaddirSync
10+
}));
11+
12+
jest.unstable_mockModule('@actions/core', () => ({
13+
info: jest.fn(),
14+
debug: jest.fn(),
15+
isDebug: jest.fn(() => false),
16+
startGroup: jest.fn(),
17+
endGroup: jest.fn()
18+
}));
19+
20+
const realUtil = await import('../../src/util.js');
21+
const mockCacheJdkDir = jest.fn();
22+
const mockExtractJdkFile = jest.fn();
23+
const mockRenameWinArchive = jest.fn();
24+
jest.unstable_mockModule('../../src/util.js', () => ({
25+
...realUtil,
26+
cacheJdkDir: mockCacheJdkDir,
27+
extractJdkFile: mockExtractJdkFile,
28+
renameWinArchive: mockRenameWinArchive
29+
}));
30+
31+
const {RedHatDistribution} =
32+
await import('../../src/distributions/redhat/installer.js');
33+
34+
const release = {
35+
version: '21.0.8+9',
36+
url: 'https://developers.redhat.com/openjdk-21.tar.xz',
37+
checksum: {
38+
algorithm: 'sha256' as const,
39+
value: 'a'.repeat(64)
40+
}
41+
};
42+
43+
const createDistribution = () =>
44+
new RedHatDistribution({
45+
version: '21',
46+
architecture: 'x64',
47+
packageType: 'jdk',
48+
checkLatest: false
49+
});
50+
51+
beforeEach(() => {
52+
jest.clearAllMocks();
53+
mockReaddirSync.mockReturnValue(['jdk-21']);
54+
mockExtractJdkFile.mockResolvedValue('/tmp/extracted');
55+
mockCacheJdkDir.mockResolvedValue('/toolcache/Java_RedHat_jdk/21.0.8-9/x64');
56+
mockRenameWinArchive.mockReturnValue('/tmp/download.zip');
57+
});
58+
59+
describe('Red Hat package installation', () => {
60+
it('extracts and caches Linux tar.xz archives', async () => {
61+
const distribution = createDistribution();
62+
distribution['downloadAndVerify'] = jest
63+
.fn()
64+
.mockResolvedValue('/tmp/download');
65+
distribution['getArchiveType'] = () => 'tar.xz';
66+
67+
const result = await distribution['downloadTool'](release);
68+
69+
expect(mockExtractJdkFile).toHaveBeenCalledWith('/tmp/download', 'tar.xz');
70+
expect(mockRenameWinArchive).not.toHaveBeenCalled();
71+
expect(mockCacheJdkDir).toHaveBeenCalledWith(
72+
path.join('/tmp/extracted', 'jdk-21'),
73+
'Java_RedHat_jdk',
74+
'21.0.8-9',
75+
'x64'
76+
);
77+
expect(result).toEqual({
78+
version: '21.0.8+9',
79+
path: '/toolcache/Java_RedHat_jdk/21.0.8-9/x64'
80+
});
81+
});
82+
83+
it('renames downloaded Windows ZIP archives before extraction', async () => {
84+
const distribution = createDistribution();
85+
distribution['downloadAndVerify'] = jest
86+
.fn()
87+
.mockResolvedValue('/tmp/download');
88+
distribution['getArchiveType'] = () => 'zip';
89+
90+
await distribution['downloadTool'](release);
91+
92+
expect(mockRenameWinArchive).toHaveBeenCalledWith('/tmp/download');
93+
expect(mockExtractJdkFile).toHaveBeenCalledWith('/tmp/download.zip', 'zip');
94+
});
95+
96+
it('fails when the extracted archive is empty', async () => {
97+
mockReaddirSync.mockReturnValue([]);
98+
const distribution = createDistribution();
99+
distribution['downloadAndVerify'] = jest
100+
.fn()
101+
.mockResolvedValue('/tmp/download');
102+
distribution['getArchiveType'] = () => 'tar.xz';
103+
104+
await expect(distribution['downloadTool'](release)).rejects.toThrow(
105+
'The Red Hat archive for Java 21.0.8+9 was empty.'
106+
);
107+
expect(mockCacheJdkDir).not.toHaveBeenCalled();
108+
});
109+
});
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import {afterEach, describe, expect, it, jest} from '@jest/globals';
2+
import {HttpClient} from '@actions/http-client';
3+
import packageDetails from '../data/redhat-package-details.json' with {type: 'json'};
4+
import packages from '../data/redhat-packages.json' with {type: 'json'};
5+
import {RedHatDistribution} from '../../src/distributions/redhat/installer.js';
6+
7+
const createDistribution = (
8+
version: string,
9+
packageType = 'jdk'
10+
): RedHatDistribution => {
11+
const distribution = new RedHatDistribution({
12+
version,
13+
architecture: 'x64',
14+
packageType,
15+
checkLatest: false
16+
});
17+
distribution['getOperatingSystem'] = () => 'linux';
18+
distribution['getArchiveType'] = () => 'tar.xz';
19+
return distribution;
20+
};
21+
22+
const mockDisco = (
23+
packageResponse: unknown = packages,
24+
detailsResponse: unknown = packageDetails
25+
) =>
26+
jest.spyOn(HttpClient.prototype, 'getJson').mockImplementation(async url => ({
27+
result: url.includes('/packages?') ? packageResponse : detailsResponse,
28+
statusCode: 200,
29+
headers: {}
30+
}));
31+
32+
afterEach(() => {
33+
jest.restoreAllMocks();
34+
});
35+
36+
describe('Red Hat package resolution', () => {
37+
it('resolves the newest matching release and authoritative checksum', async () => {
38+
const getJson = mockDisco();
39+
const distribution = createDistribution('21');
40+
41+
const release = await distribution['findPackageForDownload']('21');
42+
43+
expect(release).toEqual({
44+
version: '21.0.8+9',
45+
url: packageDetails.result[0].direct_download_uri,
46+
checksum: {
47+
algorithm: 'sha256',
48+
value: packageDetails.result[0].checksum,
49+
source:
50+
'https://api.foojay.io/disco/v3.0/ids/redhat-21.0.8-jdk-linux-x64'
51+
}
52+
});
53+
expect(getJson).toHaveBeenNthCalledWith(
54+
1,
55+
expect.stringContaining(
56+
'distro=redhat&release_status=ga&operating_system=linux&architecture=x64&package_type=jdk&archive_type=tar.xz&directly_downloadable=true'
57+
)
58+
);
59+
expect(getJson).toHaveBeenNthCalledWith(
60+
2,
61+
'https://api.foojay.io/disco/v3.0/ids/redhat-21.0.8-jdk-linux-x64'
62+
);
63+
});
64+
65+
it('normalizes feature-only Disco versions before matching', async () => {
66+
mockDisco(undefined, {
67+
result: [
68+
{
69+
...packageDetails.result[0],
70+
direct_download_uri: 'https://developers.redhat.com/openjdk-9.tar.xz'
71+
}
72+
],
73+
message: ''
74+
});
75+
const distribution = createDistribution('9');
76+
77+
const release = await distribution['findPackageForDownload']('9');
78+
79+
expect(release.version).toBe('9.0.0+181');
80+
});
81+
82+
it('selects the requested package type', async () => {
83+
const getJson = mockDisco();
84+
const distribution = createDistribution('21', 'jre');
85+
86+
await distribution['findPackageForDownload']('21');
87+
88+
expect(getJson).toHaveBeenNthCalledWith(
89+
1,
90+
expect.stringContaining('package_type=jre')
91+
);
92+
expect(getJson).toHaveBeenNthCalledWith(
93+
2,
94+
'https://api.foojay.io/disco/v3.0/ids/redhat-21.0.8-jre-linux-x64'
95+
);
96+
});
97+
98+
it('maps Windows to ZIP packages', async () => {
99+
const getJson = mockDisco();
100+
const distribution = createDistribution('17');
101+
distribution['getOperatingSystem'] = () => 'windows';
102+
distribution['getArchiveType'] = () => 'zip';
103+
104+
await distribution['findPackageForDownload']('17');
105+
106+
expect(getJson).toHaveBeenNthCalledWith(
107+
1,
108+
expect.stringContaining(
109+
'operating_system=windows&architecture=x64&package_type=jdk&archive_type=zip'
110+
)
111+
);
112+
expect(getJson).toHaveBeenNthCalledWith(
113+
2,
114+
'https://api.foojay.io/disco/v3.0/ids/redhat-17.0.16-jdk-windows-x64'
115+
);
116+
});
117+
118+
it('rejects Alpine before requesting glibc package metadata', async () => {
119+
const getJson = mockDisco();
120+
const distribution = new RedHatDistribution({
121+
version: '21',
122+
architecture: 'x64',
123+
packageType: 'jdk',
124+
checkLatest: false
125+
});
126+
127+
expect(() => distribution['getOperatingSystem'](true)).toThrow(
128+
"Distribution 'redhat' does not support Alpine Linux because Red Hat portable archives require glibc."
129+
);
130+
expect(getJson).not.toHaveBeenCalled();
131+
});
132+
133+
it('rejects early-access versions before requesting metadata', async () => {
134+
const getJson = mockDisco();
135+
const distribution = createDistribution('21-ea');
136+
137+
await expect(distribution['findPackageForDownload']('21')).rejects.toThrow(
138+
'Early access versions are not supported'
139+
);
140+
expect(getJson).not.toHaveBeenCalled();
141+
});
142+
143+
it('reports available versions when no release matches', async () => {
144+
mockDisco();
145+
const distribution = createDistribution('25');
146+
147+
await expect(distribution['findPackageForDownload']('25')).rejects.toThrow(
148+
"No matching version found for SemVer '25'.\nDistribution: RedHat"
149+
);
150+
});
151+
152+
it('fails when the package detail has no direct Red Hat URL', async () => {
153+
mockDisco(packages, {result: [], message: ''});
154+
const distribution = createDistribution('21');
155+
156+
await expect(distribution['findPackageForDownload']('21')).rejects.toThrow(
157+
'returned no direct Red Hat download URL'
158+
);
159+
});
160+
161+
it('fails when the package detail has no SHA-256 checksum', async () => {
162+
mockDisco(packages, {
163+
result: [{...packageDetails.result[0], checksum: ''}],
164+
message: ''
165+
});
166+
const distribution = createDistribution('21');
167+
168+
await expect(distribution['findPackageForDownload']('21')).rejects.toThrow(
169+
'returned no SHA-256 checksum'
170+
);
171+
});
172+
173+
it('fails with a targeted error when package metadata is missing', async () => {
174+
mockDisco(null);
175+
const distribution = createDistribution('21');
176+
177+
await expect(distribution['findPackageForDownload']('21')).rejects.toThrow(
178+
'Could not fetch Red Hat package metadata from Foojay Disco'
179+
);
180+
});
181+
});

0 commit comments

Comments
 (0)