Container based developer flow (#76)

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2021-01-29 12:29:31 +01:00 committed by GitHub
parent c9085d8a12
commit e33a6489fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 205 additions and 94 deletions

6
.dockerignore Normal file
View file

@ -0,0 +1,6 @@
/.dev
/coverage
/dist
/lib
/node_modules
/.env

View file

@ -7,13 +7,14 @@ Contributions to this project are [released](https://help.github.com/articles/gi
## Submitting a pull request
1. [Fork](https://github.com/crazy-max/ghaction-import-gpg/fork) and clone the repository
2. Configure and install the dependencies: `yarn install`
3. Make sure the tests pass on your machine: `yarn run test`
4. Create a new branch: `git checkout -b my-branch-name`
5. Make your change, add tests, and make sure the tests still pass
6. Run pre-checkin: `yarn run pre-checkin`
7. Push to your fork and [submit a pull request](https://github.com/crazy-max/ghaction-import-gpg/compare)
8. Pat your self on the back and wait for your pull request to be reviewed and merged.
2. Configure and install the dependencies locally: `yarn install`
3. Create a new branch: `git checkout -b my-branch-name`
4. Make your changes
5. Make sure the tests pass: `docker buildx bake test`
6. Format code and build javascript artifacts: `docker buildx bake pre-checkin`
7. Validate all code has correctly formatted and built: `docker buildx bake validate`
8. Push to your fork and [submit a pull request](https://github.com/crazy-max/ghaction-import-gpg/compare)
9. Pat your self on the back and wait for your pull request to be reviewed and merged.
Here are a few things you can do that will increase the likelihood of your pull request being accepted:

View file

@ -3,14 +3,13 @@ name: ci
on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
pull_request:
branches:
- master
- releases/v*
push:
branches:
- master
- releases/v*
- 'master'
- 'releases/v*'
pull_request:
branches:
- 'master'
jobs:
armored:

View file

@ -1,34 +0,0 @@
name: pre-checkin
on:
push:
branches:
- 'master'
paths-ignore:
- '**.md'
pull_request:
branches:
- 'master'
paths-ignore:
- '**.md'
jobs:
pre-checkin:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Install
run: yarn install
-
name: Pre-checkin
run: yarn run pre-checkin
-
name: Check for uncommitted changes
run: |
if [[ `git status --porcelain` ]]; then
git status --porcelain
echo "::warning::Found changes. Please run 'yarn run pre-checkin' and push"
fi

View file

@ -12,6 +12,16 @@ on:
- '**.md'
jobs:
test-containerized:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Test
run: docker buildx bake test
test:
runs-on: ${{ matrix.os }}
strategy:
@ -34,7 +44,6 @@ jobs:
-
name: Upload coverage
uses: codecov/codecov-action@v1
if: success()
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/clover.xml

25
.github/workflows/validate.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: validate
on:
push:
branches:
- 'master'
- 'releases/v*'
paths-ignore:
- '**.md'
pull_request:
branches:
- 'master'
paths-ignore:
- '**.md'
jobs:
validate:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Validate
run: docker buildx bake validate

2
.gitignore vendored
View file

@ -35,7 +35,7 @@ pids
lib-cov
# Coverage directory used by tools like istanbul
coverage
/coverage
*.lcov
# nyc test coverage

53
Dockerfile.dev Normal file
View file

@ -0,0 +1,53 @@
#syntax=docker/dockerfile:1.2
FROM node:12 AS deps
WORKDIR /src
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/src/node_modules \
yarn install
FROM scratch AS update-yarn
COPY --from=deps /src/yarn.lock /
FROM deps AS validate-yarn
COPY .git .git
RUN status=$(git status --porcelain -- yarn.lock); if [ -n "$status" ]; then echo $status; exit 1; fi
FROM deps AS base
COPY . .
FROM base AS build
RUN --mount=type=cache,target=/src/node_modules \
yarn build
FROM deps AS test
ARG GITHUB_REPOSITORY
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
ENV GITHUB_REPOSITORY=${GITHUB_REPOSITORY}
COPY . .
RUN --mount=type=cache,target=/src/node_modules \
yarn run test
FROM scratch AS test-coverage
COPY --from=test /src/coverage /coverage/
FROM base AS run-format
RUN --mount=type=cache,target=/src/node_modules \
yarn run format
FROM scratch AS format
COPY --from=run-format /src/src/*.ts /src/
FROM base AS validate-format
RUN --mount=type=cache,target=/src/node_modules \
yarn run format-check
FROM scratch AS dist
COPY --from=build /src/dist/ /dist/
FROM build AS validate-build
RUN status=$(git status --porcelain -- dist); if [ -n "$status" ]; then echo $status; exit 1; fi
FROM base AS dev
ENTRYPOINT ["bash"]

80
dist/index.js generated vendored
View file

@ -94,7 +94,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.exec = void 0;
const actionsExec = __importStar(__webpack_require__(514));
const exec = (command, args = [], silent) => __awaiter(void 0, void 0, void 0, function* () {
exports.exec = (command, args = [], silent) => __awaiter(void 0, void 0, void 0, function* () {
let stdout = '';
let stderr = '';
const options = {
@ -116,7 +116,6 @@ const exec = (command, args = [], silent) => __awaiter(void 0, void 0, void 0, f
stderr: stderr.trim()
};
});
exports.exec = exec;
//# sourceMappingURL=exec.js.map
/***/ }),
@ -241,7 +240,7 @@ const gpgConnectAgent = (command) => __awaiter(void 0, void 0, void 0, function*
return res.stdout.trim();
});
});
const getVersion = () => __awaiter(void 0, void 0, void 0, function* () {
exports.getVersion = () => __awaiter(void 0, void 0, void 0, function* () {
return yield exec.exec('gpg', ['--version'], true).then(res => {
if (res.stderr != '') {
throw new Error(res.stderr);
@ -265,8 +264,7 @@ const getVersion = () => __awaiter(void 0, void 0, void 0, function* () {
};
});
});
exports.getVersion = getVersion;
const getDirs = () => __awaiter(void 0, void 0, void 0, function* () {
exports.getDirs = () => __awaiter(void 0, void 0, void 0, function* () {
return yield exec.exec('gpgconf', ['--list-dirs'], true).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
@ -297,8 +295,7 @@ const getDirs = () => __awaiter(void 0, void 0, void 0, function* () {
};
});
});
exports.getDirs = getDirs;
const importKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
exports.importKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
const keyFolder = fs.mkdtempSync(path.join(os.tmpdir(), 'ghaction-import-gpg-'));
const keyPath = `${keyFolder}/key.pgp`;
fs.writeFileSync(keyPath, (yield openpgp.isArmored(key)) ? key : Buffer.from(key, 'base64').toString(), { mode: 0o600 });
@ -317,8 +314,7 @@ const importKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
fs.unlinkSync(keyPath);
});
});
exports.importKey = importKey;
const getKeygrips = (fingerprint) => __awaiter(void 0, void 0, void 0, function* () {
exports.getKeygrips = (fingerprint) => __awaiter(void 0, void 0, void 0, function* () {
return yield exec.exec('gpg', ['--batch', '--with-colons', '--with-keygrip', '--list-secret-keys', fingerprint], true).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
@ -332,8 +328,7 @@ const getKeygrips = (fingerprint) => __awaiter(void 0, void 0, void 0, function*
return keygrips;
});
});
exports.getKeygrips = getKeygrips;
const configureAgent = (config) => __awaiter(void 0, void 0, void 0, function* () {
exports.configureAgent = (config) => __awaiter(void 0, void 0, void 0, function* () {
const gpgAgentConf = path.join(yield getGnupgHome(), 'gpg-agent.conf');
yield fs.writeFile(gpgAgentConf, config, function (err) {
if (err)
@ -341,14 +336,12 @@ const configureAgent = (config) => __awaiter(void 0, void 0, void 0, function* (
});
yield gpgConnectAgent('RELOADAGENT');
});
exports.configureAgent = configureAgent;
const presetPassphrase = (keygrip, passphrase) => __awaiter(void 0, void 0, void 0, function* () {
exports.presetPassphrase = (keygrip, passphrase) => __awaiter(void 0, void 0, void 0, function* () {
const hexPassphrase = Buffer.from(passphrase, 'utf8').toString('hex').toUpperCase();
yield gpgConnectAgent(`PRESET_PASSPHRASE ${keygrip} -1 ${hexPassphrase}`);
return yield gpgConnectAgent(`KEYINFO ${keygrip}`);
});
exports.presetPassphrase = presetPassphrase;
const deleteKey = (fingerprint) => __awaiter(void 0, void 0, void 0, function* () {
exports.deleteKey = (fingerprint) => __awaiter(void 0, void 0, void 0, function* () {
yield exec.exec('gpg', ['--batch', '--yes', '--delete-secret-keys', fingerprint], true).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
@ -360,11 +353,9 @@ const deleteKey = (fingerprint) => __awaiter(void 0, void 0, void 0, function* (
}
});
});
exports.deleteKey = deleteKey;
const killAgent = () => __awaiter(void 0, void 0, void 0, function* () {
exports.killAgent = () => __awaiter(void 0, void 0, void 0, function* () {
yield gpgConnectAgent('KILLAGENT');
});
exports.killAgent = killAgent;
//# sourceMappingURL=gpg.js.map
/***/ }),
@ -552,7 +543,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isArmored = exports.generateKeyPair = exports.readPrivateKey = void 0;
const openpgp = __importStar(__webpack_require__(144));
const addressparser_1 = __importDefault(__webpack_require__(764));
const readPrivateKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
exports.readPrivateKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
const { keys: [privateKey], err: err } = yield openpgp.key.readArmored((yield exports.isArmored(key)) ? key : Buffer.from(key, 'base64').toString());
if (err === null || err === void 0 ? void 0 : err.length) {
throw err[0];
@ -571,8 +562,7 @@ const readPrivateKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
creationTime: privateKey.getCreationTime()
};
});
exports.readPrivateKey = readPrivateKey;
const generateKeyPair = (name, email, passphrase, numBits = 4096) => __awaiter(void 0, void 0, void 0, function* () {
exports.generateKeyPair = (name, email, passphrase, numBits = 4096) => __awaiter(void 0, void 0, void 0, function* () {
const keyPair = yield openpgp.generateKey({
userIds: [{ name: name, email: email }],
numBits,
@ -583,11 +573,9 @@ const generateKeyPair = (name, email, passphrase, numBits = 4096) => __awaiter(v
privateKey: keyPair.privateKeyArmored.replace(/\r\n/g, '\n').trim()
};
});
exports.generateKeyPair = generateKeyPair;
const isArmored = (text) => __awaiter(void 0, void 0, void 0, function* () {
exports.isArmored = (text) => __awaiter(void 0, void 0, void 0, function* () {
return text.trimLeft().startsWith('---');
});
exports.isArmored = isArmored;
//# sourceMappingURL=openpgp.js.map
/***/ }),
@ -2484,7 +2472,7 @@ Tokenizer.prototype.checkChar = function (chr) {
/***/ 144:
/***/ ((module) => {
/*! OpenPGP.js v4.10.8 - 2020-08-28 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
/*! OpenPGP.js v4.10.10 - 2021-01-24 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
(function(f){if(true){module.exports=f()}else { var g; }})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u=require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
(function (global){
"use strict";
@ -26018,7 +26006,7 @@ function modL(r, x) {
carry = 0;
for (j = i - 32, k = i - 12; j < k; ++j) {
x[j] += carry - 16 * x[i] * L[j - (i - 32)];
carry = (x[j] + 128) >> 8;
carry = Math.floor((x[j] + 128) / 256);
x[j] -= carry * 256;
}
x[j] += carry;
@ -26119,12 +26107,11 @@ function unpackneg(r, p) {
}
function crypto_sign_open(m, sm, n, pk) {
var i, mlen;
var i;
var t = new Uint8Array(32), h;
var p = [gf(), gf(), gf(), gf()],
q = [gf(), gf(), gf(), gf()];
mlen = -1;
if (n < 64) return -1;
if (unpackneg(q, pk)) return -1;
@ -26146,8 +26133,7 @@ function crypto_sign_open(m, sm, n, pk) {
}
for (i = 0; i < n; i++) m[i] = sm[i + 64];
mlen = n;
return mlen;
return n;
}
var crypto_scalarmult_BYTES = 32,
@ -27546,7 +27532,7 @@ exports.default = {
* @memberof module:config
* @property {String} versionstring A version string to be included in armored messages
*/
versionstring: "OpenPGP.js v4.10.8",
versionstring: "OpenPGP.js v4.10.10",
/**
* @memberof module:config
* @property {String} commentstring A comment string to be included in armored messages
@ -29714,8 +29700,9 @@ exports.default = {
const c2 = data_params[1].toBN();
const p = key_params[0].toBN();
const x = key_params[3].toBN();
const result = new _mpi2.default((await _public_key2.default.elgamal.decrypt(c1, c2, p, x)));
return _pkcs2.default.eme.decode(result.toString());
const result = new _mpi2.default((await _public_key2.default.elgamal.decrypt(c1, c2, p, x))); // MPI and BN.js discard any leading zeros
return _pkcs2.default.eme.decode(_util2.default.Uint8Array_to_str(result.toUint8Array('be', p.byteLength())) // re-introduce leading zeros
);
}
case _enums2.default.publicKey.ecdh:
{
@ -31239,10 +31226,6 @@ eme.encode = async function (M, k) {
* @returns {String} message, an octet string
*/
eme.decode = function (EM) {
// leading zeros truncated by bn.js
if (EM.charCodeAt(0) !== 0) {
EM = String.fromCharCode(0) + EM;
}
const firstOct = EM.charCodeAt(0);
const secondOct = EM.charCodeAt(1);
let i = 2;
@ -31609,8 +31592,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
* @module crypto/public_key/elgamal
*/
const zero = new _bn2.default(0);
exports.default = {
/**
* ElGamal Encryption function
@ -31626,8 +31607,9 @@ exports.default = {
const mred = m.toRed(redp);
const gred = g.toRed(redp);
const yred = y.toRed(redp);
// See Section 11.5 here: https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf
const k = await _random2.default.getRandomBN(zero, p); // returns in [0, p-1]
// OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ*
// hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2]
const k = await _random2.default.getRandomBN(new _bn2.default(1), p.subn(1));
return {
c1: gred.redPow(k).fromRed(),
c2: yred.redPow(k).redMul(mred).fromRed()
@ -34031,7 +34013,11 @@ exports.default = {
});
key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
}
return _util2.default.Uint8Array_to_str(nodeCrypto.privateDecrypt(key, data));
try {
return _util2.default.Uint8Array_to_str(nodeCrypto.privateDecrypt(key, data));
} catch (err) {
throw new Error('Decryption error');
}
},
bnDecrypt: async function bnDecrypt(data, n, e, d, p, q, u) {
@ -34070,7 +34056,8 @@ exports.default = {
result = result.redMul(unblinder);
}
return _pkcs2.default.eme.decode(new _mpi2.default(result).toString());
result = new _mpi2.default(result).toUint8Array('be', n.byteLength()); // preserve leading zeros
return _pkcs2.default.eme.decode(_util2.default.Uint8Array_to_str(result));
},
prime: _prime2.default
@ -35541,7 +35528,7 @@ function HKP(keyServerBaseUrl) {
/**
* Search for a public key on the key server either by key ID or part of the user ID.
* @param {String} options.keyID The long public key ID.
* @param {String} options.keyId The long public key ID.
* @param {String} options.query This can be any part of the key user ID such as name
* or email address.
* @returns {Promise<String>} The ascii armored public key.
@ -42690,11 +42677,14 @@ SecretKey.prototype.makeDummy = function () {
throw new Error("Key is not decrypted");
}
this.clearPrivateParams();
this.keyMaterial = null;
this.isEncrypted = false;
this.s2k = new _s2k2.default();
this.s2k.algorithm = 0;
this.s2k.c = 0;
this.s2k.type = 'gnu-dummy';
this.s2k_usage = 254;
this.symmetric = 'aes256';
};
/**
@ -46609,7 +46599,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
* @constructor
*/
function WKD() {
this._fetch = typeof global !== 'undefined' ? global.fetch : require('node-fetch');
this._fetch = typeof global.fetch === 'function' ? global.fetch : require('node-fetch');
}
/**

61
docker-bake.hcl Normal file
View file

@ -0,0 +1,61 @@
variable "GITHUB_REPOSITORY" {
default = "crazy-max/ghaction-import-gpg"
}
group "default" {
targets = ["build"]
}
group "pre-checkin" {
targets = ["update-yarn", "format", "build"]
}
group "validate" {
targets = ["validate-format", "validate-build", "validate-yarn"]
}
target "dockerfile" {
dockerfile = "Dockerfile.dev"
}
target "update-yarn" {
inherits = ["dockerfile"]
target = "update-yarn"
output = ["."]
}
target "build" {
inherits = ["dockerfile"]
target = "dist"
output = ["."]
}
target "test" {
args = {
GITHUB_REPOSITORY = "${GITHUB_REPOSITORY}"
}
inherits = ["dockerfile"]
target = "test-coverage"
output = ["."]
}
target "format" {
inherits = ["dockerfile"]
target = "format"
output = ["."]
}
target "validate-format" {
inherits = ["dockerfile"]
target = "validate-format"
}
target "validate-build" {
inherits = ["dockerfile"]
target = "validate-build"
}
target "validate-yarn" {
inherits = ["dockerfile"]
target = "validate-yarn"
}

View file

@ -1,5 +1,6 @@
module.exports = {
clearMocks: true,
coverageDirectory: 'coverage',
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],