Merge pull request #51 from github/thyeggman/implement-job-context-provider

Implement `job` context provider
This commit is contained in:
Jacob Wallraff
2022-12-16 14:54:20 -08:00
committed by GitHub
5 changed files with 250 additions and 6 deletions
@@ -674,6 +674,86 @@ jobs:
});
});
describe("job context", () => {
it("job context is suggested within a job", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:14.16
services:
nginx:
image: node:14.16
steps:
- run: echo \${{ job.| }}
`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["container", "services", "status"]);
});
it("container context is suggested within a job container", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:14.16
steps:
- run: echo \${{ job.container.| }}
`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["id", "network"]);
});
it("services are suggested within a job services list", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
services:
nginx:
image: node:14.16
redis:
image: redis
steps:
- run: echo \${{ job.services.| }}
`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["nginx", "redis"]);
});
it("services context is suggested within a job service", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
services:
nginx:
image: node:14.16
ports:
- 80:8080
- 90
steps:
- run: echo \${{ job.services.nginx.| }}
`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["id", "network", "ports"]);
});
});
it("context completion items include kind and insert text", async () => {
const input = `
on: push
@@ -4,6 +4,7 @@ import {WorkflowContext} from "../context/workflow-context";
import {ContextProviderConfig} from "./config";
import {getGithubContext} from "./github";
import {getInputsContext} from "./inputs";
import {getJobContext} from "./job";
import {getMatrixContext} from "./matrix";
import {getNeedsContext} from "./needs";
import {getStepsContext} from "./steps";
@@ -74,6 +75,9 @@ function getDefaultContext(name: string, workflowContext: WorkflowContext, mode:
case "strategy":
return getStrategyContext(workflowContext);
case "job":
return getJobContext(workflowContext);
}
return undefined;
@@ -0,0 +1,67 @@
import {data} from "@github/actions-expressions";
import {isMapping, isSequence} from "@github/actions-workflow-parser";
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
import {WorkflowContext} from "../context/workflow-context";
export function getJobContext(workflowContext: WorkflowContext): data.Dictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#job-context
const jobContext = new data.Dictionary();
const job = workflowContext.job;
if (!job) {
return jobContext;
}
// Container
const jobContainer = job.container;
if (jobContainer && isMapping(jobContainer)) {
const containerContext = createContainerContext(jobContainer, false);
jobContext.add("container", containerContext);
}
// Services
const jobServices = job.services;
if (jobServices && isMapping(jobServices)) {
const servicesContext = new data.Dictionary();
for (const service of jobServices) {
if (!isMapping(service.value)) {
continue;
}
const serviceContext = createContainerContext(service.value, true);
servicesContext.add(service.key.toString(), serviceContext);
}
jobContext.add("services", servicesContext);
}
// Status
jobContext.add("status", new data.Null());
return jobContext;
}
function createContainerContext(container: MappingToken, isServices: boolean): data.Dictionary {
const containerContext = new data.Dictionary();
for (const {key, value} of container) {
if (isSequence(value)) {
// service ports are the only thing that is part of the job context
if (key.toString() !== "ports") {
continue;
}
const ports = new data.Dictionary();
for (const item of value) {
// We can determine the context mapping fully only if the port is defined
// as a mapping (i.e. <port1>:<port2>), single ports are assigned randomly
const portParts = item.toString().split(":");
if (isServices && portParts.length === 2) {
ports.add(portParts[1], new data.StringData(portParts[0]));
} else {
// If the port isn't a mapping, just use null
ports.add(portParts[0], new data.Null());
}
}
containerContext.add(key.toString(), ports);
}
}
containerContext.add("id", new data.Null());
containerContext.add("network", new data.Null());
return containerContext;
}
@@ -224,6 +224,99 @@ jobs:
});
});
describe("job context", () => {
it("job.status", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:14.16
steps:
- run: echo \${{ job.container }}
- run: echo \${{ job.container.id }}
- run: echo \${{ job.container.network }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("job.status", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ job.status }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("job.services.<service_id>", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
services:
nginx:
image: node:14.16
volumes:
- my_docker_volume:/volume_mount
ports:
- 80:8080
steps:
- run: echo \${{ job.services.nginx }}
- run: echo \${{ job.services.nginx.id }}
- run: echo \${{ job.services.nginx.network }}
- run: echo \${{ job.services.nginx.ports }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("job.services.<service_id>", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:14.16
steps:
- run: echo \${{ job.container.tupperware }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: tupperware",
range: {
end: {
character: 49,
line: 9
},
start: {
character: 18,
line: 9
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
describe("strategy context", () => {
it("reference within a matrix job", async () => {
const input = `
+6 -6
View File
@@ -701,9 +701,9 @@
"link": true
},
"node_modules/@github/actions-workflow-parser": {
"version": "0.0.35",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.35/0e88d541486f7c8772c5640fc13fac630cfaeaa8",
"integrity": "sha512-OuNEuqUH4AOfWd2xb8VRl7KwJrtiZu1ic0b27lmadwvK8qgRhPHxvS+QICEc+JtFbbVWpAA2ymh8I1U35639mA==",
"version": "0.0.36",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.36/f27b908936efa949a105d7192dd3787a15094d71",
"integrity": "sha512-ATResf0PCT/bftJgs2ROAo0gDMIKQ1X0HcB4GejuYTcyHxWfvC44EqvFZmMpurV4Camht20bqK3LSY4NoJ0aVQ==",
"license": "MIT",
"dependencies": {
"@github/actions-expressions": "*",
@@ -13539,9 +13539,9 @@
}
},
"@github/actions-workflow-parser": {
"version": "0.0.35",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.35/0e88d541486f7c8772c5640fc13fac630cfaeaa8",
"integrity": "sha512-OuNEuqUH4AOfWd2xb8VRl7KwJrtiZu1ic0b27lmadwvK8qgRhPHxvS+QICEc+JtFbbVWpAA2ymh8I1U35639mA==",
"version": "0.0.36",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.36/f27b908936efa949a105d7192dd3787a15094d71",
"integrity": "sha512-ATResf0PCT/bftJgs2ROAo0gDMIKQ1X0HcB4GejuYTcyHxWfvC44EqvFZmMpurV4Camht20bqK3LSY4NoJ0aVQ==",
"requires": {
"@github/actions-expressions": "*",
"yaml": "^2.0.0-8"