Added support for GPG

This commit is contained in:
Jared Petersen 2020-05-02 04:33:15 -07:00
parent 5c87b70ffe
commit 798fdaeebd
6 changed files with 164 additions and 26 deletions

View File

@ -29,27 +29,27 @@ Examples of version specifications that the java-version parameter will accept:
- A major Java version - A major Java version
e.g. ```6, 7, 8, 9, 10, 11, 12, 13, ...``` e.g. ```6, 7, 8, 9, 10, 11, 12, 13, ...```
- A semver Java version specification - A semver Java version specification
e.g. ```8.0.232, 7.0.181, 11.0.4``` e.g. ```8.0.232, 7.0.181, 11.0.4```
e.g. ```8.0.x, >11.0.3, >=13.0.1, <8.0.212``` e.g. ```8.0.x, >11.0.3, >=13.0.1, <8.0.212```
- An early access (EA) Java version - An early access (EA) Java version
e.g. ```14-ea, 15-ea``` e.g. ```14-ea, 15-ea```
e.g. ```14.0.0-ea, 15.0.0-ea``` e.g. ```14.0.0-ea, 15.0.0-ea```
e.g. ```14.0.0-ea.28, 15.0.0-ea.2``` (syntax for specifying an EA build number) e.g. ```14.0.0-ea.28, 15.0.0-ea.2``` (syntax for specifying an EA build number)
Note that, per semver rules, EA builds will be matched by explicit EA version specifications. Note that, per semver rules, EA builds will be matched by explicit EA version specifications.
- 1.x syntax - 1.x syntax
e.g. ```1.8``` (same as ```8```) e.g. ```1.8``` (same as ```8```)
e.g. ```1.8.0.212``` (same as ```8.0.212```) e.g. ```1.8.0.212``` (same as ```8.0.212```)
@ -113,12 +113,14 @@ jobs:
server-id: maven # Value of the distributionManagement/repository/id field of the pom.xml server-id: maven # Value of the distributionManagement/repository/id field of the pom.xml
server-username: MAVEN_USERNAME # env variable for username in deploy server-username: MAVEN_USERNAME # env variable for username in deploy
server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
- name: Publish to Apache Maven Central - name: Publish to Apache Maven Central
run: mvn deploy run: mvn deploy
env: env:
MAVEN_USERNAME: maven_username123 MAVEN_USERNAME: maven_username123
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
``` ```
The two `settings.xml` files created from the above example look like the following. The two `settings.xml` files created from the above example look like the following.
@ -131,6 +133,16 @@ The two `settings.xml` files created from the above example look like the follow
<username>${env.GITHUB_ACTOR}</username> <username>${env.GITHUB_ACTOR}</username>
<password>${env.GITHUB_TOKEN}</password> <password>${env.GITHUB_TOKEN}</password>
</server> </server>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>${env.GPG_PASSPHRASE}</gpg.passphrase>
</properties>
</profile>
<profiles>
</servers> </servers>
``` ```
@ -142,10 +154,20 @@ The two `settings.xml` files created from the above example look like the follow
<username>${env.MAVEN_USERNAME}</username> <username>${env.MAVEN_USERNAME}</username>
<password>${env.MAVEN_CENTRAL_TOKEN}</password> <password>${env.MAVEN_CENTRAL_TOKEN}</password>
</server> </server>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>${env.MAVEN_GPG_PASSPHRASE}</gpg.passphrase>
</properties>
</profile>
<profiles>
</servers> </servers>
``` ```
***NOTE: The `settings.xml` file is created in the Actions $HOME directory. If you have an existing `settings.xml` file at that location, it will be overwritten. See below for using the `settings-path` to change your `settings.xml` file location.*** ***NOTE: The `settings.xml` file is created in the Actions $HOME directory. If you have an existing `settings.xml` file at that location, it will be overwritten. See below for using the `settings-path` to change your `settings.xml` file location.***
See the help docs on [Publishing a Package](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-apache-maven-for-use-with-github-packages#publishing-a-package) for more information on the `pom.xml` file. See the help docs on [Publishing a Package](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-apache-maven-for-use-with-github-packages#publishing-a-package) for more information on the `pom.xml` file.
@ -172,7 +194,7 @@ jobs:
PASSWORD: ${{ secrets.GITHUB_TOKEN }} PASSWORD: ${{ secrets.GITHUB_TOKEN }}
``` ```
***NOTE: The `USERNAME` and `PASSWORD` need to correspond to the credentials environment variables used in the publishing section of your `build.gradle`.*** ***NOTE: The `USERNAME` and `PASSWORD` need to correspond to the credentials environment variables used in the publishing section of your `build.gradle`.***
See the help docs on [Publishing a Package with Gradle](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-gradle-for-use-with-github-packages#example-using-gradle-groovy-for-a-single-package-in-a-repository) for more information on the `build.gradle` configuration file. See the help docs on [Publishing a Package with Gradle](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-gradle-for-use-with-github-packages#example-using-gradle-groovy-for-a-single-package-in-a-repository) for more information on the `build.gradle` configuration file.

View File

@ -2,6 +2,7 @@ import io = require('@actions/io');
import fs = require('fs'); import fs = require('fs');
import os = require('os'); import os = require('os');
import path = require('path'); import path = require('path');
import exec = require('@actions/exec');
// make the os.homedir() call be local to the tests // make the os.homedir() call be local to the tests
jest.doMock('os', () => { jest.doMock('os', () => {
@ -10,10 +11,19 @@ jest.doMock('os', () => {
}; };
}); });
jest.mock('@actions/exec', () => {
return {
exec: jest.fn()
};
});
import * as auth from '../src/auth'; import * as auth from '../src/auth';
const env = process.env;
const m2Dir = path.join(__dirname, auth.M2_DIR); const m2Dir = path.join(__dirname, auth.M2_DIR);
const settingsFile = path.join(m2Dir, auth.SETTINGS_FILE); const settingsFile = path.join(m2Dir, auth.SETTINGS_FILE);
const gpgDir = path.join(__dirname, auth.GPG_DIR);
const gpgFile = path.join(gpgDir, auth.GPG_FILE);
describe('auth tests', () => { describe('auth tests', () => {
beforeEach(async () => { beforeEach(async () => {
@ -23,6 +33,7 @@ describe('auth tests', () => {
afterAll(async () => { afterAll(async () => {
try { try {
await io.rmRF(m2Dir); await io.rmRF(m2Dir);
await io.rmRF(gpgDir);
} catch { } catch {
console.log('Failed to remove test directories'); console.log('Failed to remove test directories');
} }
@ -53,17 +64,25 @@ describe('auth tests', () => {
await io.rmRF(altHome); await io.rmRF(altHome);
}, 100000); }, 100000);
it('creates settings.xml with username and password', async () => { it('creates settings.xml with all data', async () => {
const id = 'packages'; const id = 'packages';
const username = 'UNAME'; const username = 'UNAME';
const password = 'TOKEN'; const password = 'TOKEN';
const gpgPrivateKey = 'PRIVATE';
const gpgPassphrase = 'GPG';
await auth.configAuthentication(id, username, password); await auth.configAuthentication(
id,
username,
password,
gpgPrivateKey,
gpgPassphrase
);
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(settingsFile)).toBe(true); expect(fs.existsSync(settingsFile)).toBe(true);
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual( expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
auth.generate(id, username, password) auth.generate(id, username, password, gpgPassphrase)
); );
}, 100000); }, 100000);
@ -128,8 +147,9 @@ describe('auth tests', () => {
const id = 'packages'; const id = 'packages';
const username = 'USER'; const username = 'USER';
const password = '&<>"\'\'"><&'; const password = '&<>"\'\'"><&';
const gpgPassphrase = 'PASSWORD';
expect(auth.generate(id, username, password)).toEqual(` expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(`
<settings> <settings>
<servers> <servers>
<server> <server>
@ -138,7 +158,44 @@ describe('auth tests', () => {
<password>\${env.&amp;&lt;&gt;&quot;&apos;&apos;&quot;&gt;&lt;&amp;}</password> <password>\${env.&amp;&lt;&gt;&quot;&apos;&apos;&quot;&gt;&lt;&amp;}</password>
</server> </server>
</servers> </servers>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>\${env.${gpgPassphrase}}</gpg.passphrase>
</properties>
</profile>
<profiles>
</settings> </settings>
`); `);
}); });
it('imports gpg private key', async () => {
const id = 'packages';
const username = 'USERNAME';
const password = 'PASSWORD';
const gpgPrivateKey = 'KEY CONTENTS';
await auth.configAuthentication(id, username, password, gpgPrivateKey);
expect(exec.exec).toHaveBeenCalledWith(`gpg --import --batch ${gpgFile}`);
expect(fs.existsSync(gpgDir)).toBe(false);
}, 100000);
it('does not import gpg private key when private key is not set', async () => {
const id = 'packages';
const username = 'USERNAME';
const password = 'PASSWORD';
await auth.configAuthentication(id, username, password);
expect(exec.exec).not.toHaveBeenCalledWith(
`gpg --import --batch ${gpgFile}`
);
expect(fs.existsSync(gpgDir)).toBe(false);
}, 100000);
}); });

View File

@ -36,6 +36,14 @@ inputs:
settings-path: settings-path:
description: 'Path to where the settings.xml file will be written. Default is ~/.m2.' description: 'Path to where the settings.xml file will be written. Default is ~/.m2.'
required: false required: false
gpg-private-key:
description: 'Environment variable name for the GPG private key to import. Default is
$GPG_PRIVATE_KEY.'
required: false
gpg-passphrase:
description: 'Environment variable name for the GPG private key passphrase. Default is
$GPG_PASSPHRASE.'
required: false
runs: runs:
using: 'node12' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'

BIN
dist/index.js generated vendored

Binary file not shown.

View File

@ -3,32 +3,57 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as io from '@actions/io'; import * as io from '@actions/io';
import * as exec from '@actions/exec';
export const M2_DIR = '.m2'; export const M2_DIR = '.m2';
export const SETTINGS_FILE = 'settings.xml'; export const SETTINGS_FILE = 'settings.xml';
export const GPG_DIR = '.gpgtmp';
export const GPG_FILE = 'private.asc';
export const DEFAULT_ID = 'github'; export const DEFAULT_ID = 'github';
export const DEFAULT_USERNAME = 'GITHUB_ACTOR'; export const DEFAULT_USERNAME = 'GITHUB_ACTOR';
export const DEFAULT_PASSWORD = 'GITHUB_TOKEN'; export const DEFAULT_PASSWORD = 'GITHUB_TOKEN';
export const DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
export const DEFAULT_GPG_PRIVATE_KEY = '';
export async function configAuthentication( export async function configAuthentication(
id = DEFAULT_ID, id = DEFAULT_ID,
username = DEFAULT_USERNAME, username = DEFAULT_USERNAME,
password = DEFAULT_PASSWORD password = DEFAULT_PASSWORD,
gpgPrivateKey = DEFAULT_GPG_PRIVATE_KEY,
gpgPassphrase = DEFAULT_GPG_PASSPHRASE
) { ) {
console.log( console.log(
`creating ${SETTINGS_FILE} with server-id: ${id};`, `creating ${SETTINGS_FILE} with server-id: ${id};`,
`environment variables: username=\$${username} and password=\$${password}` 'environment variables:',
`username=\$${username},`,
`password=\$${password},`,
`and gpg-passphrase=\$${gpgPassphrase}`
); );
// when an alternate m2 location is specified use only that location (no .m2 directory) // when an alternate m2 location is specified use only that location (no .m2 directory)
// otherwise use the home/.m2/ path // otherwise use the home/.m2/ path
const directory: string = path.join( const settingsDirectory: string = path.join(
core.getInput('settings-path') || os.homedir(), core.getInput('settings-path') || os.homedir(),
core.getInput('settings-path') ? '' : M2_DIR core.getInput('settings-path') ? '' : M2_DIR
); );
await io.mkdirP(directory); await io.mkdirP(settingsDirectory);
core.debug(`created directory ${directory}`); core.debug(`created directory ${settingsDirectory}`);
await write(directory, generate(id, username, password)); await write(
settingsDirectory,
SETTINGS_FILE,
generate(id, username, password, gpgPassphrase)
);
if (gpgPrivateKey !== DEFAULT_GPG_PRIVATE_KEY) {
console.log('importing gpg key');
const gpgDirectory: string = path.join(os.homedir(), GPG_DIR);
await io.mkdirP(gpgDirectory);
core.debug(`created directory ${gpgDirectory}`);
await write(gpgDirectory, GPG_FILE, gpgPrivateKey);
await importGpgKey(gpgDirectory, GPG_FILE);
await io.rmRF(gpgDirectory);
core.debug(`removed directory ${gpgDirectory}`);
}
} }
function escapeXML(value: string) { function escapeXML(value: string) {
@ -44,7 +69,8 @@ function escapeXML(value: string) {
export function generate( export function generate(
id = DEFAULT_ID, id = DEFAULT_ID,
username = DEFAULT_USERNAME, username = DEFAULT_USERNAME,
password = DEFAULT_PASSWORD password = DEFAULT_PASSWORD,
gpgPassphrase = DEFAULT_GPG_PASSPHRASE
) { ) {
return ` return `
<settings> <settings>
@ -55,20 +81,35 @@ export function generate(
<password>\${env.${escapeXML(password)}}</password> <password>\${env.${escapeXML(password)}}</password>
</server> </server>
</servers> </servers>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>\${env.${escapeXML(gpgPassphrase)}}</gpg.passphrase>
</properties>
</profile>
<profiles>
</settings> </settings>
`; `;
} }
async function write(directory: string, settings: string) { async function write(directory: string, file: string, contents: string) {
const location = path.join(directory, SETTINGS_FILE); const location = path.join(directory, file);
if (fs.existsSync(location)) { if (fs.existsSync(location)) {
console.warn(`overwriting existing file ${location}`); console.warn(`overwriting existing file ${location}`);
} else { } else {
console.log(`writing ${location}`); console.log(`writing ${location}`);
} }
return fs.writeFileSync(location, settings, { return fs.writeFileSync(location, contents, {
encoding: 'utf-8', encoding: 'utf-8',
flag: 'w' flag: 'w'
}); });
} }
async function importGpgKey(directory: string, file: string) {
const location = path.join(directory, file);
exec.exec(`gpg --import --batch ${location}`);
}

View File

@ -23,8 +23,18 @@ async function run() {
core.getInput('server-username', {required: false}) || undefined; core.getInput('server-username', {required: false}) || undefined;
const password = const password =
core.getInput('server-password', {required: false}) || undefined; core.getInput('server-password', {required: false}) || undefined;
const gpgPassphrase =
core.getInput('gpg-passphrase', {required: false}) || undefined;
const gpgPrivateKey =
core.getInput('gpg-private-key', {required: false}) || undefined;
await auth.configAuthentication(id, username, password); await auth.configAuthentication(
id,
username,
password,
gpgPassphrase,
gpgPrivateKey
);
} catch (error) { } catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
} }