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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions .github/workflows/e2e-publishing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,25 @@ jobs:
$xmlPath = Join-Path $HOME ".m2" "settings.xml"
Get-Content $xmlPath | ForEach-Object { Write-Host $_ }

[xml]$xml = Get-Content $xmlPath
$servers = $xml.settings.servers.server
if (($servers[0].id -ne 'maven') -or ($servers[0].username -ne '${env.MAVEN_USERNAME}') -or ($servers[0].password -ne '${env.MAVEN_CENTRAL_TOKEN}')) {
throw "Generated XML file is incorrect"
}
$content = [System.IO.File]::ReadAllText($xmlPath)
$expected = @(
'<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"'
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
' xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">'
' <interactiveMode>false</interactiveMode>'
' <servers>'
' <server>'
' <id>maven</id>'
' <username>${env.MAVEN_USERNAME}</username>'
' <password>${env.MAVEN_CENTRAL_TOKEN}</password>'
' </server>'
' </servers>'
'</settings>'
) -join "`n"

if (($servers[1].id -ne 'gpg.passphrase') -or ($servers[1].passphrase -ne '${env.MAVEN_GPG_PASSPHRASE}')) {
if ($content -ne $expected) {
Write-Host "Expected settings.xml:"
$expected -split "`n" | ForEach-Object { Write-Host $_ }
throw "Generated XML file is incorrect"
}

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ This action allows you to work with Java and Scala projects.

- **Migrated to ESM** to enable support for the latest `@actions/*` package versions. This is an internal implementation change only. No changes are required to your workflow configuration, and the action's behavior is unchanged. Existing workflows continue to work as before.

## Breaking changes in V6

- **The GPG passphrase is now passed to the Maven GPG Plugin through an environment variable (`gpg.passphraseEnvName`) instead of the deprecated `gpg.passphrase` server in `settings.xml`.** The `gpg-passphrase` input and its default (`GPG_PASSPHRASE`) are unchanged, so if you already set that environment variable in your build step your workflow keeps working. However, this now requires `maven-gpg-plugin` **3.2.0 or newer**; older versions do not honor `gpg.passphraseEnvName` and, because the `gpg.passphrase` server is no longer written, will not pick up the passphrase. Upgrade the plugin to 3.2.0+.

See [GPG](docs/advanced-usage.md#gpg) for details.

## Breaking changes in V5

- Upgraded action from node20 to node24
Expand Down
35 changes: 33 additions & 2 deletions __tests__/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,40 @@ describe('auth tests', () => {
<username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
</server>
</servers>
<profiles>
<profile>
<id>setup-java-gpg</id>
<properties>
<gpg.passphraseEnvName>${gpgPassphrase}</gpg.passphraseEnvName>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>setup-java-gpg</activeProfile>
</activeProfiles>
</settings>`;

expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
expectedSettings
);
});

it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => {
const id = 'packages';
const username = 'USER';
const password = '&<>"\'\'"><&';
const gpgPassphrase = 'MAVEN_GPG_PASSPHRASE';

const expectedSettings = `<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<interactiveMode>false</interactiveMode>
<servers>
<server>
<id>gpg.passphrase</id>
<passphrase>\${env.${gpgPassphrase}}</passphrase>
<id>${id}</id>
<username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
</server>
</servers>
</settings>`;
Expand Down
6 changes: 6 additions & 0 deletions dist/cleanup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95849,6 +95849,12 @@ const INPUT_GPG_PRIVATE_KEY = 'gpg-private-key';
const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
const INPUT_DEFAULT_GPG_PRIVATE_KEY = (/* unused pure expression or super */ null && (undefined));
const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
// The default name of the environment variable the maven-gpg-plugin reads the
// passphrase from (property `gpg.passphraseEnvName`). When the configured
// passphrase env var name matches this, no extra configuration is required.
const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
const INPUT_CACHE = 'cache';
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
const INPUT_JOB_STATUS = 'job-status';
Expand Down
29 changes: 24 additions & 5 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73294,6 +73294,12 @@ const INPUT_GPG_PRIVATE_KEY = 'gpg-private-key';
const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined;
const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
// The default name of the environment variable the maven-gpg-plugin reads the
// passphrase from (property `gpg.passphraseEnvName`). When the configured
// passphrase env var name matches this, no extra configuration is required.
const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
const INPUT_CACHE = 'cache';
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
const constants_INPUT_JOB_STATUS = 'job-status';
Expand Down Expand Up @@ -127311,12 +127317,25 @@ function generate(id, username, password, gpgPassphrase) {
}
}
};
if (gpgPassphrase) {
const gpgServer = {
id: 'gpg.passphrase',
passphrase: `\${env.${gpgPassphrase}}`
// The maven-gpg-plugin reads the passphrase from the environment variable
// named by the `gpg.passphraseEnvName` property (default MAVEN_GPG_PASSPHRASE).
// Only configure it when the requested env var name differs from that default;
// otherwise the plugin already reads the right variable and no extra settings
// are needed. Writing `gpg.passphrase` to settings.xml is deprecated and fails
// when the plugin's `bestPractices` mode is enabled.
if (gpgPassphrase &&
gpgPassphrase !== MAVEN_GPG_PASSPHRASE_DEFAULT_ENV) {
xmlObj.settings.profiles = {
profile: {
id: GPG_PASSPHRASE_PROFILE_ID,
properties: {
'gpg.passphraseEnvName': gpgPassphrase
}
}
};
xmlObj.settings.activeProfiles = {
activeProfile: GPG_PASSPHRASE_PROFILE_ID
};
xmlObj.settings.servers.server.push(gpgServer);
}
return (0,lib/* create */.vt)(xmlObj).end({
headless: true,
Expand Down
30 changes: 23 additions & 7 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,9 @@ See the help docs on [Publishing a Package](https://help.github.com/en/github/ma

#### Legacy / alternative: let setup-java import the key

If you use `maven-gpg-plugin` older than 3.2.0, or you prefer signing with the `gpg` executable, you can let setup-java import the key instead by providing the `gpg-private-key` and `gpg-passphrase` inputs. The private key is written to a file in the runner's temp directory, imported into the GPG keychain, and the file is promptly removed before proceeding with the rest of the setup process. A cleanup step removes the imported private key from the GPG keychain after the job completes regardless of the job status. This ensures that the private key is no longer accessible on self-hosted runners and cannot "leak" between jobs (hosted runners are always clean instances).
If you prefer signing with the `gpg` executable (for example because you are using `maven-gpg-plugin` older than 3.2.0), you can let setup-java import the key instead by providing the `gpg-private-key` and `gpg-passphrase` inputs. The private key is written to a file in the runner's temp directory, imported into the GPG keychain, and the file is promptly removed before proceeding with the rest of the setup process. A cleanup step removes the imported private key from the GPG keychain after the job completes regardless of the job status. This ensures that the private key is no longer accessible on self-hosted runners and cannot "leak" between jobs (hosted runners are always clean instances).

setup-java imports the key independently of the plugin version, but the generated passphrase profile described below uses `gpg.passphraseEnvName`, which requires `maven-gpg-plugin` 3.2.0 or newer. Since `gpg-passphrase` defaults to `GPG_PASSPHRASE`, setup-java writes that profile unless you override the input to `MAVEN_GPG_PASSPHRASE`.

```yaml
- name: Set up Apache Maven Central
Expand All @@ -682,15 +684,29 @@ If you use `maven-gpg-plugin` older than 3.2.0, or you prefer signing with the `
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
```

With these inputs, setup-java adds a `gpg.passphrase` server to the generated `settings.xml`:
The `gpg-passphrase` input is the **name of the environment variable** that holds the passphrase (not the passphrase itself). It defaults to `GPG_PASSPHRASE`. The [Maven GPG Plugin](https://maven.apache.org/plugins/maven-gpg-plugin/) reads the passphrase from the environment variable named by its `gpg.passphraseEnvName` property, whose own default is `MAVEN_GPG_PASSPHRASE`.

- If `gpg-passphrase` is `MAVEN_GPG_PASSPHRASE`, the plugin already reads that variable by default, so setup-java writes nothing extra to `settings.xml`.
- Otherwise (including the default `GPG_PASSPHRASE`), setup-java configures `gpg.passphraseEnvName` through an active profile in the generated `settings.xml` so the plugin reads the passphrase from that variable. For the default `gpg-passphrase: GPG_PASSPHRASE`:

```xml
<server>
<id>gpg.passphrase</id>
<passphrase>${env.MAVEN_GPG_PASSPHRASE}</passphrase>
</server>
<profiles>
<profile>
<id>setup-java-gpg</id>
<properties>
<gpg.passphraseEnvName>GPG_PASSPHRASE</gpg.passphraseEnvName>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>setup-java-gpg</activeProfile>
</activeProfiles>
```

> **Note:** Earlier versions of setup-java wrote a `gpg.passphrase` server to `settings.xml`. That mechanism is deprecated by the Maven GPG Plugin and fails when its `bestPractices` mode is enabled, so setup-java now relies on `gpg.passphraseEnvName` instead. The `gpg-passphrase` input and its `GPG_PASSPHRASE` default are unchanged, so existing workflows that set the `GPG_PASSPHRASE` environment variable keep working.

> **Compatibility note:** Reading the passphrase from an environment variable (`gpg.passphraseEnvName`) requires `maven-gpg-plugin` 3.2.0 or newer. Older versions do not honor this property and will not pick up the passphrase, because setup-java no longer writes the deprecated `gpg.passphrase` server to `settings.xml`. If you are pinned to `maven-gpg-plugin` older than 3.2.0, upgrade to 3.2.0+.

When signing with the `gpg` executable, the Maven GPG Plugin configuration in your `pom.xml` should contain the following structure to avoid possible issues like `Inappropriate ioctl for device` or `gpg: signing failed: No such file or directory`:

```xml
Expand All @@ -703,7 +719,7 @@ When signing with the `gpg` executable, the Maven GPG Plugin configuration in yo
</configuration>
```

GPG 2.1 requires `--pinentry-mode` to be set to `loopback` in order to pick up the `gpg.passphrase` value defined in Maven `settings.xml`.
GPG 2.1 requires `--pinentry-mode` to be set to `loopback` in order to read the passphrase non-interactively.

***NOTE***: If, when using the default `gpg` signer, the error `gpg: Sorry, no terminal at all requested - can't get input` [is encountered](https://github.com/actions/setup-java/issues/554), please update the version of `maven-gpg-plugin` to 1.6 or higher.

Expand Down
25 changes: 20 additions & 5 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,27 @@ export function generate(
}
};

if (gpgPassphrase) {
const gpgServer = {
id: 'gpg.passphrase',
passphrase: `\${env.${gpgPassphrase}}`
// The maven-gpg-plugin reads the passphrase from the environment variable
// named by the `gpg.passphraseEnvName` property (default MAVEN_GPG_PASSPHRASE).
// Only configure it when the requested env var name differs from that default;
// otherwise the plugin already reads the right variable and no extra settings
// are needed. Writing `gpg.passphrase` to settings.xml is deprecated and fails
// when the plugin's `bestPractices` mode is enabled.
if (
gpgPassphrase &&
gpgPassphrase !== constants.MAVEN_GPG_PASSPHRASE_DEFAULT_ENV
) {
xmlObj.settings.profiles = {
profile: {
id: constants.GPG_PASSPHRASE_PROFILE_ID,
properties: {
'gpg.passphraseEnvName': gpgPassphrase
}
}
};
xmlObj.settings.activeProfiles = {
activeProfile: constants.GPG_PASSPHRASE_PROFILE_ID
};
xmlObj.settings.servers.server.push(gpgServer);
}

return xmlCreate(xmlObj).end({
Expand Down
8 changes: 8 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
export const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined;
export const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';

// The default name of the environment variable the maven-gpg-plugin reads the
// passphrase from (property `gpg.passphraseEnvName`). When the configured
// passphrase env var name matches this, no extra configuration is required.
export const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';

// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
export const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';

export const INPUT_CACHE = 'cache';
export const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
export const INPUT_JOB_STATUS = 'job-status';
Expand Down
Loading