Skip to content

Commit a42a52c

Browse files
brunoborgesCopilot
andauthored
Add multiple Maven server credentials (#1239)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent fb4abd7 commit a42a52c

9 files changed

Lines changed: 365 additions & 74 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ steps:
170170
| `server-id` | Maven repository ID used in generated `settings.xml`. | `github` |
171171
| `server-username-env-var` | Environment variable name for Maven repository username. | `GITHUB_ACTOR` |
172172
| `server-password-env-var` | Environment variable name for Maven repository password or token. | `GITHUB_TOKEN` |
173+
| `mvn-server-credentials` | Multiline Maven server credentials in the format `server-id:USERNAME_ENV:PASSWORD_ENV`. Replaces the single server configured by the three inputs above when set. | |
173174
| `settings-path` | Directory where `settings.xml` is written. | `~/.m2` |
174175
| `overwrite-settings` | Overwrite an existing `settings.xml`. | `true` |
175176
| `gpg-private-key` | GPG private key to import into an isolated temporary keyring. | |

__tests__/auth.test.ts

Lines changed: 167 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jest.unstable_mockModule('@actions/core', () => ({
2626
setOutput: jest.fn(),
2727
getInput: jest.fn(),
2828
getBooleanInput: jest.fn(),
29-
getMultilineInput: jest.fn(),
29+
getMultilineInput: jest.fn(() => []),
3030
addPath: jest.fn(),
3131
exportVariable: jest.fn(),
3232
saveState: jest.fn(),
@@ -57,6 +57,9 @@ const {M2_DIR, MVN_SETTINGS_FILE, STATE_GPG_HOME} =
5757
const __dirname = path.dirname(fileURLToPath(import.meta.url));
5858
const m2Dir = path.join(__dirname, M2_DIR);
5959
const settingsFile = path.join(m2Dir, MVN_SETTINGS_FILE);
60+
const credentials = (id: string, username: string, password: string) => [
61+
{id, usernameEnvVar: username, passwordEnvVar: password}
62+
];
6063

6164
describe('auth tests', () => {
6265
let spyOSHomedir: any;
@@ -73,6 +76,9 @@ describe('auth tests', () => {
7376

7477
afterEach(() => {
7578
(core.getInput as jest.Mock).mockReset();
79+
(core.getMultilineInput as jest.Mock).mockReset();
80+
(core.getMultilineInput as jest.Mock).mockReturnValue([]);
81+
(core.warning as jest.Mock).mockReset();
7682
(core.exportVariable as jest.Mock).mockReset();
7783
(gpg.importKey as jest.Mock).mockReset();
7884
(gpg.removeGpgHome as jest.Mock).mockReset();
@@ -100,9 +106,7 @@ describe('auth tests', () => {
100106
await io.rmRF(altHome); // ensure it doesn't already exist
101107

102108
await auth.createAuthenticationSettings(
103-
id,
104-
username,
105-
password,
109+
credentials(id, username, password),
106110
altHome,
107111
true
108112
);
@@ -113,7 +117,7 @@ describe('auth tests', () => {
113117
expect(fs.existsSync(altHome)).toBe(true);
114118
expect(fs.existsSync(altSettingsFile)).toBe(true);
115119
expect(fs.readFileSync(altSettingsFile, 'utf-8')).toEqual(
116-
auth.generate(id, username, password)
120+
auth.generate(credentials(id, username, password))
117121
);
118122

119123
await io.rmRF(altHome);
@@ -125,17 +129,15 @@ describe('auth tests', () => {
125129
const password = 'TOKEN';
126130

127131
await auth.createAuthenticationSettings(
128-
id,
129-
username,
130-
password,
132+
credentials(id, username, password),
131133
m2Dir,
132134
true
133135
);
134136

135137
expect(fs.existsSync(m2Dir)).toBe(true);
136138
expect(fs.existsSync(settingsFile)).toBe(true);
137139
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
138-
auth.generate(id, username, password)
140+
auth.generate(credentials(id, username, password))
139141
);
140142
}, 100000);
141143

@@ -146,9 +148,7 @@ describe('auth tests', () => {
146148
const gpgPassphrase = 'GPG';
147149

148150
await auth.createAuthenticationSettings(
149-
id,
150-
username,
151-
password,
151+
credentials(id, username, password),
152152
m2Dir,
153153
true,
154154
gpgPassphrase
@@ -157,7 +157,7 @@ describe('auth tests', () => {
157157
expect(fs.existsSync(m2Dir)).toBe(true);
158158
expect(fs.existsSync(settingsFile)).toBe(true);
159159
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
160-
auth.generate(id, username, password, gpgPassphrase)
160+
auth.generate(credentials(id, username, password), gpgPassphrase)
161161
);
162162
}, 100000);
163163

@@ -218,17 +218,15 @@ describe('auth tests', () => {
218218
expect(fs.existsSync(settingsFile)).toBe(true);
219219

220220
await auth.createAuthenticationSettings(
221-
id,
222-
username,
223-
password,
221+
credentials(id, username, password),
224222
m2Dir,
225223
true
226224
);
227225

228226
expect(fs.existsSync(m2Dir)).toBe(true);
229227
expect(fs.existsSync(settingsFile)).toBe(true);
230228
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
231-
auth.generate(id, username, password)
229+
auth.generate(credentials(id, username, password))
232230
);
233231
}, 100000);
234232

@@ -243,9 +241,7 @@ describe('auth tests', () => {
243241
expect(fs.existsSync(settingsFile)).toBe(true);
244242

245243
await auth.createAuthenticationSettings(
246-
id,
247-
username,
248-
password,
244+
credentials(id, username, password),
249245
m2Dir,
250246
false
251247
);
@@ -273,7 +269,9 @@ describe('auth tests', () => {
273269
</servers>
274270
</settings>`;
275271

276-
expect(auth.generate(id, username, password)).toEqual(expectedSettings);
272+
expect(auth.generate(credentials(id, username, password))).toEqual(
273+
expectedSettings
274+
);
277275
});
278276

279277
it('generates valid settings.xml with additional configuration', () => {
@@ -306,9 +304,9 @@ describe('auth tests', () => {
306304
</activeProfiles>
307305
</settings>`;
308306

309-
expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
310-
expectedSettings
311-
);
307+
expect(
308+
auth.generate(credentials(id, username, password), gpgPassphrase)
309+
).toEqual(expectedSettings);
312310
});
313311

314312
it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => {
@@ -330,9 +328,9 @@ describe('auth tests', () => {
330328
</servers>
331329
</settings>`;
332330

333-
expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
334-
expectedSettings
335-
);
331+
expect(
332+
auth.generate(credentials(id, username, password), gpgPassphrase)
333+
).toEqual(expectedSettings);
336334
});
337335

338336
it('escapes settings.xml values while preserving parsed semantics', () => {
@@ -341,7 +339,10 @@ describe('auth tests', () => {
341339
const password = `TOKEN&<>"'é`;
342340
const gpgPassphrase = `GPG&<>"'é`;
343341

344-
const xml = auth.generate(id, username, password, gpgPassphrase);
342+
const xml = auth.generate(
343+
credentials(id, username, password),
344+
gpgPassphrase
345+
);
345346
const parsed = parseXmlObject(xml) as any;
346347

347348
expect(parsed.settings.interactiveMode).toBe('false');
@@ -352,6 +353,144 @@ describe('auth tests', () => {
352353
expect(parsed.settings.activeProfiles.activeProfile).toBe('setup-java-gpg');
353354
});
354355

356+
it('generates ordered settings.xml entries for multiple servers', () => {
357+
const servers = [
358+
{
359+
id: 'releases',
360+
usernameEnvVar: 'RELEASES_USERNAME',
361+
passwordEnvVar: 'RELEASES_PASSWORD'
362+
},
363+
{
364+
id: 'snapshots',
365+
usernameEnvVar: 'SNAPSHOTS_USERNAME',
366+
passwordEnvVar: 'SNAPSHOTS_PASSWORD'
367+
}
368+
];
369+
370+
const parsed = parseXmlObject(auth.generate(servers)) as any;
371+
372+
expect(parsed.settings.servers.server).toEqual([
373+
{
374+
id: 'releases',
375+
username: '${env.RELEASES_USERNAME}',
376+
password: '${env.RELEASES_PASSWORD}'
377+
},
378+
{
379+
id: 'snapshots',
380+
username: '${env.SNAPSHOTS_USERNAME}',
381+
password: '${env.SNAPSHOTS_PASSWORD}'
382+
}
383+
]);
384+
});
385+
386+
it('parses and trims multiline Maven server credentials', () => {
387+
expect(
388+
auth.parseMavenServerCredentials([
389+
'',
390+
' releases : RELEASES_USERNAME : RELEASES_PASSWORD ',
391+
'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD'
392+
])
393+
).toEqual([
394+
{
395+
id: 'releases',
396+
usernameEnvVar: 'RELEASES_USERNAME',
397+
passwordEnvVar: 'RELEASES_PASSWORD'
398+
},
399+
{
400+
id: 'snapshots',
401+
usernameEnvVar: 'SNAPSHOTS_USERNAME',
402+
passwordEnvVar: 'SNAPSHOTS_PASSWORD'
403+
}
404+
]);
405+
});
406+
407+
it.each([
408+
{
409+
entries: ['releases:RELEASES_USERNAME'],
410+
error:
411+
'Invalid mvn-server-credentials entry at line 1. Expected format: server-id:USERNAME_ENV:PASSWORD_ENV'
412+
},
413+
{
414+
entries: ['', 'releases:RELEASES_USERNAME:RELEASES_PASSWORD:EXTRA'],
415+
error:
416+
'Invalid mvn-server-credentials entry at line 2. Expected format: server-id:USERNAME_ENV:PASSWORD_ENV'
417+
},
418+
{
419+
entries: ['releases::RELEASES_PASSWORD'],
420+
error:
421+
'Invalid mvn-server-credentials entry at line 1. server-id, username environment variable, and password environment variable are required'
422+
}
423+
])(
424+
'rejects malformed Maven server credentials: $entries',
425+
({entries, error}) => {
426+
expect(() => auth.parseMavenServerCredentials(entries)).toThrow(error);
427+
}
428+
);
429+
430+
it('rejects duplicate Maven server ids', () => {
431+
expect(() =>
432+
auth.parseMavenServerCredentials([
433+
'releases:RELEASES_USERNAME:RELEASES_PASSWORD',
434+
'releases:OTHER_USERNAME:OTHER_PASSWORD'
435+
])
436+
).toThrow("Duplicate server-id 'releases' in mvn-server-credentials input");
437+
});
438+
439+
it('uses multiline Maven server credentials instead of single-server inputs', () => {
440+
(core.getMultilineInput as jest.Mock).mockReturnValue([
441+
'releases:RELEASES_USERNAME:RELEASES_PASSWORD',
442+
'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD'
443+
]);
444+
445+
expect(auth.getMavenServerSettings()).toEqual([
446+
{
447+
id: 'releases',
448+
usernameEnvVar: 'RELEASES_USERNAME',
449+
passwordEnvVar: 'RELEASES_PASSWORD'
450+
},
451+
{
452+
id: 'snapshots',
453+
usernameEnvVar: 'SNAPSHOTS_USERNAME',
454+
passwordEnvVar: 'SNAPSHOTS_PASSWORD'
455+
}
456+
]);
457+
expect(core.getInput).not.toHaveBeenCalled();
458+
});
459+
460+
it('uses the existing single-server inputs when multiline credentials are absent', () => {
461+
(core.getInput as jest.Mock).mockImplementation((name: string) =>
462+
name === 'server-id' ? 'github' : ''
463+
);
464+
465+
expect(auth.getMavenServerSettings()).toEqual([
466+
{
467+
id: 'github',
468+
usernameEnvVar: 'GITHUB_ACTOR',
469+
passwordEnvVar: 'GITHUB_TOKEN'
470+
}
471+
]);
472+
});
473+
474+
it('preserves deprecated single-server aliases as fallback inputs', () => {
475+
(core.getInput as jest.Mock).mockImplementation((name: string) => {
476+
const inputs: Record<string, string> = {
477+
'server-id': 'legacy',
478+
'server-username': 'LEGACY_USERNAME',
479+
'server-password': 'LEGACY_PASSWORD'
480+
};
481+
return inputs[name] ?? '';
482+
});
483+
484+
expect(auth.getMavenServerSettings()).toEqual([
485+
{
486+
id: 'legacy',
487+
usernameEnvVar: 'LEGACY_USERNAME',
488+
passwordEnvVar: 'LEGACY_PASSWORD'
489+
}
490+
]);
491+
expect(core.warning).toHaveBeenCalledTimes(2);
492+
});
493+
355494
function xmlElementText(xml: string, tagName: string): string {
356495
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
357496
expect(match).not.toBeNull();

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ inputs:
6464
server-password:
6565
description: 'Deprecated alias for server-password-env-var'
6666
required: false
67+
mvn-server-credentials:
68+
description: 'Multiline list of Maven server credentials in the format `server-id:USERNAME_ENV:PASSWORD_ENV`. When set, replaces the single server configured by server-id, server-username-env-var, and server-password-env-var.'
69+
required: false
6770
settings-path:
6871
description: 'Path to where the settings.xml file will be written. Default is ~/.m2.'
6972
required: false

dist/cleanup/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30774,7 +30774,7 @@ module.exports = {
3077430774
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
3077530775
/* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS)
3077630776
/* harmony export */ });
30777-
/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */
30777+
/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, INPUT_MVN_SERVER_CREDENTIALS, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */
3077830778
const MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
3077930779
const INPUT_JAVA_VERSION = 'java-version';
3078030780
const INPUT_JAVA_VERSION_FILE = 'java-version-file';
@@ -30789,6 +30789,7 @@ const INPUT_SET_DEFAULT = 'set-default';
3078930789
const INPUT_PROBLEM_MATCHER = 'problem-matcher';
3079030790
const INPUT_VERIFY_SIGNATURE = 'verify-signature';
3079130791
const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
30792+
const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials';
3079230793
const INPUT_SERVER_ID = 'server-id';
3079330794
const INPUT_SERVER_USERNAME_ENV_VAR = 'server-username-env-var';
3079430795
const INPUT_SERVER_PASSWORD_ENV_VAR = 'server-password-env-var';

0 commit comments

Comments
 (0)