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 208 additions and 37 deletions

View file

@ -113,12 +113,14 @@ jobs:
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-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
run: mvn deploy
env:
MAVEN_USERNAME: maven_username123
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.
@ -131,6 +133,16 @@ The two `settings.xml` files created from the above example look like the follow
<username>${env.GITHUB_ACTOR}</username>
<password>${env.GITHUB_TOKEN}</password>
</server>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>${env.GPG_PASSPHRASE}</gpg.passphrase>
</properties>
</profile>
<profiles>
</servers>
```
@ -142,6 +154,16 @@ The two `settings.xml` files created from the above example look like the follow
<username>${env.MAVEN_USERNAME}</username>
<password>${env.MAVEN_CENTRAL_TOKEN}</password>
</server>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>${env.MAVEN_GPG_PASSPHRASE}</gpg.passphrase>
</properties>
</profile>
<profiles>
</servers>
```

View file

@ -2,6 +2,7 @@ import io = require('@actions/io');
import fs = require('fs');
import os = require('os');
import path = require('path');
import exec = require('@actions/exec');
// make the os.homedir() call be local to the tests
jest.doMock('os', () => {
@ -10,10 +11,19 @@ jest.doMock('os', () => {
};
});
jest.mock('@actions/exec', () => {
return {
exec: jest.fn()
};
});
import * as auth from '../src/auth';
const env = process.env;
const m2Dir = path.join(__dirname, auth.M2_DIR);
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', () => {
beforeEach(async () => {
@ -23,6 +33,7 @@ describe('auth tests', () => {
afterAll(async () => {
try {
await io.rmRF(m2Dir);
await io.rmRF(gpgDir);
} catch {
console.log('Failed to remove test directories');
}
@ -53,17 +64,25 @@ describe('auth tests', () => {
await io.rmRF(altHome);
}, 100000);
it('creates settings.xml with username and password', async () => {
it('creates settings.xml with all data', async () => {
const id = 'packages';
const username = 'UNAME';
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(settingsFile)).toBe(true);
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
auth.generate(id, username, password)
auth.generate(id, username, password, gpgPassphrase)
);
}, 100000);
@ -128,8 +147,9 @@ describe('auth tests', () => {
const id = 'packages';
const username = 'USER';
const password = '&<>"\'\'"><&';
const gpgPassphrase = 'PASSWORD';
expect(auth.generate(id, username, password)).toEqual(`
expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(`
<settings>
<servers>
<server>
@ -138,7 +158,44 @@ describe('auth tests', () => {
<password>\${env.&amp;&lt;&gt;&quot;&apos;&apos;&quot;&gt;&lt;&amp;}</password>
</server>
</servers>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>\${env.${gpgPassphrase}}</gpg.passphrase>
</properties>
</profile>
<profiles>
</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:
description: 'Path to where the settings.xml file will be written. Default is ~/.m2.'
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:
using: 'node12'
main: 'dist/index.js'

55
dist/index.js generated vendored
View file

@ -2879,20 +2879,35 @@ const os = __importStar(__webpack_require__(87));
const path = __importStar(__webpack_require__(622));
const core = __importStar(__webpack_require__(470));
const io = __importStar(__webpack_require__(1));
const exec = __importStar(__webpack_require__(986));
exports.M2_DIR = '.m2';
exports.SETTINGS_FILE = 'settings.xml';
exports.GPG_DIR = '.gpgtmp';
exports.GPG_FILE = 'private.asc';
exports.DEFAULT_ID = 'github';
exports.DEFAULT_USERNAME = 'GITHUB_ACTOR';
exports.DEFAULT_PASSWORD = 'GITHUB_TOKEN';
function configAuthentication(id = exports.DEFAULT_ID, username = exports.DEFAULT_USERNAME, password = exports.DEFAULT_PASSWORD) {
exports.DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
exports.DEFAULT_GPG_PRIVATE_KEY = '';
function configAuthentication(id = exports.DEFAULT_ID, username = exports.DEFAULT_USERNAME, password = exports.DEFAULT_PASSWORD, gpgPrivateKey = exports.DEFAULT_GPG_PRIVATE_KEY, gpgPassphrase = exports.DEFAULT_GPG_PASSPHRASE) {
return __awaiter(this, void 0, void 0, function* () {
console.log(`creating ${exports.SETTINGS_FILE} with server-id: ${id};`, `environment variables: username=\$${username} and password=\$${password}`);
console.log(`creating ${exports.SETTINGS_FILE} with server-id: ${id};`, 'environment variables:', `username=\$${username},`, `password=\$${password},`, `and gpg-passphrase=\$${gpgPassphrase}`);
// when an alternate m2 location is specified use only that location (no .m2 directory)
// otherwise use the home/.m2/ path
const directory = path.join(core.getInput('settings-path') || os.homedir(), core.getInput('settings-path') ? '' : exports.M2_DIR);
yield io.mkdirP(directory);
core.debug(`created directory ${directory}`);
yield write(directory, generate(id, username, password));
const settingsDirectory = path.join(core.getInput('settings-path') || os.homedir(), core.getInput('settings-path') ? '' : exports.M2_DIR);
yield io.mkdirP(settingsDirectory);
core.debug(`created directory ${settingsDirectory}`);
yield write(settingsDirectory, exports.SETTINGS_FILE, generate(id, username, password, gpgPassphrase));
if (gpgPrivateKey !== exports.DEFAULT_GPG_PRIVATE_KEY) {
console.log('importing gpg key');
const gpgDirectory = path.join(os.homedir(), exports.GPG_DIR);
yield io.mkdirP(gpgDirectory);
core.debug(`created directory ${gpgDirectory}`);
yield write(gpgDirectory, exports.GPG_FILE, gpgPrivateKey);
yield importGpgKey(gpgDirectory, exports.GPG_FILE);
yield io.rmRF(gpgDirectory);
core.debug(`removed directory ${gpgDirectory}`);
}
});
}
exports.configAuthentication = configAuthentication;
@ -2905,7 +2920,7 @@ function escapeXML(value) {
.replace(/'/g, '&apos;');
}
// only exported for testing purposes
function generate(id = exports.DEFAULT_ID, username = exports.DEFAULT_USERNAME, password = exports.DEFAULT_PASSWORD) {
function generate(id = exports.DEFAULT_ID, username = exports.DEFAULT_USERNAME, password = exports.DEFAULT_PASSWORD, gpgPassphrase = exports.DEFAULT_GPG_PASSPHRASE) {
return `
<settings>
<servers>
@ -2915,25 +2930,41 @@ function generate(id = exports.DEFAULT_ID, username = exports.DEFAULT_USERNAME,
<password>\${env.${escapeXML(password)}}</password>
</server>
</servers>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>\${env.${escapeXML(gpgPassphrase)}}</gpg.passphrase>
</properties>
</profile>
<profiles>
</settings>
`;
}
exports.generate = generate;
function write(directory, settings) {
function write(directory, file, contents) {
return __awaiter(this, void 0, void 0, function* () {
const location = path.join(directory, exports.SETTINGS_FILE);
const location = path.join(directory, file);
if (fs.existsSync(location)) {
console.warn(`overwriting existing file ${location}`);
}
else {
console.log(`writing ${location}`);
}
return fs.writeFileSync(location, settings, {
return fs.writeFileSync(location, contents, {
encoding: 'utf-8',
flag: 'w'
});
});
}
function importGpgKey(directory, file) {
return __awaiter(this, void 0, void 0, function* () {
const location = path.join(directory, file);
exec.exec(`gpg --import --batch ${location}`);
});
}
/***/ }),
@ -4563,7 +4594,9 @@ function run() {
const id = core.getInput('server-id', { required: false }) || undefined;
const username = core.getInput('server-username', { required: false }) || undefined;
const password = core.getInput('server-password', { required: false }) || undefined;
yield auth.configAuthentication(id, username, password);
const gpgPassphrase = core.getInput('gpg-passphrase', { required: false }) || undefined;
const gpgPrivateKey = core.getInput('gpg-private-key', { required: false }) || undefined;
yield auth.configAuthentication(id, username, password, gpgPassphrase, gpgPrivateKey);
}
catch (error) {
core.setFailed(error.message);

View file

@ -3,32 +3,57 @@ import * as os from 'os';
import * as path from 'path';
import * as core from '@actions/core';
import * as io from '@actions/io';
import * as exec from '@actions/exec';
export const M2_DIR = '.m2';
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_USERNAME = 'GITHUB_ACTOR';
export const DEFAULT_PASSWORD = 'GITHUB_TOKEN';
export const DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
export const DEFAULT_GPG_PRIVATE_KEY = '';
export async function configAuthentication(
id = DEFAULT_ID,
username = DEFAULT_USERNAME,
password = DEFAULT_PASSWORD
password = DEFAULT_PASSWORD,
gpgPrivateKey = DEFAULT_GPG_PRIVATE_KEY,
gpgPassphrase = DEFAULT_GPG_PASSPHRASE
) {
console.log(
`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)
// 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') ? '' : M2_DIR
);
await io.mkdirP(directory);
core.debug(`created directory ${directory}`);
await write(directory, generate(id, username, password));
await io.mkdirP(settingsDirectory);
core.debug(`created directory ${settingsDirectory}`);
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) {
@ -44,7 +69,8 @@ function escapeXML(value: string) {
export function generate(
id = DEFAULT_ID,
username = DEFAULT_USERNAME,
password = DEFAULT_PASSWORD
password = DEFAULT_PASSWORD,
gpgPassphrase = DEFAULT_GPG_PASSPHRASE
) {
return `
<settings>
@ -55,20 +81,35 @@ export function generate(
<password>\${env.${escapeXML(password)}}</password>
</server>
</servers>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>\${env.${escapeXML(gpgPassphrase)}}</gpg.passphrase>
</properties>
</profile>
<profiles>
</settings>
`;
}
async function write(directory: string, settings: string) {
const location = path.join(directory, SETTINGS_FILE);
async function write(directory: string, file: string, contents: string) {
const location = path.join(directory, file);
if (fs.existsSync(location)) {
console.warn(`overwriting existing file ${location}`);
} else {
console.log(`writing ${location}`);
}
return fs.writeFileSync(location, settings, {
return fs.writeFileSync(location, contents, {
encoding: 'utf-8',
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;
const password =
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) {
core.setFailed(error.message);
}