@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
162 lines (149 loc) • 5.42 kB
text/typescript
/*
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { File as ProjectFile } from "@atomist/automation-client/lib/project/File";
import { FileParser } from "@atomist/automation-client/lib/tree/ast/FileParser";
import { logger } from "@atomist/automation-client/lib/util/logger";
import { TreeNode } from "@atomist/tree-path";
import { DockerfileParser, From, Instruction, Label } from "dockerfile-ast";
import { TextDocument } from "vscode-languageserver-textdocument";
import { Position } from "vscode-languageserver-types/lib/umd/main";
class DockerFileParserClass implements FileParser {
public readonly rootName: "docker";
public async toAst(f: ProjectFile): Promise<TreeNode> {
try {
const dockerfile = DockerfileParser.parse(await f.getContent());
const doc = (dockerfile as any).document;
const $children = dockerfile.getInstructions().map(i => instructionToTreeNode(i, doc));
return {
$name: f.name,
$children,
};
} catch (err) {
logger.error("Error parsing Dockerfile", err);
throw err;
}
}
}
/**
* FileParser instance to use for Docker files.
* Example path expressions, given "node:argon" as the image
* //FROM/image/name - returns a node with the value "node" etc
* //FROM/image/tag - returns a node with the value "argon" etc
* @type {DockerFileParserClass}
*/
export const DockerFileParser: FileParser = new DockerFileParserClass();
function instructionToTreeNode(l: Instruction, doc: TextDocument, parent?: TreeNode): TreeNode {
const n: TreeNode = {
$name: l.getKeyword(),
$value: l.getTextContent(),
$parent: parent,
$offset: l.getRange() ? convertToOffset(l.getRange().start, doc) : undefined,
};
// Deconstruct subelements. There is no generic tree structure in the
// AST library, so we need to do this manually for subelements we care about.
if (isFrom(l)) {
addChildrenFromFromStructure(n, l, doc);
} else if (isLabel(l)) {
addChildrenFromLabelStructure(n, l, doc);
} else {
switch (l.getKeyword()) {
case "MAINTAINER":
addChildrenFromMaintainer(n, l, doc);
break;
case "EXPOSE":
n.$children = [
{
$name: "port",
$value: l.getArgumentsContent(),
},
];
break;
default:
break;
}
}
return n;
}
function isFrom(l: Instruction): l is From {
const maybe = l as From;
return !!maybe.getImageName;
}
function isLabel(l: Instruction): l is Label {
const maybe = l as Label;
return !!maybe.getProperties;
}
// Deconstruct FROM to add children.
// We need to do this for all structures we want to see inside
function addChildrenFromFromStructure(n: TreeNode, l: From, doc: TextDocument): void {
const nameChild = {
$name: "name",
$value: l.getImageName(),
$offset: convertToOffset(l.getImageNameRange().start, doc),
};
const $children = [nameChild];
if (!!l.getImageTag()) {
$children.push({
$name: "tag",
$value: l.getImageTag(),
$offset: convertToOffset(l.getImageTagRange().start, doc),
});
}
n.$children = [
{
$parent: n,
$name: "image",
$value: l.getImage(),
$offset: convertToOffset(l.getImageRange().start, doc),
$children,
},
];
}
function addChildrenFromLabelStructure(n: TreeNode, l: Label, doc: TextDocument): void {
n.$children = l.getProperties().map(prop => ({
$parent: n,
$name: "pair",
$value: l.getTextContent().slice("LABEL ".length),
// Children are the name and value
$children: [
{
$name: "key",
$value: prop.getName(),
$offset: convertToOffset(prop.getNameRange().start, doc),
},
{
$name: "value",
$value: prop.getValue(),
$offset: prop.getValueRange() ? convertToOffset(prop.getValueRange().start, doc) : undefined,
},
],
}));
}
function addChildrenFromMaintainer(n: TreeNode, m: Instruction, doc: TextDocument): void {
const rest = n.$value.slice("MAINTAINER ".length);
n.$children = [
{
$parent: n,
$name: "maintainer",
$value: rest,
$offset: n.$offset + "MAINTAINER ".length,
$children: [],
},
];
}
// Convert a position to an offset, given the document
function convertToOffset(pos: Position, doc: TextDocument): number {
return doc.offsetAt(pos);
}