Skip to content

Commit

Permalink
Added support for GPG
Browse files Browse the repository at this point in the history
  • Loading branch information
Jared Petersen committed May 16, 2020
1 parent 5c87b70 commit d94db22
Show file tree
Hide file tree
Showing 17 changed files with 37,441 additions and 5,141 deletions.
49 changes: 34 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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```)


Expand Down Expand Up @@ -113,39 +113,58 @@ 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
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
- 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.

`settings.xml` file created for the first deploy to GitHub Packages
```xml
<servers>
<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">
<servers>
<server>
<id>github</id>
<username>${env.GITHUB_ACTOR}</username>
<password>${env.GITHUB_TOKEN}</password>
</server>
</servers>
<server>
<id>gpg.passphrase</id>
<passphrase>${env.GPG_PASSPHRASE}</passphrase>
</server>
</servers>
</settings>
```

`settings.xml` file created for the second deploy to Apache Maven Central
```xml
<servers>
<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">
<servers>
<server>
<id>maven</id>
<username>${env.MAVEN_USERNAME}</username>
<password>${env.MAVEN_CENTRAL_TOKEN}</password>
</server>
</servers>
<server>
<id>gpg.passphrase</id>
<passphrase>${env.MAVEN_GPG_PASSPHRASE}</passphrase>
</server>
</servers>
</settings>
```

***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.

Expand All @@ -172,7 +191,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.

Expand Down
106 changes: 56 additions & 50 deletions __tests__/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('auth tests', () => {
await io.rmRF(altHome);
}, 100000);

it('creates settings.xml with username and password', async () => {
it('creates settings.xml with minimal configuration', async () => {
const id = 'packages';
const username = 'UNAME';
const password = 'TOKEN';
Expand All @@ -67,78 +67,84 @@ describe('auth tests', () => {
);
}, 100000);

it('overwrites existing settings.xml files', async () => {
it('creates settings.xml with additional configuration', async () => {
const id = 'packages';
const username = 'USERNAME';
const password = 'PASSWORD';

fs.mkdirSync(m2Dir, {recursive: true});
fs.writeFileSync(settingsFile, 'FAKE FILE');
expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(settingsFile)).toBe(true);
const username = 'UNAME';
const password = 'TOKEN';
const gpgPassphrase = 'GPG';

await auth.configAuthentication(id, username, password);
await auth.configAuthentication(id, username, password, 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);

it('does not create settings.xml without required parameters', async () => {
await auth.configAuthentication('FOO');

expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(settingsFile)).toBe(true);
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
auth.generate('FOO', auth.DEFAULT_USERNAME, auth.DEFAULT_PASSWORD)
);

await auth.configAuthentication(undefined, 'BAR', undefined);

expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(settingsFile)).toBe(true);
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
auth.generate(auth.DEFAULT_ID, 'BAR', auth.DEFAULT_PASSWORD)
);

await auth.configAuthentication(undefined, undefined, 'BAZ');
it('overwrites existing settings.xml files', async () => {
const id = 'packages';
const username = 'USERNAME';
const password = 'PASSWORD';

fs.mkdirSync(m2Dir, {recursive: true});
fs.writeFileSync(settingsFile, 'FAKE FILE');
expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(settingsFile)).toBe(true);
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
auth.generate(auth.DEFAULT_ID, auth.DEFAULT_USERNAME, 'BAZ')
);

await auth.configAuthentication();
await auth.configAuthentication(id, username, password);

expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(settingsFile)).toBe(true);
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
auth.generate(
auth.DEFAULT_ID,
auth.DEFAULT_USERNAME,
auth.DEFAULT_PASSWORD
)
auth.generate(id, username, password)
);
}, 100000);

it('escapes invalid XML inputs', () => {
it('generates valid settings.xml with minimal configuration', () => {
const id = 'packages';
const username = 'USER';
const password = '&<>"\'\'"><&';

expect(auth.generate(id, username, password)).toEqual(`
<settings>
<servers>
<server>
<id>${id}</id>
<username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;&quot;&apos;&apos;&quot;&gt;&lt;&amp;}</password>
</server>
</servers>
</settings>
`);
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">
<servers>
<server>
<id>${id}</id>
<username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
</server>
</servers>
</settings>`;

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

it('generates valid settings.xml with additional configuration', () => {
const id = 'packages';
const username = 'USER';
const password = '&<>"\'\'"><&';
const gpgPassphrase = '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">
<servers>
<server>
<id>${id}</id>
<username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
</server>
<server>
<id>gpg.passphrase</id>
<passphrase>\${env.${gpgPassphrase}}</passphrase>
</server>
</servers>
</settings>`;

expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
expectedSettings
);
});
});
56 changes: 56 additions & 0 deletions __tests__/gpg.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import path = require('path');
import io = require('@actions/io');
import exec = require('@actions/exec');

jest.mock('@actions/exec', () => {
return {
exec: jest.fn()
};
});

const tempDir = path.join(__dirname, 'runner', 'temp');
process.env['RUNNER_TEMP'] = tempDir;

import gpg = require('../src/gpg');

describe('gpg tests', () => {
beforeEach(async () => {
await io.mkdirP(tempDir);
}, 300000);

afterAll(async () => {
try {
await io.rmRF(tempDir);
} catch {
console.log('Failed to remove test directories');
}
}, 100000);

describe('importKey', () => {
it('attempts to import private key and returns null key id on failure', async () => {
const privateKey = 'KEY CONTENTS';
const keyId = await gpg.importKey(privateKey);

expect(keyId).toBeNull();

expect(exec.exec).toHaveBeenCalledWith(
'gpg',
expect.anything(),
expect.anything()
);
});
});

describe('deleteKey', () => {
it('deletes private key', async () => {
const keyId = 'asdfhjkl';
await gpg.deleteKey(keyId);

expect(exec.exec).toHaveBeenCalledWith(
'gpg',
expect.anything(),
expect.anything()
);
});
});
});
61 changes: 61 additions & 0 deletions __tests__/util.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import path = require('path');

const env = process.env;

describe('util tests', () => {
beforeEach(() => {
const tempEnv = Object.assign({}, env);
delete tempEnv.RUNNER_TEMP;
delete tempEnv.USERPROFILE;
process.env = tempEnv;
Object.defineProperty(process, 'platform', {value: 'linux'});
});

describe('getTempDir', () => {
it('gets temp dir using env', () => {
process.env['RUNNER_TEMP'] = 'defaulttmp';
const util = require('../src/util');

const tempDir = util.getTempDir();

expect(tempDir).toEqual(process.env['RUNNER_TEMP']);
});

it('gets temp dir for windows using userprofile', () => {
Object.defineProperty(process, 'platform', {value: 'win32'});
process.env['USERPROFILE'] = 'winusertmp';
const util = require('../src/util');

const tempDir = util.getTempDir();

expect(tempDir).toEqual(
path.join(process.env['USERPROFILE'], 'actions', 'temp')
);
});

it('gets temp dir for windows using c drive', () => {
Object.defineProperty(process, 'platform', {value: 'win32'});
const util = require('../src/util');

const tempDir = util.getTempDir();

expect(tempDir).toEqual(path.join('C:\\', 'actions', 'temp'));
});

it('gets temp dir for mac', () => {
Object.defineProperty(process, 'platform', {value: 'darwin'});
const util = require('../src/util');

const tempDir = util.getTempDir();

expect(tempDir).toEqual(path.join('/Users', 'actions', 'temp'));
});

it('gets temp dir for linux', () => {
const util = require('../src/util');
const tempDir = util.getTempDir();

expect(tempDir).toEqual(path.join('/home', 'actions', 'temp'));
});
});
});
10 changes: 9 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 'GPG private key to import. Default is empty string.'
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'
main: 'dist/setup/index.js'
post: 'dist/cleanup/index.js'
Loading

0 comments on commit d94db22

Please sign in to comment.