Add variables context provider (#88)

This commit is contained in:
Laura Yu
2023-01-18 16:31:49 -08:00
committed by GitHub
parent 235709c1af
commit 05de7282bd
3 changed files with 162 additions and 1 deletions
@@ -35,6 +35,13 @@ const testContext = new Dictionary(
value: new BooleanData(true)
})
},
{
key: "vars",
value: new Dictionary({
key: "VAR_NAME",
value: new BooleanData(true)
})
},
{
key: "hashFiles(1,255)",
value: new Dictionary()
@@ -110,6 +117,14 @@ describe("auto-complete", () => {
expect(testComplete("toJSON(secrets.")).toEqual(expected);
});
it("provides suggestions for variables", () => {
const expected = completionItems("VAR_NAME");
expect(testComplete("vars.V")).toEqual(expected);
expect(testComplete("1 == vars.F")).toEqual(expected);
expect(testComplete("toJSON(vars.")).toEqual(expected);
});
it("provides suggestions for contexts in function call", () => {
expect(testComplete("toJSON(env.|)")).toEqual(completionItems("BAR_TEST", "FOO"));
});
@@ -4,6 +4,7 @@ import {WorkflowContext} from "@github/actions-languageservice/context/workflow-
import {Octokit} from "@octokit/rest";
import {getSecrets} from "./context-providers/secrets";
import {getStepsContext} from "./context-providers/steps";
import {getVariables} from "./context-providers/variables";
import {RepositoryContext} from "./initializationOptions";
import {TTLCache} from "./utils/cache";
@@ -28,7 +29,8 @@ export function contextProviders(
switch (name) {
case "secrets":
return await getSecrets(workflowContext, octokit, cache, repo, defaultContext);
case "vars":
return await getVariables(workflowContext, octokit, cache, repo, defaultContext);
case "steps":
return await getStepsContext(octokit, cache, defaultContext, workflowContext);
}
@@ -0,0 +1,144 @@
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
import {isMapping, isString} from "@github/actions-workflow-parser";
import {Octokit} from "@octokit/rest";
import {Pair} from "@github/actions-expressions/data/expressiondata";
import {RepositoryContext} from "../initializationOptions";
import {StringData} from "@github/actions-expressions/data/index";
import {TTLCache} from "../utils/cache";
export async function getVariables(
workflowContext: WorkflowContext,
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext,
defaultContext: DescriptionDictionary | undefined
): Promise<DescriptionDictionary | undefined> {
let environmentName: string | undefined;
if (workflowContext?.job?.environment) {
if (isString(workflowContext.job.environment)) {
environmentName = workflowContext.job.environment.value;
} else if (isMapping(workflowContext.job.environment)) {
for (const x of workflowContext.job.environment) {
if (isString(x.key) && x.key.value === "name") {
if (isString(x.value)) {
environmentName = x.value.value;
}
break;
}
}
}
}
const variables = await getRemoteVariables(octokit, cache, repo, environmentName);
// Build combined map of variables
const variablesMap = new Map<
string,
{
key: string;
value: data.StringData;
description?: string;
}
>();
variables.repoVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Repository variable`
})
);
// Override repo variables with environment veriables (if defined)
variables.environmentVariables.forEach(variable =>
variablesMap.set(variable.key.toLowerCase(), {
key: variable.key,
value: new data.StringData(variable.value.coerceString()),
description: `${variable.value.coerceString()} - Variable for environment \`${environmentName}\``
})
);
const variablesContext = defaultContext || new DescriptionDictionary();
// Sort variables by key and add to context
Array.from(variablesMap.values())
.sort((a, b) => a.key.localeCompare(b.key))
.forEach(variable => variablesContext?.add(variable.key, variable.value, variable.description));
return variablesContext;
}
export async function getRemoteVariables(
octokit: Octokit,
cache: TTLCache,
repo: RepositoryContext,
environmentName?: string
): Promise<{
repoVariables: Pair[];
environmentVariables: Pair[];
}> {
// Repo variables
return {
repoVariables: await cache.get(`${repo.owner}/${repo.name}/vars`, undefined, () =>
fetchVariables(octokit, repo.owner, repo.name)
),
environmentVariables:
(environmentName &&
(await cache.get(`${repo.owner}/${repo.name}/vars/environment/${environmentName}`, undefined, () =>
fetchEnvironmentVariables(octokit, repo.id, environmentName)
))) ||
[]
};
}
async function fetchVariables(octokit: Octokit, owner: string, name: string): Promise<Pair[]> {
try {
const response = (await octokit.paginate("GET /repos/{owner}/{repo}/actions/variables{?per_page}", {
owner: owner,
repo: name
})) as {
name: string;
value: string;
created_at: string;
updated_at: string;
}[];
return response.map(variable => {
return {key: variable.name, value: new StringData(variable.value)};
});
} catch (e) {
console.log("Failure to retrieve variables: ", e);
}
return [];
}
async function fetchEnvironmentVariables(
octokit: Octokit,
repositoryId: number,
environmentName: string
): Promise<Pair[]> {
try {
const response = (await octokit.paginate(
"GET /repositories/{repository_id}/environments/{environment_name}/variables{?per_page}",
{
repository_id: repositoryId,
environment_name: environmentName
}
)) as {
name: string;
value: string;
created_at: string;
updated_at: string;
}[];
return response.map(variable => {
return {key: variable.name, value: new StringData(variable.value)};
});
} catch (e) {
console.log("Failure to retrieve environment variables: ", e);
}
return [];
}