Skip to content

Commit

Permalink
Merge pull request #63 from jaredpetersen/master
Browse files Browse the repository at this point in the history
Added support for GPG signing
  • Loading branch information
konradpabjan authored Jul 22, 2020
2 parents d920b7d + 38e0c8b commit 1277492
Show file tree
Hide file tree
Showing 17 changed files with 37,572 additions and 137 deletions.
51 changes: 36 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,60 @@ 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.***

If `gpg-private-key` input is provided, the private key will be written to a file in the runner's temp directory, the private key file will be imported into the GPG keychain, and then the file will be promptly removed before proceeding with the rest of the setup process. A cleanup step will remove 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).

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 +193,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);
});

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

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'));
});
});
});
Loading

0 comments on commit 1277492

Please sign in to comment.