snyk-docker-plugin
Version: 
Snyk CLI docker plugin
96 lines (80 loc) • 2.59 kB
text/typescript
import { DockerfileParser } from "dockerfile-ast";
import { EOL } from "os";
import { getDockerfileBaseImageName } from "./instruction-parser";
import {
  UpdateDockerfileBaseImageNameErrorCode,
  UpdateDockerfileBaseImageNameResult,
} from "./types";
export { updateDockerfileBaseImageName };
/**
 * Updates the image name of the last from stage, after resolving all aliases
 * @param contents Contents of the Dockerfile to update
 * @param newBaseImageName New base image name Dockerfile contents should be updated to
 */
function updateDockerfileBaseImageName(
  contents: string,
  newBaseImageName: string,
): UpdateDockerfileBaseImageNameResult {
  const dockerfile = DockerfileParser.parse(contents);
  const result = getDockerfileBaseImageName(dockerfile);
  if (result.error) {
    return {
      contents,
      error: {
        code: UpdateDockerfileBaseImageNameErrorCode.BASE_IMAGE_NAME_NOT_FOUND,
      },
    };
  }
  const currentBaseImageName = result.baseImage;
  const fromRanges = dockerfile
    .getFROMs()
    .filter((from) => from.getImage() === currentBaseImageName)
    .map((from) => from.getImageRange()!);
  const argRanges = dockerfile
    .getARGs()
    .filter((arg) => arg.getProperty()?.getValue() === currentBaseImageName)
    .map((arg) => arg.getProperty()?.getValueRange()!);
  const ranges = fromRanges.concat(argRanges);
  if (ranges.length === 0) {
    /**
     * This happens when the image is split over multiple FROM and ARG statements
     * making it difficult to update Dockerfiles that fall into these edge cases.
     * e.g.:
     *    ARG REPO=repo
     *    ARG TAG=tag
     *    FROM ${REPO}:${TAG}
     */
    return {
      contents,
      error: {
        code: UpdateDockerfileBaseImageNameErrorCode.BASE_IMAGE_NAME_FRAGMENTED,
      },
    };
  }
  const lines = contents.split(EOL);
  for (const range of ranges) {
    const lineNumber = range.start.line;
    const start = range.start.character;
    const end = range.end.character;
    const content = lines[lineNumber];
    const updated =
      content.substring(0, start) + newBaseImageName + content.substring(end);
    lines[lineNumber] = updated;
  }
  const updatedContents = lines.join(EOL);
  const updatedDockerfile = DockerfileParser.parse(updatedContents);
  if (
    dockerfile.getInstructions().length !==
    updatedDockerfile.getInstructions().length
  ) {
    return {
      contents,
      error: {
        code: UpdateDockerfileBaseImageNameErrorCode.DOCKERFILE_GENERATION_FAILED,
      },
    };
  }
  return {
    contents: updatedContents,
  };
}