UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

162 lines (149 loc) 5.42 kB
/* * 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); }