Skip to content

Commit

Permalink
Parse and validate the apps input
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzatron committed Aug 27, 2024
1 parent f3196e1 commit 9762e04
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 17 deletions.
8 changes: 8 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
name: Provision GitHub Tokens
description: Declare GitHub tokens in your repos, and rotate them automatically
author: Ghalactic
inputs:
apps:
description: >-
Apps to use for provisioning tokens, encoded as a YAML (or JSON) array.
The schema at
https://ghalactic.github.io/provision-github-tokens/schema/apps.v1.schema.json
can be used to validate the input.
required: true
branding:
icon: key
color: blue
Expand Down
13 changes: 6 additions & 7 deletions dist/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions dist/main.js.map

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"ajv": "^8.17.1",
"js-yaml": "^4.1.0",
"regexp.escape": "^2.0.1"
},
"devDependencies": {
"@octokit/types": "^13.5.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.5.0",
"@vitest/coverage-v8": "^2.0.5",
"esbuild": "^0.23.1",
"prettier": "^3.3.3",
Expand Down
26 changes: 26 additions & 0 deletions src/config/apps-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getInput } from "@actions/core";
import { load } from "js-yaml";
import { errorMessage } from "../error.js";
import type { AppInput } from "../type/input.js";
import { validateApps } from "./validation.js";

export function readAppsInput(): AppInput[] {
const yaml = getInput("apps");
let parsed;

try {
parsed = load(yaml);
} catch (error) {
throw new Error(
`Parsing of apps action input failed: ${errorMessage(error)}`,
);
}

try {
return validateApps(parsed);
} catch (error) {
throw new Error(
`Validation of apps action input failed: ${errorMessage(error)}`,
);
}
}
66 changes: 66 additions & 0 deletions src/config/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import ajvModule, { ErrorObject } from "ajv";
import appsSchema from "../schema/apps.v1.schema.json";
import type { AppInput } from "../type/input.js";

// see https://github.com/ajv-validator/ajv/issues/2132
const Ajv = ajvModule.default;

const ajv = new Ajv({
schemas: [appsSchema],
allErrors: true,
useDefaults: true,
});

export const validateApps = createValidate<AppInput[]>(
appsSchema.$id,
"apps input",
);

class ValidateError extends Error {
public errors: ErrorObject[];

constructor(message: string, errors: ErrorObject[]) {
super(message);

this.errors = errors;
}
}

function createValidate<T>(
schemaId: string,
label: string,
): (value: unknown) => T {
return function validate(value) {
const validator = ajv.getSchema(schemaId);

/* v8 ignore start */
if (!validator) {
throw new Error(`Invariant violation: Undefined schema ${schemaId}`);
}
/* v8 ignore stop */

if (validator(value)) return value as T;

/* v8 ignore start - never seen errors be nullish */
const errors = validator.errors ?? [];
/* v8 ignore stop */

const error = new ValidateError(
`Invalid ${label}:\n${renderErrors(errors)}`,
errors,
);

throw error;
};
}

function renderErrors(errors: ErrorObject[]): string {
return ` - ${errors.map(renderError).join("\n - ")}\n`;
}

function renderError(error: ErrorObject): string {
const { instancePath, message } = error;
const subject = instancePath && ` (${instancePath})`;

return `${message}${subject}`;
}
11 changes: 11 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function errorMessage(error: unknown): string {
/* v8 ignore start - never seen non-error */
return error instanceof Error ? error.message : "unknown cause";
/* v8 ignore stop */
}

export function errorStack(error: unknown): string {
/* v8 ignore start - never seen non-error */
return (error instanceof Error ? error.stack : undefined) ?? "unknown cause";
/* v8 ignore stop */
}
3 changes: 0 additions & 3 deletions src/guard.ts

This file was deleted.

5 changes: 2 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { info, setFailed } from "@actions/core";
import { isError } from "./guard.js";
import { errorStack } from "./error.js";

main().catch((error) => {
const stack = isError(error) ? error.stack : undefined;
setFailed(stack ?? "unknown cause");
setFailed(errorStack(error) ?? "unknown cause");
});

async function main(): Promise<void> {
Expand Down
35 changes: 35 additions & 0 deletions src/schema/apps.v1.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://ghalactic.github.io/provision-github-tokens/schema/apps.v1.schema.json",
"title": "Provision GitHub Tokens (apps)",
"description": "Apps to use for provisioning tokens.",
"type": "array",
"items": {
"description": "An app to use for provisioning tokens.",
"type": "object",
"additionalProperties": false,
"required": ["appId", "privateKey"],
"properties": {
"appId": {
"description": "The GitHub app ID.",
"type": "integer"
},
"privateKey": {
"description": "The GitHub app private key in PEM format.",
"type": "string",
"minLength": 1
},
"roles": {
"description": "The roles of the app.",
"type": "array",
"uniqueItems": true,
"default": [],
"items": {
"description": "An app role.",
"type": "string",
"minLength": 1
}
}
}
}
}
5 changes: 5 additions & 0 deletions src/type/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type AppInput = {
appId: number;
privateKey: string;
roles: string[];
};
Loading

0 comments on commit 9762e04

Please sign in to comment.