From 798fdaeebd3e9f21ef2de4dae3f65e9cd027f527 Mon Sep 17 00:00:00 2001 From: Jared Petersen Date: Sat, 2 May 2020 04:33:15 -0700 Subject: [PATCH] Added support for GPG --- README.md | 44 +++++++++++++++++++++------- __tests__/auth.test.ts | 65 ++++++++++++++++++++++++++++++++++++++--- action.yml | 8 +++++ dist/index.js | Bin 168248 -> 169988 bytes src/auth.ts | 61 +++++++++++++++++++++++++++++++------- src/setup-java.ts | 12 +++++++- 6 files changed, 164 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c7f492b3..3ffbcc3b 100644 --- a/README.md +++ b/README.md @@ -29,27 +29,27 @@ Examples of version specifications that the java-version parameter will accept: - A major Java version e.g. ```6, 7, 8, 9, 10, 11, 12, 13, ...``` - + - A semver Java version specification 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``` - + - An early access (EA) Java version e.g. ```14-ea, 15-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) - + Note that, per semver rules, EA builds will be matched by explicit EA version specifications. - + - 1.x syntax e.g. ```1.8``` (same as ```8```) - + 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-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 + 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 ${env.GITHUB_ACTOR} ${env.GITHUB_TOKEN} + + + + true + + + ${env.GPG_PASSPHRASE} + + + ``` @@ -142,10 +154,20 @@ The two `settings.xml` files created from the above example look like the follow ${env.MAVEN_USERNAME} ${env.MAVEN_CENTRAL_TOKEN} + + + + true + + + ${env.MAVEN_GPG_PASSPHRASE} + + + ``` -***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. @@ -172,7 +194,7 @@ jobs: 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. diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index 13509682..54ffa4d3 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -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(` @@ -138,7 +158,44 @@ describe('auth tests', () => { \${env.&<>"''"><&} + + + + true + + + \${env.${gpgPassphrase}} + + + `); }); + + 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); }); diff --git a/action.yml b/action.yml index de7711fa..a468b5fb 100644 --- a/action.yml +++ b/action.yml @@ -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' diff --git a/dist/index.js b/dist/index.js index 9663265367c82ea787bffe9122dd3970af3e1aad..b3fd50c0344cac8963723a8f001cb4f8dd8e5c8b 100644 GIT binary patch delta 1484 zcmah}&rcIU6wWLJrBNZ28V?AA!L&`nO|Um? zK~b`n2X`FH*Lg7Qvsp<)B386!09uEeiDk3Nhlz~Q2J)Gzvwd~@{R=MG`AHS%>p3_$ z9S+;M&N+2IY`Mty2zSlxmFleT)H|ybK0!?{xs~EP19W8+Go|Q!O{t@@r1|_^lkCvx z4EYzx+7WP*cL%^VNJETb-DJMXjS@8v#zz@umDnzJFdH1MLt2^H0yxuhT51>fPeoglhX*36!5*iWNcp*(GEM!7#?Y@ygk|EPZ4->6zWd4zugYa_K1+vWoY(anGKYc zZb_}WNlO5$ml+d)q9S7?V{#^d`XGbIvlDQgs3q{@g2hEekh)oE$n=C18TNGDZdd@a zVB^N${|bi4WC=i?L5}-@c9F=lkFQ8;fx*rI(LW^ti#2jo0^`STMUehYrapq9V9ycI z*GW_~#8(kc%4!4qyp(o42jno#-8SObL)v}3&WST0w;;V_EU-4&cdh#-o`6H(+5hBs K9nWRBfyKWosp~la delta 260 zcmZqKz_nu$SHl)Yq3G%E(TpC`&qXulPM;FPSU^<>}#z5*vJfk9KN@h`Na!Gzs<#fjcMny)=$q!{jw=Ya$ zoW{64J%y2lar)FWMlX)y)RL0Sy!2w2BiW|&XEG{GkB @@ -55,20 +81,35 @@ export function generate( \${env.${escapeXML(password)}} + + + + true + + + \${env.${escapeXML(gpgPassphrase)}} + + + `; } -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}`); +} diff --git a/src/setup-java.ts b/src/setup-java.ts index d0392175..6564f2cd 100644 --- a/src/setup-java.ts +++ b/src/setup-java.ts @@ -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); }