Задача Добавить функцию скачивания докер-образов

Добавить функцию скачивания докер-образов

Сейчас есть запрос на создание контейнера

mutation createContainer {
  createContainer(
    image: "node:22-alpine"
    cmd: ["tail", "-f", "/dev/null"]
    labels: { test: "SDfsdf" }
  ) {
    ...DockerContainer_
  }
}

Получаем ошибку

{
  "errors": [
    {
      "message": "(HTTP code 404) no such container - No such image: node:22-alpine ",
      "locations": [
        {
          "line": 16,
          "column": 3
        }
      ],
      "path": [
        "createContainer"
      ],

Это потому что образ еще не скачан. Надо: 1. Добавить отдельный резолвер для скачивания образов 2. Добавить в резолвер создания контейнера проверку есть ли уже образ, и если нету, то скачивать его, и только потом создавать контейнер.

Ворклоги

Сделано.

Хелперы:

Скачивание образа

import Docker from 'dockerode'
import { DockerImage } from '../DockerImage/types'

export async function pullDockerImage(
  docker: Docker,
  image: string,
): Promise<DockerImage> {
  return new Promise((resolve, reject) => {
    docker.pull(image, (err: Error | null, stream: NodeJS.ReadableStream) => {
      if (err) {
        reject(err)
        return
      }

      docker.modem.followProgress(
        stream,
        async (err: Error | null) => {
          if (err) {
            reject(err)
            return
          }

          try {
            const imageInfo = docker.getImage(image)
            const inspectData = await imageInfo.inspect()

            resolve({
              id: inspectData.Id,
              repoTags: inspectData.RepoTags,
              repoDigests: inspectData.RepoDigests,
              created: new Date(inspectData.Created),
              size: inspectData.Size,
              virtualSize: inspectData.VirtualSize,
              labels: inspectData.Config?.Labels ?? null,
            })
          } catch (inspectErr) {
            reject(inspectErr)
          }
        },
        (event: { status: string }) => {
          if (process.env.NODE_ENV === 'development') {
            // eslint-disable-next-line no-console
            console.log('pullDockerImage progress:', event.status)
          }
        },
      )
    })
  })
}

Проверка наличия образа и скачивание

import Docker from 'dockerode'
import { pullDockerImage } from './pullDockerImage'

export async function ensureDockerImage(
  docker: Docker,
  image: string,
): Promise<void> {
  try {
    const imageInfo = docker.getImage(image)
    await imageInfo.inspect()
  } catch (err) {
    if ((err as { statusCode?: number }).statusCode === 404) {
      await pullDockerImage(docker, image)
    } else {
      throw err
    }
  }
}

Обновленный резолвер создания контейнера с проверкой и скачиванием образа

import { Container } from 'dockerode'
import { builder } from 'server/schema/builder'
import { ensureDockerImage } from '../../helpers/ensureDockerImage'
import { DockerContainer } from '../types'

export const createDockerContainerResolver = builder.mutationField(
  'createDockerContainer',
  (t) =>
    t.field({
      type: DockerContainer,
      nullable: true,
      args: {
        image: t.arg.string({ required: true }),
        labels: t.arg({ type: 'Json' }),
        cmd: t.arg.stringList(),
      },
      async resolve(_, { image, labels, cmd }, ctx) {
        const { dockerClient } = ctx
        const docker = dockerClient.getDockerClient()

        await ensureDockerImage(docker, image)

        let auxContainer: Container | undefined

        return docker
          .createContainer({
            Image: image,
            AttachStdin: false,
            AttachStdout: true,
            AttachStderr: true,
            Tty: true,
            Cmd: cmd ?? undefined,
            OpenStdin: false,
            StdinOnce: false,
            Labels:
              typeof labels === 'object'
                ? (labels as Record<string, string>)
                : undefined,
          })
          .then(async function (container) {
            auxContainer = container

            return auxContainer.start()
          })
          .then(function () {
            return auxContainer?.inspect()
          })
          .then(function (containerInfo): DockerContainer | null {
            return containerInfo
              ? {
                  id: containerInfo.Id,
                  names: [containerInfo.Name],
                  command: containerInfo.Config.Cmd,
                  created: new Date(containerInfo.Created),
                  image: containerInfo.Config.Image,
                  imageID: containerInfo.Image,
                  state: containerInfo.State.Status,
                  status: containerInfo.State.Status,
                  labels: containerInfo.Config.Labels,
                }
              : null
          })
      },
    }),
)