diff --git a/actions-workflow-parser/src/model/converter/job.ts b/actions-workflow-parser/src/model/converter/job.ts index a7f1776..ee5f30e 100644 --- a/actions-workflow-parser/src/model/converter/job.ts +++ b/actions-workflow-parser/src/model/converter/job.ts @@ -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 }; diff --git a/actions-workflow-parser/src/model/converter/job/inputs.ts b/actions-workflow-parser/src/model/converter/job/inputs.ts new file mode 100644 index 0000000..6b5d366 --- /dev/null +++ b/actions-workflow-parser/src/model/converter/job/inputs.ts @@ -0,0 +1,54 @@ +import {TemplateContext} from "../../../templates/template-context"; +import {MappingToken, TemplateToken} from "../../../templates/tokens"; +import {ReusableWorkflowJob} from "../../workflow-template"; + +type TokenMap = Map; + +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(); + for (const item of mapping) { + const name = item.key.assertString(`${description} key`); + result.set(name.value.toLowerCase(), [name.value, item.value]); + } + + return result; +} diff --git a/actions-workflow-parser/src/model/converter/referencedWorkflow.ts b/actions-workflow-parser/src/model/converter/referencedWorkflow.ts index 0ac6f91..b40f739 100644 --- a/actions-workflow-parser/src/model/converter/referencedWorkflow.ts +++ b/actions-workflow-parser/src/model/converter/referencedWorkflow.ts @@ -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; diff --git a/actions-workflow-parser/src/model/workflow-template.ts b/actions-workflow-parser/src/model/workflow-template.ts index 094e209..660fdea 100644 --- a/actions-workflow-parser/src/model/workflow-template.ts +++ b/actions-workflow-parser/src/model/workflow-template.ts @@ -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[]; }; diff --git a/actions-workflow-parser/testdata/reader/errors-reusable-workflow-job-inputs-required.yml b/actions-workflow-parser/testdata/reader/errors-reusable-workflow-job-inputs-required.yml index df6d826..6f1d9fb 100644 --- a/actions-workflow-parser/testdata/reader/errors-reusable-workflow-job-inputs-required.yml +++ b/actions-workflow-parser/testdata/reader/errors-reusable-workflow-job-inputs-required.yml @@ -1,7 +1,6 @@ include-source: false # Drop file/line/col from output skip: - Go - - TypeScript --- on: push jobs: diff --git a/actions-workflow-parser/testdata/reader/reusable-workflow-inputs.yml b/actions-workflow-parser/testdata/reader/reusable-workflow-inputs.yml index cd82fa4..3604796 100644 --- a/actions-workflow-parser/testdata/reader/reusable-workflow-inputs.yml +++ b/actions-workflow-parser/testdata/reader/reusable-workflow-inputs.yml @@ -1,7 +1,6 @@ include-source: false # Drop file/line/col from output skip: - Go - - TypeScript --- on: push jobs: diff --git a/actions-workflow-parser/testdata/reader/reusable-workflow-job-inputs-type-coercion.yml b/actions-workflow-parser/testdata/reader/reusable-workflow-job-inputs-type-coercion.yml index dbe138c..0afcc09 100644 --- a/actions-workflow-parser/testdata/reader/reusable-workflow-job-inputs-type-coercion.yml +++ b/actions-workflow-parser/testdata/reader/reusable-workflow-job-inputs-type-coercion.yml @@ -1,7 +1,6 @@ include-source: false # Drop file/line/col from output skip: - Go - - TypeScript --- on: push jobs: diff --git a/actions-workflow-parser/testdata/reader/reusable-workflow-job-strategy.yml b/actions-workflow-parser/testdata/reader/reusable-workflow-job-strategy.yml index cd35281..c343684 100644 --- a/actions-workflow-parser/testdata/reader/reusable-workflow-job-strategy.yml +++ b/actions-workflow-parser/testdata/reader/reusable-workflow-job-strategy.yml @@ -1,7 +1,6 @@ include-source: false # Drop file/line/col from output skip: - Go - - TypeScript --- on: push jobs: diff --git a/actions-workflow-parser/testdata/reader/reusable-workflow-outputs.yml b/actions-workflow-parser/testdata/reader/reusable-workflow-outputs.yml index c567cac..62501c2 100644 --- a/actions-workflow-parser/testdata/reader/reusable-workflow-outputs.yml +++ b/actions-workflow-parser/testdata/reader/reusable-workflow-outputs.yml @@ -1,7 +1,6 @@ include-source: false # Drop file/line/col from output skip: - Go - - TypeScript --- on: push jobs: