UNPKG

@catladder/pipeline

Version:

Panter workflow for cloud CI/CD and DevOps

232 lines (212 loc) • 7.27 kB
import { existsSync } from "fs"; import { merge } from "lodash"; import path from "path"; import type { BuildConfig, BuildConfigDocker } from "."; import { isOfDeployType } from "../deploy"; import { getArtifactsRegistryBuildCacheImage, getArtifactsRegistryHost, getArtifactsRegistryImageName, } from "../deploy/cloudRun/artifactsRegistry"; import { gcloudServiceAccountLoginCommands } from "../deploy/cloudRun/utils/gcloudServiceAccountLoginCommands"; import { getRunnerImage } from "../runner"; import type { ComponentContext, ComponentContextWithBuild, DockerBuildJobDefinition, } from "../types"; import type { CatladderJob } from "../types/jobs"; import { collapseableSection } from "../utils/gitlab"; import { createJobCacheFromCacheConfigs } from "./cache/createJobCache"; const DOCKER_BUILD_RUNNER_REQUESTS = { KUBERNETES_CPU_REQUEST: "0.45", KUBERNETES_MEMORY_REQUEST: "1Gi", KUBERNETES_MEMORY_LIMIT: "2Gi", }; export const getDockerImageVariables = (context: ComponentContext) => { const deployConfig = context.deploy?.config; return { ...(isOfDeployType(deployConfig, "google-cloudrun") ? { DOCKER_REGISTRY: getArtifactsRegistryHost(context), DOCKER_IMAGE: getArtifactsRegistryImageName(context), DOCKER_CACHE_IMAGE: getArtifactsRegistryBuildCacheImage(context), } : // gitlab registry: { DOCKER_REGISTRY: "$CI_REGISTRY", DOCKER_CACHE_IMAGE: "$CI_REGISTRY_IMAGE/caches/" + context.name, // ONLY USED IN KUBERNETES DOCKER_IMAGE_NAME: context.env + "/" + context.name, DOCKER_IMAGE: "$CI_REGISTRY_IMAGE/$DOCKER_IMAGE_NAME", }), DOCKER_IMAGE_TAG: "$CI_COMMIT_SHA", }; }; /** * Weather the context requires a docker build */ export const requiresDockerBuild = (context: ComponentContext): boolean => { const deployConfig = context.deploy?.config; return ( isOfDeployType( deployConfig, "kubernetes", "google-cloudrun", "dockerTag", ) || (isOfDeployType(deployConfig, "custom") && deployConfig.requiresDocker) ); }; // those need to be runner variables const getDockerBuildRunnerVariables = () => ({ DOCKER_HOST: "tcp://docker:2375", DOCKER_TLS_CERTDIR: "", DOCKER_DRIVER: "overlay2", DOCKER_BUILDKIT: "1", // see https://docs.docker.com/develop/develop-images/build_enhancements/ }); const getDockerAdditions = (build: BuildConfig) => { if (!("docker" in build)) return {}; if (!build.docker) return {}; return { DOCKERFILE_ADDITIONS: "additionsBegin" in build.docker ? build.docker.additionsBegin?.join("\n") : undefined, DOCKERFILE_ADDITIONS_END: "additionsEnd" in build.docker ? build.docker.additionsEnd?.join("\n") : undefined, }; }; export const getDockerBuildVariables = (context: ComponentContextWithBuild) => { return { ...getDockerAdditions(context.build.config), APP_DIR: context.build.dir, DOCKER_BUILD_CONTEXT: "docker" in context.build.config && context.build.config.docker && "buildContextLocation" in context.build.config.docker && context.build.config.docker?.buildContextLocation === "component" ? context.build.dir : ".", ...getDockerImageVariables(context), }; }; export const DOCKER_BUILD_JOB_NAME = "🔨 docker"; export const getDockerJobBaseProps = (): Pick< CatladderJob, "image" | "services" | "variables" | "runnerVariables" > => { return { image: getRunnerImage("docker-build"), services: [ { name: "docker:24.0.6-dind", // see see https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27300#note_466755332 command: ["--tls=false", "--registry-mirror=https://mirror.gcr.io"], }, ], variables: {}, runnerVariables: getDockerBuildRunnerVariables(), }; }; export const createDockerBuildJobBase = ( context: ComponentContextWithBuild, { script, cache, ...def }: DockerBuildJobDefinition, ): CatladderJob => { return merge( { name: DOCKER_BUILD_JOB_NAME, envMode: "jobPerEnv", stage: "build", cache: cache ? createJobCacheFromCacheConfigs(context, cache) : undefined, ...getDockerJobBaseProps(), script: script || [], }, { variables: getDockerBuildVariables(context), runnerVariables: { ...DOCKER_BUILD_RUNNER_REQUESTS, ...getDockerBuildRunnerVariables(), }, }, def, ); }; export const gitlabDockerLogin = (context: ComponentContext) => context.deploy && isOfDeployType(context.deploy.config, "google-cloudrun") ? [ ...gcloudServiceAccountLoginCommands(context), `gcloud auth configure-docker ${getArtifactsRegistryHost(context)}`, ] : [ "docker login --username gitlab-ci-token --password $CI_JOB_TOKEN $CI_REGISTRY", ]; const BUILT_IN_ENSURE_DOCKERFILE_SCRIPTS = { meteor: "ensureMeteorDockerfile", node: "ensureNodeDockerfile", nginx: "ensureNginxDockerfile", custom: null, } satisfies { [type in BuildConfigDocker["type"]]: string | null; }; const getEnsureDockerFileScript = ( context: ComponentContextWithBuild, fallbackType?: BuildConfigDocker["type"], ) => { if ( "docker" in context.build.config && context.build.config.docker && "type" in context.build.config.docker ) { if (context.build.config.docker?.type === "custom") { if (context.build.config.docker?.dockerfileContent) { // we need to create a script that creates a Dockerfile in the current directory return ` echo "Creating Dockerfile" cat >$APP_DIR/Dockerfile <<EOF ${context.build.config.docker?.dockerfileContent?.join("\n")} EOF`; } } const type = context.build.config.docker?.type ?? fallbackType; return type ? BUILT_IN_ENSURE_DOCKERFILE_SCRIPTS[type] : null; } return fallbackType ? BUILT_IN_ENSURE_DOCKERFILE_SCRIPTS[fallbackType] : null; }; export const getDockerBuildScriptWithBuiltInDockerFile = ( context: ComponentContextWithBuild, fallbackType?: BuildConfigDocker["type"], ) => { return getDockerBuildDefaultScript( context, getEnsureDockerFileScript(context, fallbackType), ); }; export const getDockerBuildDefaultScript = ( context: ComponentContext, ensureDockerFileScript?: string | null, ) => [ ensureDockerFileScript ?? undefined, ...collapseableSection( "docker-login", "Docker Login", )(gitlabDockerLogin(context)), ...collapseableSection( "docker-build", "Docker build", )([ "docker build --network host --cache-from $DOCKER_CACHE_IMAGE --tag $DOCKER_IMAGE:$DOCKER_IMAGE_TAG -f $APP_DIR/Dockerfile $DOCKER_BUILD_CONTEXT --build-arg BUILDKIT_INLINE_CACHE=1", //BUILDKIT_INLINE_CACHE, see https://testdriven.io/blog/faster-ci-builds-with-docker-cache/ ]), ...collapseableSection( "docker-push", "Docker push and tag", )([ "docker push $DOCKER_IMAGE:$DOCKER_IMAGE_TAG", "docker tag $DOCKER_IMAGE:$DOCKER_IMAGE_TAG $DOCKER_CACHE_IMAGE", "docker push $DOCKER_CACHE_IMAGE", ]), ].filter(Boolean); export const hasDockerfile = (context: ComponentContext) => existsSync(path.join(context.build.dir, "Dockerfile"));