Use fetchActionMetadata to check action validity (#147)

This commit is contained in:
Laura Yu
2023-02-15 16:19:05 -08:00
committed by GitHub
parent a3d27a797b
commit d0be8df735
6 changed files with 74 additions and 44 deletions
+3 -3
View File
@@ -24,9 +24,9 @@ import {getFileProvider} from "./file-provider";
import {InitializationOptions, RepositoryContext} from "./initializationOptions"; import {InitializationOptions, RepositoryContext} from "./initializationOptions";
import {onCompletion} from "./on-completion"; import {onCompletion} from "./on-completion";
import {ReadFileRequest, Requests} from "./request"; import {ReadFileRequest, Requests} from "./request";
import {fetchActionMetadata} from "./utils/action-metadata";
import {TTLCache} from "./utils/cache"; import {TTLCache} from "./utils/cache";
import {valueProviders} from "./value-providers"; import {valueProviders} from "./value-providers";
import {getActionInputs} from "./value-providers/action-inputs";
export function initConnection(connection: Connection) { export function initConnection(connection: Connection) {
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
@@ -102,9 +102,9 @@ export function initConnection(connection: Connection) {
const config: ValidationConfig = { const config: ValidationConfig = {
valueProviderConfig: valueProviders(client, repoContext, cache), valueProviderConfig: valueProviders(client, repoContext, cache),
contextProviderConfig: contextProviders(client, repoContext, cache), contextProviderConfig: contextProviders(client, repoContext, cache),
getActionInputs: async action => { fetchActionMetadata: async action => {
if (client) { if (client) {
return await getActionInputs(client, cache, action); return await fetchActionMetadata(client, cache, action);
} }
return undefined; return undefined;
@@ -1,14 +1,9 @@
import {ActionReference, ActionInputs, ActionOutputs, actionIdentifier} from "@github/actions-languageservice/action"; import {ActionReference, ActionMetadata, actionIdentifier} from "@github/actions-languageservice/action";
import {error} from "@github/actions-languageservice/log"; import {error} from "@github/actions-languageservice/log";
import {Octokit, RestEndpointMethodTypes} from "@octokit/rest"; import {Octokit, RestEndpointMethodTypes} from "@octokit/rest";
import {parse} from "yaml"; import {parse} from "yaml";
import {TTLCache} from "./cache"; import {TTLCache} from "./cache";
export type ActionMetadata = {
inputs?: ActionInputs;
outputs?: ActionOutputs;
};
export async function fetchActionMetadata( export async function fetchActionMetadata(
client: Octokit, client: Octokit,
cache: TTLCache, cache: TTLCache,
+6
View File
@@ -1,3 +1,9 @@
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
export type ActionMetadata = {
inputs?: ActionInputs;
outputs?: ActionOutputs;
};
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs // https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
export type ActionInput = { export type ActionInput = {
description: string; description: string;
@@ -14,7 +14,7 @@ export async function validateAction(
step: Step | undefined, step: Step | undefined,
config: ValidationConfig | undefined config: ValidationConfig | undefined
): Promise<void> { ): Promise<void> {
if (!isMapping(stepToken) || !step || !isActionStep(step) || !config?.getActionInputs) { if (!isMapping(stepToken) || !step || !isActionStep(step) || !config?.fetchActionMetadata) {
return; return;
} }
@@ -23,8 +23,8 @@ export async function validateAction(
return; return;
} }
const actionInputs = await config.getActionInputs(action); const actionMetadata = await config.fetchActionMetadata(action);
if (actionInputs === undefined) { if (actionMetadata === undefined) {
diagnostics.push({ diagnostics.push({
severity: DiagnosticSeverity.Error, severity: DiagnosticSeverity.Error,
range: mapRange(step.uses.range), range: mapRange(step.uses.range),
@@ -50,6 +50,11 @@ export async function validateAction(
} }
} }
const actionInputs = actionMetadata.inputs;
if (actionInputs === undefined) {
return;
}
for (const [input, inputToken] of stepInputs) { for (const [input, inputToken] of stepInputs) {
if (!actionInputs[input]) { if (!actionInputs[input]) {
diagnostics.push({ diagnostics.push({
@@ -1,5 +1,5 @@
import {DiagnosticSeverity} from "vscode-languageserver-types"; import {DiagnosticSeverity} from "vscode-languageserver-types";
import {ActionInput, ActionReference} from "./action"; import {ActionMetadata, ActionReference} from "./action";
import {registerLogger} from "./log"; import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document"; import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger"; import {TestLogger} from "./test-utils/logger";
@@ -9,53 +9,63 @@ import {ValueProviderKind} from "./value-providers/config";
registerLogger(new TestLogger()); registerLogger(new TestLogger());
const validationConfig: ValidationConfig = { const validationConfig: ValidationConfig = {
getActionInputs: async (ref: ActionReference) => { fetchActionMetadata: async (ref: ActionReference) => {
let inputs: Record<string, ActionInput> | undefined = undefined; let metadata: ActionMetadata | undefined = undefined;
switch (ref.owner + "/" + ref.name + "@" + ref.ref) { switch (ref.owner + "/" + ref.name + "@" + ref.ref) {
case "actions/checkout@v3": case "actions/checkout@v3":
inputs = { metadata = {
repository: { inputs: {
description: "Repository name with owner", repository: {
default: "${{ github.repository }}" description: "Repository name with owner",
default: "${{ github.repository }}"
}
} }
}; };
break; break;
case "actions/setup-node@v1": case "actions/setup-node@v1":
inputs = { metadata = {
version: { inputs: {
description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019", version: {
deprecationMessage: description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019",
"The version property will not be supported after October 1, 2019. Use node-version instead" deprecationMessage:
"The version property will not be supported after October 1, 2019. Use node-version instead"
}
} }
}; };
break; break;
case "actions/deploy-pages@main": case "actions/deploy-pages@main":
inputs = { metadata = {
token: { inputs: {
required: true, token: {
description: "token to use", required: true,
default: "${{ github.token }}" description: "token to use",
default: "${{ github.token }}"
}
} }
}; };
break; break;
case "actions/cache@v1": case "actions/cache@v1":
inputs = { metadata = {
path: { inputs: {
description: "A directory to store and save the cache", path: {
required: true description: "A directory to store and save the cache",
}, required: true
key: { },
description: "An explicit key for restoring and saving the cache", key: {
required: true description: "An explicit key for restoring and saving the cache",
}, required: true
"restore-keys": { },
description: "An ordered list of keys to use for restoring the cache if no cache hit occurred for key", "restore-keys": {
required: false description: "An ordered list of keys to use for restoring the cache if no cache hit occurred for key",
required: false
}
} }
}; };
break; break;
case "actions/action-no-input@v1":
metadata = {};
} }
return inputs; return metadata;
} }
}; };
@@ -103,6 +113,20 @@ jobs:
]); ]);
}); });
it("action does not define inputs", async () => {
const input = `
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/action-no-input@v1
`;
const result = await validate(createDocument("wf.yaml", input), validationConfig);
expect(result).toEqual([]);
});
it("invalid input", async () => { it("invalid input", async () => {
const input = ` const input = `
on: push on: push
+2 -2
View File
@@ -17,7 +17,7 @@ import {File} from "@github/actions-workflow-parser/workflows/file";
import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provider"; import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provider";
import {TextDocument} from "vscode-languageserver-textdocument"; import {TextDocument} from "vscode-languageserver-textdocument";
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types"; import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
import {ActionInputs, ActionReference} from "./action"; import {ActionMetadata, ActionReference} from "./action";
import {ContextProviderConfig} from "./context-providers/config"; import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default"; import {getContext, Mode} from "./context-providers/default";
@@ -35,7 +35,7 @@ import {defaultValueProviders} from "./value-providers/default";
export type ValidationConfig = { export type ValidationConfig = {
valueProviderConfig?: ValueProviderConfig; valueProviderConfig?: ValueProviderConfig;
contextProviderConfig?: ContextProviderConfig; contextProviderConfig?: ContextProviderConfig;
getActionInputs?(action: ActionReference): Promise<ActionInputs | undefined>; fetchActionMetadata?(action: ActionReference): Promise<ActionMetadata | undefined>;
fileProvider?: FileProvider; fileProvider?: FileProvider;
}; };