Merge pull request #128 from github/joshmgross/reusable-inputs-outputs
Support reusable workflow `inputs` and `outputs`
This commit is contained in:
@@ -20,6 +20,7 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
|||||||
let needs: StringToken[] | undefined = undefined;
|
let needs: StringToken[] | undefined = undefined;
|
||||||
let steps: Step[] = [];
|
let steps: Step[] = [];
|
||||||
let workflowJobRef: StringToken | undefined;
|
let workflowJobRef: StringToken | undefined;
|
||||||
|
let workflowJobInputs: MappingToken | undefined;
|
||||||
|
|
||||||
for (const item of token) {
|
for (const item of token) {
|
||||||
const propertyName = item.key.assertString("job property name");
|
const propertyName = item.key.assertString("job property name");
|
||||||
@@ -88,7 +89,11 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "uses":
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,6 +106,9 @@ export function convertJob(context: TemplateContext, jobKey: StringToken, token:
|
|||||||
needs: needs ?? [],
|
needs: needs ?? [],
|
||||||
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined, undefined),
|
||||||
ref: workflowJobRef,
|
ref: workflowJobRef,
|
||||||
|
"input-definitions": undefined,
|
||||||
|
"input-values": workflowJobInputs,
|
||||||
|
outputs: undefined,
|
||||||
concurrency,
|
concurrency,
|
||||||
strategy
|
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 {TokenType} from "../../templates/tokens/types";
|
||||||
import {ReusableWorkflowJob} from "../workflow-template";
|
import {ReusableWorkflowJob} from "../workflow-template";
|
||||||
import {handleTemplateTokenErrors} from "./handle-errors";
|
import {handleTemplateTokenErrors} from "./handle-errors";
|
||||||
|
import {convertWorkflowJobInputs} from "./job/inputs";
|
||||||
import {convertJobs} from "./jobs";
|
import {convertJobs} from "./jobs";
|
||||||
|
|
||||||
export function convertReferencedWorkflow(
|
export function convertReferencedWorkflow(
|
||||||
@@ -35,7 +36,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
|
|||||||
case TokenType.String: {
|
case TokenType.String: {
|
||||||
const event = on.assertString("Reference workflow on value").value;
|
const event = on.assertString("Reference workflow on value").value;
|
||||||
if (event === "workflow_call") {
|
if (event === "workflow_call") {
|
||||||
// TODO: Validate workflow job trigger
|
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -46,7 +47,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
|
|||||||
for (const eventToken of events) {
|
for (const eventToken of events) {
|
||||||
const event = eventToken.assertString(`Reference workflow on value ${eventToken}`).value;
|
const event = eventToken.assertString(`Reference workflow on value ${eventToken}`).value;
|
||||||
if (event === "workflow_call") {
|
if (event === "workflow_call") {
|
||||||
// TODO: Validate workflow job trigger
|
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,7 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pair.value.templateTokenType === TokenType.Null) {
|
if (pair.value.templateTokenType === TokenType.Null) {
|
||||||
// TODO: Validate workflow job trigger
|
handleTemplateTokenErrors(on, context, undefined, () => convertWorkflowJobInputs(context, job));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,10 +75,14 @@ function convertReferencedWorkflowOn(context: TemplateContext, on: TemplateToken
|
|||||||
case "inputs":
|
case "inputs":
|
||||||
job["input-definitions"] = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
|
job["input-definitions"] = definition.value.assertMapping(`on-workflow_call-${definition.key}`);
|
||||||
break;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export type BaseJob = {
|
|||||||
if: BasicExpressionToken;
|
if: BasicExpressionToken;
|
||||||
concurrency?: TemplateToken;
|
concurrency?: TemplateToken;
|
||||||
strategy?: TemplateToken;
|
strategy?: TemplateToken;
|
||||||
|
outputs?: MappingToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
// `job-factory` in the schema
|
// `job-factory` in the schema
|
||||||
@@ -50,7 +51,6 @@ export type Job = BaseJob & {
|
|||||||
"runs-on"?: TemplateToken;
|
"runs-on"?: TemplateToken;
|
||||||
container?: TemplateToken;
|
container?: TemplateToken;
|
||||||
services?: TemplateToken;
|
services?: TemplateToken;
|
||||||
outputs?: MappingToken;
|
|
||||||
steps: Step[];
|
steps: Step[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
-1
@@ -1,7 +1,6 @@
|
|||||||
include-source: false # Drop file/line/col from output
|
include-source: false # Drop file/line/col from output
|
||||||
skip:
|
skip:
|
||||||
- Go
|
- Go
|
||||||
- TypeScript
|
|
||||||
---
|
---
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
include-source: false # Drop file/line/col from output
|
include-source: false # Drop file/line/col from output
|
||||||
skip:
|
skip:
|
||||||
- Go
|
- Go
|
||||||
- TypeScript
|
|
||||||
---
|
---
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
-1
@@ -1,7 +1,6 @@
|
|||||||
include-source: false # Drop file/line/col from output
|
include-source: false # Drop file/line/col from output
|
||||||
skip:
|
skip:
|
||||||
- Go
|
- Go
|
||||||
- TypeScript
|
|
||||||
---
|
---
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
include-source: false # Drop file/line/col from output
|
include-source: false # Drop file/line/col from output
|
||||||
skip:
|
skip:
|
||||||
- Go
|
- Go
|
||||||
- TypeScript
|
|
||||||
---
|
---
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
include-source: false # Drop file/line/col from output
|
include-source: false # Drop file/line/col from output
|
||||||
skip:
|
skip:
|
||||||
- Go
|
- Go
|
||||||
- TypeScript
|
|
||||||
---
|
---
|
||||||
on: push
|
on: push
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
Reference in New Issue
Block a user