Files
languageservices/workflow-parser/src/model/converter/container.ts
T
eric sciple f62a0e189d Remove allowServiceContainerCommand feature flag (#345)
Service container entrypoint/command support is now unconditional.
2026-04-03 10:29:34 -05:00

185 lines
5.8 KiB
TypeScript

import {TemplateContext} from "../../templates/template-context.js";
import {MappingToken, SequenceToken, StringToken, TemplateToken} from "../../templates/tokens/index.js";
import {isString} from "../../templates/tokens/type-guards.js";
import {Container, Credential} from "../workflow-template.js";
export function convertToJobContainer(context: TemplateContext, container: TemplateToken): Container | undefined {
let image: StringToken | undefined;
let env: MappingToken | undefined;
let ports: SequenceToken | undefined;
let volumes: SequenceToken | undefined;
let options: StringToken | undefined;
// Skip validation for expressions for now to match
// behavior of the other parsers
for (const [, token] of TemplateToken.traverse(container)) {
if (token.isExpression) {
return;
}
}
if (isString(container)) {
// Workflow uses shorthand syntax `container: image-name`
image = container.assertString("container item");
return {image: image};
}
const mapping = container.assertMapping("container item");
if (mapping)
for (const item of mapping) {
const key = item.key.assertString("container item key");
const value = item.value;
switch (key.value) {
case "image":
image = value.assertString("container image");
break;
case "credentials":
convertToJobCredentials(context, value);
break;
case "env":
env = value.assertMapping("container env");
for (const envItem of env) {
envItem.key.assertString("container env value");
}
break;
case "ports":
ports = value.assertSequence("container ports");
for (const port of ports) {
port.assertString("container port");
}
break;
case "volumes":
volumes = value.assertSequence("container volumes");
for (const volume of volumes) {
volume.assertString("container volume");
}
break;
case "options":
options = value.assertString("container options");
break;
default:
context.error(key, `Unexpected container item key: ${key.value}`);
}
}
if (!image) {
context.error(container, "Container image cannot be empty");
} else {
return {image, env, ports, volumes, options};
}
}
export function convertToServiceContainer(context: TemplateContext, container: TemplateToken): Container | undefined {
let image: StringToken | undefined;
let env: MappingToken | undefined;
let ports: SequenceToken | undefined;
let volumes: SequenceToken | undefined;
let options: StringToken | undefined;
let entrypoint: StringToken | undefined;
let command: StringToken | undefined;
// Skip validation for expressions for now to match
// behavior of the other parsers
for (const [, token] of TemplateToken.traverse(container)) {
if (token.isExpression) {
return;
}
}
if (isString(container)) {
image = container.assertString("container item");
return {image: image};
}
const mapping = container.assertMapping("container item");
if (mapping)
for (const item of mapping) {
const key = item.key.assertString("container item key");
const value = item.value;
switch (key.value) {
case "image":
image = value.assertString("container image");
break;
case "credentials":
convertToJobCredentials(context, value);
break;
case "env":
env = value.assertMapping("container env");
for (const envItem of env) {
envItem.key.assertString("container env value");
}
break;
case "ports":
ports = value.assertSequence("container ports");
for (const port of ports) {
port.assertString("container port");
}
break;
case "volumes":
volumes = value.assertSequence("container volumes");
for (const volume of volumes) {
volume.assertString("container volume");
}
break;
case "options":
options = value.assertString("container options");
break;
case "entrypoint":
entrypoint = value.assertString("container entrypoint");
break;
case "command":
command = value.assertString("container command");
break;
default:
context.error(key, `Unexpected container item key: ${key.value}`);
}
}
if (!image) {
context.error(container, "Container image cannot be empty");
} else {
return {image, env, ports, volumes, options, entrypoint, command};
}
}
export function convertToJobServices(context: TemplateContext, services: TemplateToken): Container[] | undefined {
const serviceList: Container[] = [];
const mapping = services.assertMapping("services");
for (const service of mapping) {
service.key.assertString("service key");
const container = convertToServiceContainer(context, service.value);
if (container) {
serviceList.push(container);
}
}
return serviceList;
}
function convertToJobCredentials(context: TemplateContext, value: TemplateToken): Credential | undefined {
const mapping = value.assertMapping("credentials");
let username: StringToken | undefined;
let password: StringToken | undefined;
for (const item of mapping) {
const key = item.key.assertString("credentials item");
const value = item.value;
switch (key.value) {
case "username":
username = value.assertString("credentials username");
break;
case "password":
password = value.assertString("credentials password");
break;
default:
context.error(key, `credentials key ${key.value}`);
}
}
return {username, password};
}