Merge pull request #128 from github/joshmgross/reusable-inputs-outputs

Support reusable workflow `inputs` and `outputs`
This commit is contained in:
Josh Gross
2023-02-07 10:39:59 -05:00
committed by GitHub
9 changed files with 73 additions and 11 deletions
@@ -20,6 +20,7 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
let needs: StringToken[] | undefined = undefined;
let steps: Step[] = [];
let workflowJobRef: StringToken | undefined;
let workflowJobInputs: MappingToken | undefined;
for (const item of token) {
const propertyName = item.key.assertString("job property name");
@@ -88,7 +89,11 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
break;
case "uses":
workflowJobRef = item.value.assertString("job item uses");
workflowJobRef = item.value.assertString("job uses value");
break;
case "with":
workflowJobInputs = item.value.assertMapping("uses-with value");
break;
}
}
@@ -101,6 +106,9 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
needs: needs ?? [],
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
ref: workflowJobRef,
"input-definitions": undefined,
"input-values": workflowJobInputs,
outputs: undefined,
concurrency,
strategy
};
@@ -0,0 +1,54 @@
import {TemplateContext} from "../../../templates/template-context";
import {MappingToken, TemplateToken} from "../../../templates/tokens";
import {ReusableWorkflowJob} from "../../workflow-template";
type TokenMap = Map<string, [key: string, value: TemplateToken]>;
export function convertWorkflowJobInputs(context: TemplateContext, job: ReusableWorkflowJob) {
const inputDefinitions = createTokenMap(
job["input-definitions"]?.assertMapping("workflow job input definitions"),
"inputs"
);
const inputValues = createTokenMap(job["input-values"]?.assertMapping("workflow job input values"), "with");
if (inputDefinitions !== undefined) {
for (const [_, [name, value]] of inputDefinitions) {
const inputSpec = createTokenMap(value.assertMapping(`input ${name}`), `input ${name} key`)!;
const inputTypeToken = inputSpec.get("type")?.[1];
if (!inputTypeToken) {
// This should be validated by the template reader per the schema
continue;
}
const inputSet = inputValues !== undefined && inputValues.has(name.toLowerCase());
const required = inputSpec.get("required")?.[1].assertBoolean(`input ${name} required`).value;
if (required && !inputSet) {
context.error(job.ref, `Input ${name} is required, but not provided while calling.`);
}
}
}
if (inputValues !== undefined) {
for (const [_, [name, value]] of inputValues) {
if (inputDefinitions !== undefined && !inputDefinitions.has(name.toLowerCase())) {
context.error(value, `Invalid input, ${name} is not defined in the referenced workflow.`);
}
}
}
}
function createTokenMap(mapping: MappingToken | undefined, description: string): TokenMap | undefined {
if (!mapping) {
return undefined;
}
const result = new Map<string, [key: string, value: TemplateToken]>();
for (const item of mapping) {
const name = item.key.assertString(`${description} key`);
result.set(name.value.toLowerCase(), [name.value, item.value]);
}
return result;
}
@@ -3,6 +3,7 @@ import {TemplateToken} from "../../templates/tokens";
import {TokenType} from "../../templates/tokens/types";
import {ReusableWorkflowJob} from "../workflow-template";
import {handleTemplateTokenErrors} from "./handle-errors";
import {convertWorkflowJobInputs} from "./job/inputs";
import {convertJobs} from "./jobs";
export function convertReferencedWorkflow(
@@ -35,7 +36,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
case TokenType.String: {
const event = on.assertString("Reference workflow on value").value;
if (event === "workflow_call") {
// TODO: Validate workflow job trigger
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
break;
@@ -46,7 +47,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
for (const eventToken of events) {
const event = eventToken.assertString(`Reference workflow on value ${eventToken}`).value;
if (event === "workflow_call") {
// TODO: Validate workflow job trigger
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
}
@@ -63,7 +64,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
}
if (pair.value.templateTokenType === TokenType.Null) {
// TODO: Validate workflow job trigger
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
@@ -74,10 +75,14 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
case "inputs":
job["input-definitions"] = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
break;
case "outputs":
job.outputs = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
break;
}
}
// TODO: Validate workflow job trigger
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
return;
}
break;
@@ -40,6 +40,7 @@ export type BaseJob = {
if: BasicExpressionToken;
concurrency?: TemplateToken;
strategy?: TemplateToken;
outputs?: MappingToken;
};
// `job-factory` in the schema
@@ -50,7 +51,6 @@ export type Job = BaseJob & {
"runs-on"?: TemplateToken;
container?: TemplateToken;
services?: TemplateToken;
outputs?: MappingToken;
steps: Step[];
};
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs:
@@ -1,7 +1,6 @@
include-source: false # Drop file/line/col from output
skip:
- Go
- TypeScript
---
on: push
jobs: