diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0a57031b245adb484280053874b12727acd06e2..aea0ed8b396fb671e80b18da29d10cc5ce7458a8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,8 @@ -Docker: - image: jonasled.dev/infra/docker-build:latest +build: stage: build - needs: [] + image: + name: gcr.io/kaniko-project/executor:v1.23.2-debug + entrypoint: [ "" ] script: - - build-image.sh + - CI_COMMIT_BRANCH=$(echo "$CI_COMMIT_BRANCH" | sed 's/\//-/g') + - /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/Dockerfile" --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_BRANCH}" diff --git a/Dockerfile b/Dockerfile index 0870fd3cac0e9bb21afbc4485683a1bea7e6b5b1..2590b4d4eb9a6aa7cecc177d8a9fc88ab42f9ea8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,49 @@ -FROM alpine:3.21 -ENV S6_LOGGING= + +ARG ALPINE_VERSION=3.21.2 +FROM mplatform/manifest-tool:v2.1.9 AS manifest-tool +FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:1.23.4-alpine AS kaniko-base +ARG KANIKO_VERSION=v1.23.2 +RUN apk add --no-cache bash git make && \ + git clone -b ${KANIKO_VERSION} https://github.com/GoogleContainerTools/kaniko /kanikosrc +WORKDIR /kanikosrc + +FROM kaniko-base AS kaniko-amd64 +ENV GOOS=linux \ + GOARCH=amd64 +RUN make out/executor && \ + make out/warmer +FROM kaniko-base AS kaniko-arm64 +ENV GOOS=linux \ + GOARCH=arm64 +RUN make out/executor && \ + make out/warmer + +FROM public.ecr.aws/docker/library/alpine:$ALPINE_VERSION AS rootfs +ARG ALPINE_VERSION=3.21.2 +RUN wget https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/x86_64/alpine-minirootfs-${ALPINE_VERSION}-x86_64.tar.gz -O alpine-amd64.tar.gz && \ + wget https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/aarch64/alpine-minirootfs-${ALPINE_VERSION}-aarch64.tar.gz -O alpine-arm64.tar.gz && \ + mkdir -p /build/amd64 /build/arm64 && \ + tar -xzf alpine-amd64.tar.gz -C /build/amd64 && \ + tar -xzf alpine-arm64.tar.gz -C /build/arm64 && \ + mkdir /build/amd64/var/run/proot /build/arm64/var/run/proot + +FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:1.23.4-alpine AS helper-tool +COPY helper/ ./ +RUN CGO_ENABLED=0 go build -ldflags "-w -s" -o /build-image + +FROM public.ecr.aws/docker/library/alpine:$ALPINE_VERSION +ENV KANIKO_DIR=/kaniko \ + PROOT_DONT_POLLUTE_ROOTFS=1 \ + PROOT_TMP_DIR=/var/run/proot RUN sed -i 's|dl-cdn.alpinelinux.org/alpine|alpine.jonasled.de|g' /etc/apk/repositories && \ - apk add --no-cache docker buildkit buildctl docker-cli-buildx docker-cli-compose \ - qemu jq git s6-overlay -COPY /files/ / + echo "@testing https://alpine.jonasled.de/edge/testing" >> /etc/apk/repositories && \ + apk add --no-cache git jq proot@testing qemu qemu-aarch64 && \ + mkdir /var/run/proot /root/.docker + +COPY --from=rootfs /build /build +COPY --from=kaniko-amd64 /kanikosrc/out /build/amd64/kaniko +COPY --from=kaniko-arm64 /kanikosrc/out /build/arm64/kaniko +COPY --from=manifest-tool /manifest-tool /usr/local/bin/manifest-tool +COPY --from=helper-tool /build-image /usr/local/bin/build-image WORKDIR /app -CMD ["/init"] -EXPOSE 2375 \ No newline at end of file +CMD [ "/usr/local/bin/build-image" ] \ No newline at end of file diff --git a/Readme.md b/Readme.md index a0d7b64690a7eaa226dd113ae476638d09413007..6c5d8f907e8671cb96f37c9cbea78ccaff644853 100644 --- a/Readme.md +++ b/Readme.md @@ -1,3 +1,3 @@ -# Docker Build Container +# Docker Build Container using kaniko -This repos contains the base image, used for all build jobs, building a container image. \ No newline at end of file +Currently only native arch builds are possible \ No newline at end of file diff --git a/files/etc/docker/daemon.json b/files/etc/docker/daemon.json deleted file mode 100644 index b5da9362b99b0b7fd28473485e79b50c4922b6de..0000000000000000000000000000000000000000 --- a/files/etc/docker/daemon.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "features": { - "containerd-snapshotter": true, - "buildkit": true - }, - "hosts": [ - "tcp://0.0.0.0:2375", - "unix:///var/run/docker.sock" - ], - "max-concurrent-downloads": 20, - "max-concurrent-uploads": 20 - } \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/buildcontext/dependencies.d/docker b/files/etc/s6-overlay/s6-rc.d/buildcontext/dependencies.d/docker deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/files/etc/s6-overlay/s6-rc.d/buildcontext/type b/files/etc/s6-overlay/s6-rc.d/buildcontext/type deleted file mode 100644 index 3d92b15f2d56c7753feb51fd035d8c490de86bd7..0000000000000000000000000000000000000000 --- a/files/etc/s6-overlay/s6-rc.d/buildcontext/type +++ /dev/null @@ -1 +0,0 @@ -oneshot \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/buildcontext/up b/files/etc/s6-overlay/s6-rc.d/buildcontext/up deleted file mode 100644 index 825664d95e8e5c36743cdb81261b7bc24882c640..0000000000000000000000000000000000000000 --- a/files/etc/s6-overlay/s6-rc.d/buildcontext/up +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/ash -while ! docker info >/dev/null 2>&1; do - echo "Waiting for Docker to start..." - sleep 1 -done -echo "creating build context" -docker buildx use default \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/buildkit/run b/files/etc/s6-overlay/s6-rc.d/buildkit/run deleted file mode 100644 index 0b6214b0003a8541819a91b9f04a4ab0a8a66377..0000000000000000000000000000000000000000 --- a/files/etc/s6-overlay/s6-rc.d/buildkit/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/ash -buildkitd \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/buildkit/type b/files/etc/s6-overlay/s6-rc.d/buildkit/type deleted file mode 100644 index 1780f9f44efd7a9a5240468e2d3d851ae5b7a471..0000000000000000000000000000000000000000 --- a/files/etc/s6-overlay/s6-rc.d/buildkit/type +++ /dev/null @@ -1 +0,0 @@ -longrun \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/docker/run b/files/etc/s6-overlay/s6-rc.d/docker/run deleted file mode 100644 index 584de9a390f977ec328f67a9e9a909d484439b1c..0000000000000000000000000000000000000000 --- a/files/etc/s6-overlay/s6-rc.d/docker/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/ash -dockerd --tls=false \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/docker/type b/files/etc/s6-overlay/s6-rc.d/docker/type deleted file mode 100644 index 1780f9f44efd7a9a5240468e2d3d851ae5b7a471..0000000000000000000000000000000000000000 --- a/files/etc/s6-overlay/s6-rc.d/docker/type +++ /dev/null @@ -1 +0,0 @@ -longrun \ No newline at end of file diff --git a/files/etc/s6-overlay/s6-rc.d/user/contents.d/buildcontext b/files/etc/s6-overlay/s6-rc.d/user/contents.d/buildcontext deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/files/etc/s6-overlay/s6-rc.d/user/contents.d/docker b/files/etc/s6-overlay/s6-rc.d/user/contents.d/docker deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/files/usr/local/bin/build-image.sh b/files/usr/local/bin/build-image.sh deleted file mode 100755 index 8e920ffbe4b28ff0c8424bb1625c3c22d651763c..0000000000000000000000000000000000000000 --- a/files/usr/local/bin/build-image.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/ash - -if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then - DEFAULT_IMAGE_TAG="latest" -else - DEFAULT_IMAGE_TAG="$CI_COMMIT_REF_SLUG" -fi -create-build-context.sh -wait-for-docker.sh - -IMAGE="${IMAGE_NAME:=$CI_REGISTRY_IMAGE}:${IMAGE_TAG:=$DEFAULT_IMAGE_TAG}" -docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY -echo "Building docker image $IMAGE for ${ARCHITECTURES:="linux/arm64,linux/amd64"}" - -docker buildx build --platform "${ARCHITECTURES:="linux/arm64,linux/amd64"}" --push --provenance false --tag "$IMAGE" . -export IMAGE_HASH=$(docker buildx imagetools inspect "$IMAGE" --format "{{json .Manifest}}" | jq .digest) -echo "Image Hash: $IMAGE_HASH" -echo "$IMAGE_HASH" >> image-hash.txt diff --git a/files/usr/local/bin/create-build-context.sh b/files/usr/local/bin/create-build-context.sh deleted file mode 100755 index 5306567669a9eb9995b5233c1e577ab3c43841f3..0000000000000000000000000000000000000000 --- a/files/usr/local/bin/create-build-context.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/ash -#!/bin/sh - -# Host to check -HOSTNAME="docker" - -# Check if the host "docker" can be resolved -if getent hosts "$HOSTNAME" >/dev/null 2>&1; then - docker context create remote-docker --docker "host=tcp://$HOSTNAME:2375" - docker context use remote-docker - docker buildx use remote-docker - echo "Switched to the remote Docker context." - -else - docker buildx use default -fi diff --git a/files/usr/local/bin/detect-default-image-tag.sh b/files/usr/local/bin/detect-default-image-tag.sh deleted file mode 100755 index 0e1d7bd5d42dc6f7c5a9bb6dffcb94eeb800eed0..0000000000000000000000000000000000000000 --- a/files/usr/local/bin/detect-default-image-tag.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/ash -if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then - echo "latest" -else - echo "$CI_COMMIT_REF_SLUG" -fi diff --git a/files/usr/local/bin/wait-for-docker.sh b/files/usr/local/bin/wait-for-docker.sh deleted file mode 100755 index dabb27a8e75dc63b13906102e543481069de35d0..0000000000000000000000000000000000000000 --- a/files/usr/local/bin/wait-for-docker.sh +++ /dev/null @@ -1,5 +0,0 @@ -while ! docker info >/dev/null 2>&1; do - echo "Waiting for Docker daemon..." - sleep 1 -done -docker buildx use default \ No newline at end of file diff --git a/helper/cmd.go b/helper/cmd.go new file mode 100644 index 0000000000000000000000000000000000000000..a57481e7c4a977fcd2c46446898c631c97e01031 --- /dev/null +++ b/helper/cmd.go @@ -0,0 +1,55 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "os/exec" +) + +func executeCmd(command string) { + cmd := exec.Command("/bin/sh", "-c", command) + + // Get the command's stdout and stderr pipes + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Println("Error:", err) + return + } + stderr, err := cmd.StderrPipe() + if err != nil { + fmt.Println("Error:", err) + return + } + + // Start the command in the background + err = cmd.Start() + if err != nil { + fmt.Println("Error starting command:", err) + return + } + + // Create a scanner to read stdout and stderr + go printOutput(stdout) + go printOutput(stderr) + + // Wait for the command to finish + err = cmd.Wait() + if err != nil { + fmt.Println("Error waiting for command to finish:", err) + os.Exit(1) + return + } + +} + +func printOutput(pipe io.Reader) { + scanner := bufio.NewScanner(pipe) + for scanner.Scan() { + fmt.Println(scanner.Text()) + } + if err := scanner.Err(); err != nil { + fmt.Println("Error reading output:", err) + } +} diff --git a/helper/go.mod b/helper/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..4567926bfa758d28fc0f81def8e965b45a7ac222 --- /dev/null +++ b/helper/go.mod @@ -0,0 +1,5 @@ +module jonasled.dev/infra/docker-build/helper + +go 1.23.4 + +require gopkg.in/yaml.v2 v2.4.0 diff --git a/helper/go.sum b/helper/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..dd0bc19f1fe3e536098c0fd769c96042bf9ebc73 --- /dev/null +++ b/helper/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/helper/helper.go b/helper/helper.go new file mode 100644 index 0000000000000000000000000000000000000000..53a0c9c154078b740917b4e4493f686e86f8c127 --- /dev/null +++ b/helper/helper.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +func getImageTag() string { + imageTag := os.Getenv("CI_COMMIT_REF_SLUG") + if os.Getenv("CI_COMMIT_BRANCH") == os.Getenv("CI_DEFAULT_BRANCH") { + imageTag = "latest" + } + if os.Getenv("IMAGE_TAG") != "" { + imageTag = os.Getenv("IMAGE_TAG") + } + return imageTag +} + +func getImageName() string { + if os.Getenv("IMAGE_NAME") != "" { + return os.Getenv("IMAGE_NAME") + } + return os.Getenv("CI_REGISTRY_IMAGE") +} + +func getDockerfilePath() string { + dockerfilePath := "Dockerfile" + if os.Getenv("DOCKERFILE_PATH") != "" { + dockerfilePath = os.Getenv("DOCKERFILE_PATH") + } + cwd, _ := os.Getwd() + return filepath.Join(cwd, dockerfilePath) +} + +func getArchitectures() []string { + if os.Getenv("ARCHITECTURES") != "" { + return strings.Split(os.Getenv("ARCHITECTURES"), ",") + } + return []string{ + "amd64", + "arm64", + } +} + +func getSingleArchImageName() string { + if os.Getenv("SINGLEARCH_IMAGE_NAME") == "" { + return "single-arch" + } + return os.Getenv("SINGLEARCH_IMAGE_NAME") +} + +func getImageDigest() (string, error) { + filePath := "./image.txt" + content, err := os.ReadFile(filePath) + if err != nil { + return "", fmt.Errorf("failed to read file: %w", err) + } + + err = os.Remove(filePath) + if err != nil { + return "", fmt.Errorf("failed to delete file: %w", err) + } + + return strings.TrimSpace(string(content)), nil +} diff --git a/helper/main.go b/helper/main.go new file mode 100644 index 0000000000000000000000000000000000000000..a2ddd874e8f3dba2540e5f7c0e6f499c557dcc8c --- /dev/null +++ b/helper/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v2" +) + +var manifestConfig ManifestToolConfig + +func main() { + var imageDigest string + for _, arch := range getArchitectures() { + switch arch { + case "amd64": + imageDigest = buildAmd64() + case "arm64": + imageDigest = buildArm64() + default: + fmt.Println("Unknown architecture: ", arch) + continue + } + manifest := ManifestToolManifest{ + Image: imageDigest, + Platform: ManifestToolPlatform{ + OS: "linux", + Architecture: arch, + }, + } + manifestConfig.Manifests = append(manifestConfig.Manifests, manifest) + } + buildManifest() +} + +func buildAmd64() string { + fmt.Println("Building image for amd64") + buildCmd := fmt.Sprintf("proot --bind=\"$(pwd)\" -S /build/amd64 /kaniko/executor --context \"$(pwd)\" --ignore-path=/tmp/ --skip-unused-stages --reproducible --use-new-run --force --dockerfile \"%s\" --destination \"%s/%s:%s-amd64\" --image-name-with-digest-file \"$(pwd)/image.txt\"", + getDockerfilePath(), + getImageName(), + getSingleArchImageName(), + getImageTag()) + fmt.Println("Assembled build command: ", buildCmd) + executeCmd(buildCmd) + imageDigest, err := getImageDigest() + if err != nil { + fmt.Println("Failed to get image digest for AMD64 platform") + os.Exit(1) + } + return imageDigest +} + +func buildArm64() string { + fmt.Println("Building image for arm64") + buildCmd := fmt.Sprintf("proot -q qemu-aarch64 --bind=\"$(pwd)\" -S /build/arm64 /kaniko/executor --context \"$(pwd)\" --ignore-path=/tmp/ --skip-unused-stages --reproducible --use-new-run --force --dockerfile \"%s\" --destination \"%s/%s:%s-arm64\" --image-name-with-digest-file \"$(pwd)/image.txt\"", + getDockerfilePath(), + getImageName(), + getSingleArchImageName(), + getImageTag()) + fmt.Println("Assembled build command: ", buildCmd) + executeCmd(buildCmd) + imageDigest, err := getImageDigest() + if err != nil { + fmt.Println("Failed to get image digest for AMD64 platform") + os.Exit(1) + } + return imageDigest +} + +func buildManifest() { + executeCmd("mkdir -p ~/.docker") + executeCmd("echo \"{\\\"auths\\\":{\\\"${CI_REGISTRY}\\\": {\\\"auth\\\": \\\"$(printf \"%s:%s\" \"${CI_REGISTRY_USER}\" \"${CI_REGISTRY_PASSWORD}\" | base64 | tr -d '\\n')\\\"}}}\" > ~/.docker/config.json") + + fmt.Println("Writing Image manifest config") + manifestConfig.Image = getImageName() + ":" + getImageTag() + yamlData, err := yaml.Marshal(&manifestConfig) + if err != nil { + fmt.Println("Failed converting struct to yaml: ", err.Error()) + os.Exit(1) + } + err = os.WriteFile("./imagemanifest.yaml", yamlData, 0644) + if err != nil { + fmt.Println("Failed writing manifest yaml: ", err.Error()) + os.Exit(1) + } + fmt.Println("Generating and uploading manifest") + executeCmd("manifest-tool push from-spec ./imagemanifest.yaml") +} diff --git a/helper/manifestStruct.go b/helper/manifestStruct.go new file mode 100644 index 0000000000000000000000000000000000000000..68ed7cb858a3ee66f76c550b765150dc39a891d3 --- /dev/null +++ b/helper/manifestStruct.go @@ -0,0 +1,17 @@ +package main + +type ManifestToolPlatform struct { + Architecture string `yaml:"architecture"` + OS string `yaml:"os"` +} + +type ManifestToolManifest struct { + Image string `yaml:"image"` + Platform ManifestToolPlatform `yaml:"platform"` +} + +type ManifestToolConfig struct { + Image string `yaml:"image"` + Tags []string `yaml:"tags"` + Manifests []ManifestToolManifest `yaml:"manifests"` +}