Nhiệm vụ: Thêm tính năng tải xuống docker image

Thêm tính năng tải xuống docker image

Hiện tại có một yêu cầu tạo container

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

Chúng ta nhận được lỗi

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

Điều này là do image chưa được tải xuống. Cần phải: 1. Thêm một resolver riêng để tải xuống các image 2. Thêm kiểm tra vào resolver tạo container xem image đã tồn tại chưa, và nếu chưa, hãy tải nó xuống, rồi mới tạo container.

Ворклоги

Đã xong.

Các hàm hỗ trợ (Helpers):

Tải xuống image

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)
          }
        },
      )
    })
  })
}

Kiểm tra sự tồn tại của image và tiến hành tải xuống

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
    }
  }
}

Resolver tạo container đã được cập nhật với tính năng kiểm tra và tải xuống image

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
          })
      },
    }),
)