Merge pull request #51 from github/thyeggman/implement-job-context-provider
Implement `job` context provider
This commit is contained in:
@@ -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 = `
|
||||
|
||||
Generated
+6
-6
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user