Convert secrets, complete secrets in called workflow
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {DescriptionDictionary} from "@github/actions-expressions";
|
||||
import {ContextProviderConfig} from "@github/actions-languageservice";
|
||||
import {Mode} from "@github/actions-languageservice/context-providers/default";
|
||||
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
|
||||
import {Octokit} from "@octokit/rest";
|
||||
import {getSecrets} from "./context-providers/secrets";
|
||||
@@ -20,11 +21,12 @@ export function contextProviders(
|
||||
const getContext = async (
|
||||
name: string,
|
||||
defaultContext: DescriptionDictionary | undefined,
|
||||
workflowContext: WorkflowContext
|
||||
workflowContext: WorkflowContext,
|
||||
mode: Mode
|
||||
) => {
|
||||
switch (name) {
|
||||
case "secrets":
|
||||
return await getSecrets(workflowContext, client, cache, repo, defaultContext);
|
||||
return await getSecrets(workflowContext, client, cache, repo, defaultContext, mode);
|
||||
case "vars":
|
||||
return await getVariables(workflowContext, client, cache, repo, defaultContext);
|
||||
case "steps":
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {data, DescriptionDictionary} from "@github/actions-expressions";
|
||||
import {StringData} from "@github/actions-expressions/data/string";
|
||||
import {Mode} from "@github/actions-languageservice/context-providers/default";
|
||||
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
|
||||
import {isMapping, isString} from "@github/actions-workflow-parser";
|
||||
import {Octokit} from "@octokit/rest";
|
||||
@@ -11,7 +12,8 @@ export async function getSecrets(
|
||||
octokit: Octokit,
|
||||
cache: TTLCache,
|
||||
repo: RepositoryContext,
|
||||
defaultContext: DescriptionDictionary | undefined
|
||||
defaultContext: DescriptionDictionary | undefined,
|
||||
mode: Mode
|
||||
): Promise<DescriptionDictionary> {
|
||||
let environmentName: string | undefined;
|
||||
if (workflowContext?.job?.environment) {
|
||||
@@ -30,6 +32,15 @@ export async function getSecrets(
|
||||
}
|
||||
|
||||
const secretsContext = defaultContext || new DescriptionDictionary();
|
||||
|
||||
// Exit early if workflow_call is the only trigger
|
||||
if (mode === Mode.Completion) {
|
||||
const eventsConfig = workflowContext?.template?.events;
|
||||
if (eventsConfig?.workflow_call && Object.keys(eventsConfig).length == 1) {
|
||||
return secretsContext;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const secrets = await getRemoteSecrets(octokit, cache, repo, environmentName);
|
||||
|
||||
|
||||
@@ -535,6 +535,28 @@ jobs:
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("secrets", async () => {
|
||||
const input = `
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
secret1:
|
||||
required: true
|
||||
description: "first secret"
|
||||
secret2:
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/deploy@v100
|
||||
with:
|
||||
deploy-key: \${{ secrets.|
|
||||
`;
|
||||
|
||||
const result = await complete(...getPositionFromCursor(input), {contextProviderConfig});
|
||||
expect(result.map(x => x.label)).toEqual(["GITHUB_TOKEN", "secret1", "secret2"]);
|
||||
});
|
||||
|
||||
describe("github context", () => {
|
||||
it("includes expected keys", async () => {
|
||||
const input = `
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {DescriptionDictionary} from "@github/actions-expressions";
|
||||
import {WorkflowContext} from "../context/workflow-context";
|
||||
import {Mode} from "./default";
|
||||
|
||||
export type ContextProviderConfig = {
|
||||
getContext: (
|
||||
name: string,
|
||||
defaultContext: DescriptionDictionary | undefined,
|
||||
workflowContext: WorkflowContext
|
||||
workflowContext: WorkflowContext,
|
||||
mode: Mode
|
||||
) => Promise<DescriptionDictionary | undefined>;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import {getJobContext} from "./job";
|
||||
import {getJobsContext} from "./jobs";
|
||||
import {getMatrixContext} from "./matrix";
|
||||
import {getNeedsContext} from "./needs";
|
||||
import {getSecretsContext} from "./secrets";
|
||||
import {getStepsContext} from "./steps";
|
||||
import {getStrategyContext} from "./strategy";
|
||||
|
||||
@@ -39,7 +40,7 @@ export async function getContext(
|
||||
continue;
|
||||
}
|
||||
|
||||
value = (await config?.getContext(contextName, value, workflowContext)) || value;
|
||||
value = (await config?.getContext(contextName, value, workflowContext, mode)) || value;
|
||||
|
||||
context.add(contextName, value, getDescription(RootContext, contextName));
|
||||
}
|
||||
@@ -81,11 +82,7 @@ function getDefaultContext(name: string, workflowContext: WorkflowContext, mode:
|
||||
});
|
||||
|
||||
case "secrets":
|
||||
return new DescriptionDictionary({
|
||||
key: "GITHUB_TOKEN",
|
||||
value: new data.StringData("***"),
|
||||
description: getDescription("secrets", "GITHUB_TOKEN")
|
||||
});
|
||||
return getSecretsContext(workflowContext, mode);
|
||||
|
||||
case "steps":
|
||||
return getStepsContext(workflowContext);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import {data, DescriptionDictionary} from "@github/actions-expressions";
|
||||
import {StringData} from "@github/actions-expressions/data/string";
|
||||
import {WorkflowContext} from "../context/workflow-context";
|
||||
import {Mode} from "./default";
|
||||
import {getDescription} from "./descriptions";
|
||||
|
||||
export function getSecretsContext(workflowContext: WorkflowContext, mode: Mode): DescriptionDictionary {
|
||||
const d = new DescriptionDictionary({
|
||||
key: "GITHUB_TOKEN",
|
||||
value: new data.StringData("***"),
|
||||
description: getDescription("secrets", "GITHUB_TOKEN")
|
||||
});
|
||||
|
||||
if (mode === Mode.Completion) {
|
||||
const eventsConfig = workflowContext?.template?.events;
|
||||
if (eventsConfig?.workflow_call?.secrets) {
|
||||
for (const [name, value] of Object.entries(eventsConfig.workflow_call.secrets)) {
|
||||
d.add(name, new StringData(""), value.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const events = workflowContext?.template?.events;
|
||||
if (!events) {
|
||||
return d;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
@@ -314,4 +314,71 @@ jobs:
|
||||
jobs: []
|
||||
});
|
||||
});
|
||||
|
||||
// Extra coverage since workflow_call components are not all covered by x-lang parsers
|
||||
it("converts workflow_call on", async () => {
|
||||
const result = parseWorkflow(
|
||||
{
|
||||
name: "wf.yaml",
|
||||
content: `on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
test:
|
||||
type: string
|
||||
description: 'a test'
|
||||
required: true
|
||||
default: 'testing 123'
|
||||
secrets:
|
||||
secret1:
|
||||
description: 'first secret'
|
||||
required: true
|
||||
secret2:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest`
|
||||
},
|
||||
nullTrace
|
||||
);
|
||||
|
||||
const template = await convertWorkflowTemplate(result.context, result.value!, undefined, {
|
||||
errorPolicy: ErrorPolicy.TryConversion
|
||||
});
|
||||
|
||||
expect(serializeTemplate(template)).toEqual({
|
||||
events: {
|
||||
workflow_call: {
|
||||
inputs: {
|
||||
test: {
|
||||
description: "a test",
|
||||
required: true,
|
||||
type: "string",
|
||||
default: "testing 123"
|
||||
}
|
||||
},
|
||||
secrets: {
|
||||
secret1: {
|
||||
description: "first secret",
|
||||
required: true
|
||||
},
|
||||
secret2: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
jobs: [
|
||||
{
|
||||
id: "build",
|
||||
if: {
|
||||
expr: "success()",
|
||||
type: 3
|
||||
},
|
||||
name: "build",
|
||||
needs: undefined,
|
||||
outputs: undefined,
|
||||
"runs-on": "ubuntu-latest",
|
||||
steps: [],
|
||||
type: "job"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "../workflow-template";
|
||||
import {isValidCron} from "./cron";
|
||||
import {convertStringList} from "./string-list";
|
||||
import {convertEventWorkflowCall} from "./workflow-call";
|
||||
import {convertEventWorkflowDispatchInputs} from "./workflow-dispatch";
|
||||
|
||||
export function convertOn(context: TemplateContext, token: TemplateToken): EventsConfig {
|
||||
@@ -66,8 +67,9 @@ export function convertOn(context: TemplateContext, token: TemplateToken): Event
|
||||
...convertPatternFilter("paths", eventToken),
|
||||
...convertFilter("types", eventToken),
|
||||
...convertFilter("workflows", eventToken),
|
||||
// TODO - share input parsing for now, but workflow_call also needs outputs and secrets
|
||||
...convertEventWorkflowDispatchInputs(context, eventToken)
|
||||
// workflow_call and workflow_dispatch share input parsing
|
||||
...convertEventWorkflowDispatchInputs(context, eventToken),
|
||||
...convertEventWorkflowCall(context, eventToken)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import {TemplateContext} from "../../templates/template-context";
|
||||
import {MappingToken, TemplateToken} from "../../templates/tokens";
|
||||
import {isMapping} from "../../templates/tokens/type-guards";
|
||||
import {SecretConfig, WorkflowCallConfig} from "../workflow-template";
|
||||
|
||||
export function convertEventWorkflowCall(context: TemplateContext, token: MappingToken): WorkflowCallConfig {
|
||||
const result: WorkflowCallConfig = {};
|
||||
|
||||
for (const item of token) {
|
||||
const key = item.key.assertString("workflow dispatch input key");
|
||||
|
||||
switch (key.value) {
|
||||
case "inputs":
|
||||
// Ignore, these are handled by convertEventWorkflowDispatchInputs
|
||||
break;
|
||||
|
||||
case "secrets":
|
||||
result.secrets = convertWorkflowCallSecrets(context, item.value.assertMapping("workflow dispatch inputs"));
|
||||
break;
|
||||
|
||||
case "outputs":
|
||||
// TODO - outputs
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertWorkflowCallSecrets(
|
||||
context: TemplateContext,
|
||||
token: MappingToken
|
||||
): {[secretName: string]: SecretConfig} {
|
||||
const result: {[secretName: string]: SecretConfig} = {};
|
||||
|
||||
for (const item of token) {
|
||||
const secretName = item.key.assertString("secret name");
|
||||
|
||||
result[secretName.value] = convertWorkflowCallSecret(context, item.value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertWorkflowCallSecret(context: TemplateContext, token: TemplateToken): SecretConfig {
|
||||
const result: SecretConfig = {};
|
||||
|
||||
if (isMapping(token)) {
|
||||
for (const item of token) {
|
||||
const key = item.key.assertString("workflow call secret key");
|
||||
|
||||
switch (key.value) {
|
||||
case "description":
|
||||
result.description = item.value.assertString("secret description").value;
|
||||
break;
|
||||
|
||||
case "required":
|
||||
result.required = item.value.assertBoolean("secret required").value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -158,10 +158,10 @@ export type WorkflowDispatchConfig = {
|
||||
};
|
||||
|
||||
export type WorkflowCallConfig = {
|
||||
inputs: {[inputName: string]: InputConfig};
|
||||
inputs?: {[inputName: string]: InputConfig};
|
||||
secrets?: {[secretName: string]: SecretConfig};
|
||||
// TODO - these are supported in C# and Go but not in TS yet
|
||||
// outputs: { [outputName: string]: OutputConfig }
|
||||
// secrets: { [secretName: string]: SecretConfig }
|
||||
};
|
||||
|
||||
export enum InputType {
|
||||
@@ -179,6 +179,11 @@ export type InputConfig = {
|
||||
options?: string[];
|
||||
};
|
||||
|
||||
export type SecretConfig = {
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
export type ScheduleConfig = {
|
||||
cron: string;
|
||||
};
|
||||
|
||||
@@ -204,6 +204,10 @@ on:
|
||||
description: 'Foo'
|
||||
required: true
|
||||
default: 'bar'
|
||||
secrets:
|
||||
password:
|
||||
description: 'Password'
|
||||
required: true
|
||||
workflow_run:
|
||||
workflows: ci
|
||||
types:
|
||||
@@ -500,6 +504,12 @@ jobs:
|
||||
"required": true,
|
||||
"default": "bar"
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"password": {
|
||||
"description": "Password",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workflow_run": {
|
||||
|
||||
Reference in New Issue
Block a user