# Copyright 2018 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# kind node base image
#
# For systemd + docker configuration used below, see the following references:
# https://systemd.io/CONTAINER_INTERFACE/

# start from debian slim, this image is reasonably small as a starting point
# for a kubernetes node image, it doesn't contain much (anything?) we don't need
# this stage will install basic files and packages
ARG BASE_IMAGE=debian:trixie-slim
FROM $BASE_IMAGE AS base

# copy in static files
# all scripts and directories are 0755 (rwx r-x r-x)
# all non-scripts are 0644 (rw- r-- r--)
COPY --chmod=0755 files/usr/local/bin/* /usr/local/bin/

COPY --chmod=0644 files/kind/ /kind/
# COPY only applies to files, not the directory itself, so the permissions are
# fixed in RUN below with a chmod.
COPY --chmod=0755 files/kind/bin/ /kind/bin/

COPY --chmod=0644 files/LICENSES/* /LICENSES/*
COPY --chmod=0644 files/etc/* /etc/
COPY --chmod=0644 files/etc/containerd/* /etc/containerd/
COPY --chmod=0644 files/etc/default/* /etc/default/
COPY --chmod=0644 files/etc/sysctl.d/* /etc/sysctl.d/
COPY --chmod=0644 files/etc/systemd/system/* /etc/systemd/system/
COPY --chmod=0644 files/etc/systemd/system/kubelet.service.d/* /etc/systemd/system/kubelet.service.d/

# Install dependencies, first from apt, then from release tarballs.
# NOTE: we use one RUN to minimize layers.
#
# The base image already has a basic userspace + apt but we need to install more packages.
# Packages installed are broken down into (each on a line):
# - packages needed to run services (systemd)
# - packages needed for kubernetes components
# - packages needed for networked backed storage with kubernetes
# - packages used by kubernetes storage e2e tests
# - packages needed by the container runtime
# - misc packages kind uses itself
# - packages that provide semi-core kubernetes functionality
# After installing packages we cleanup by:
# - removing unwanted systemd services
# - disabling kmsg in journald (these log entries would be confusing)
#
# Then we install containerd from our nightly build infrastructure, as this
# build for multiple architectures and allows us to upgrade to patched releases
# more quickly.
#
# Next we download and extract crictl and CNI plugin binaries from upstream.
#
# Next we ensure the /etc/kubernetes/manifests directory exists. Normally
# a kubeadm debian / rpm package would ensure that this exists but we install
# freshly built binaries directly when we build the node image.
#
# Finally we adjust tempfiles cleanup to be 1 minute after "boot" instead of 15m
# This is plenty after we've done initial setup for a node, but before we are
# likely to try to export logs etc.
RUN chmod 755 /kind /kind/bin && \
    echo "Installing Packages ..." \
    && DEBIAN_FRONTEND=noninteractive clean-install \
      systemd \
      conntrack iptables nftables iproute2 ethtool util-linux mount kmod \
      libseccomp2 pigz fuse-overlayfs \
      nfs-common open-iscsi \
      e2fsprogs \
      bash ca-certificates curl jq procps \
    && find /lib/systemd/system/sysinit.target.wants/ -name "systemd-tmpfiles-setup.service" -delete \
    && rm -f /lib/systemd/system/multi-user.target.wants/* \
    && rm -f /etc/systemd/system/*.wants/* \
    && rm -f /lib/systemd/system/local-fs.target.wants/* \
    && rm -f /lib/systemd/system/sockets.target.wants/*udev* \
    && rm -f /lib/systemd/system/sockets.target.wants/*initctl* \
    && rm -f /lib/systemd/system/basic.target.wants/* \
    && echo "ReadKMsg=no" >> /etc/systemd/journald.conf \
    && ln -s /usr/lib/systemd/systemd /sbin/init

# NOTE: systemd-binfmt.service will register things into binfmt_misc which is kernel-global
RUN echo "Enabling / Disabling services ... " \
    && systemctl enable kubelet.service \
    && systemctl enable containerd.service \
    && systemctl enable undo-mount-hacks.service \
    && systemctl mask systemd-binfmt.service

RUN echo "Ensuring /etc/kubernetes/manifests" \
    && mkdir -p /etc/kubernetes/manifests

# shared stage to setup go version for building binaries
# NOTE we will be cross-compiling for performance reasons
# This is also why we start again FROM the same base image but a different
# platform and only the files needed for building
# We will copy the built binaries from later stages to the final stage(s)
FROM --platform=$BUILDPLATFORM $BASE_IMAGE AS go-build
COPY --chmod=0755 files/usr/local/bin/* /usr/local/bin/
COPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/
COPY --chmod=0755 scripts/target-cc /usr/local/bin/
# tools needed at build-time only
# first ensure we can install packages for both architectures
RUN dpkg --add-architecture arm64 && dpkg --add-architecture amd64 \
    && clean-install bash ca-certificates curl git make pkg-config \
    build-essential crossbuild-essential-amd64 crossbuild-essential-arm64 \
    libseccomp-dev:amd64 libseccomp-dev:arm64
# set by makefile to .go-version
ARG GO_VERSION
RUN eval "$(gimme "${GO_VERSION}")" \
    && export GOTOOLCHAIN="go${GO_VERSION}" \
    && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest


# stage for building containerd
FROM go-build AS build-containerd
ARG TARGETARCH GO_VERSION
ARG CONTAINERD_VERSION="v2.3.1"
ARG CONTAINERD_CLONE_URL="https://github.com/containerd/containerd"
# we don't build with optional snapshotters, we never select any of these
# they're not ideal inside kind anyhow, and we save some disk space
ARG BUILDTAGS="no_aufs no_zfs no_btrfs no_devmapper"
RUN git clone --filter=tree:0 "${CONTAINERD_CLONE_URL}" /containerd \
    && cd /containerd \
    && git checkout "${CONTAINERD_VERSION}" \
    && eval "$(gimme "${GO_VERSION}")" \
    && export GOTOOLCHAIN="go${GO_VERSION}" \
    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \
    && make bin/ctr bin/containerd bin/containerd-shim-runc-v2 \
    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES \
        ./cmd/ctr ./cmd/containerd ./cmd/containerd-shim-runc-v2

# stage for building runc
FROM go-build AS build-runc
ARG TARGETARCH GO_VERSION
ARG RUNC_VERSION="v1.4.2"
ARG RUNC_CLONE_URL="https://github.com/opencontainers/runc"
RUN git clone --filter=tree:0 "${RUNC_CLONE_URL}" /runc \
    && cd /runc \
    && git checkout "${RUNC_VERSION}" \
    && eval "$(gimme "${GO_VERSION}")" \
    && export GOTOOLCHAIN="go${GO_VERSION}" \
    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \
    && make runc \
    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES .

# stage for building crictl
FROM go-build AS build-crictl
ARG TARGETARCH GO_VERSION
ARG CRI_TOOLS_CLONE_URL="https://github.com/kubernetes-sigs/cri-tools"
ARG CRICTL_VERSION="v1.36.0"
RUN git clone --filter=tree:0 "${CRI_TOOLS_CLONE_URL}" /cri-tools \
    && cd /cri-tools \
    && git checkout "${CRICTL_VERSION}" \
    && eval "$(gimme "${GO_VERSION}")" \
    && export GOTOOLCHAIN="go${GO_VERSION}" \
    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=0 \
    && make BUILD_BIN_PATH=./build crictl \
    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/crictl

# stage for building cni-plugins
FROM go-build AS build-cni
ARG TARGETARCH GO_VERSION
ARG CNI_PLUGINS_VERSION="v1.9.1"
ARG CNI_PLUGINS_CLONE_URL="https://github.com/containernetworking/plugins"
RUN git clone --filter=tree:0 "${CNI_PLUGINS_CLONE_URL}" /cni-plugins \
    && cd /cni-plugins \
    && git checkout "${CNI_PLUGINS_VERSION}" \
    && eval "$(gimme "${GO_VERSION}")" \
    && export GOTOOLCHAIN="go${GO_VERSION}" \
    && mkdir ./bin \
    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=0 \
    && go build -o ./bin/host-local -mod=vendor ./plugins/ipam/host-local \
    && go build -o ./bin/loopback -mod=vendor ./plugins/main/loopback \
    && go build -o ./bin/ptp -mod=vendor ./plugins/main/ptp \
    && go build -o ./bin/portmap -mod=vendor ./plugins/meta/portmap \
    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES \
        ./plugins/ipam/host-local \
        ./plugins/main/loopback ./plugins/main/ptp \
        ./plugins/meta/portmap

# stage for building containerd-fuse-overlayfs
FROM go-build AS build-fuse-overlayfs
ARG TARGETARCH GO_VERSION
ARG CONTAINERD_FUSE_OVERLAYFS_VERSION="v2.1.7"
ARG CONTAINERD_FUSE_OVERLAYFS_CLONE_URL="https://github.com/containerd/fuse-overlayfs-snapshotter"
RUN git clone --filter=tree:0 "${CONTAINERD_FUSE_OVERLAYFS_CLONE_URL}" /fuse-overlayfs-snapshotter \
    && cd /fuse-overlayfs-snapshotter \
    && git checkout "${CONTAINERD_FUSE_OVERLAYFS_VERSION}" \
    && eval "$(gimme "${GO_VERSION}")" \
    && export GOTOOLCHAIN="go${GO_VERSION}" \
    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \
    && make bin/containerd-fuse-overlayfs-grpc \
    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/containerd-fuse-overlayfs-grpc


# build final image layout from other stages
FROM base AS build
# copy over containerd build and install
COPY --from=build-containerd /containerd/bin/containerd /usr/local/bin/
COPY --from=build-containerd /containerd/bin/ctr /usr/local/bin/
COPY --from=build-containerd /containerd/bin/containerd-shim-runc-v2 /usr/local/bin/
RUN ctr oci spec \
        | jq '.hooks.createContainer[.hooks.createContainer| length] |= . + {"path": "/kind/bin/mount-product-files.sh"}' \
        | jq 'del(.process.rlimits)' \
        > /etc/containerd/cri-base.json \
    && containerd --version
COPY --from=build-containerd /_LICENSES/* /LICENSES/
# copy over runc build and install
COPY --from=build-runc /runc/runc /usr/local/sbin/runc
RUN runc --version
COPY --from=build-runc /_LICENSES/* /LICENSES/
# copy over crictl build and install
COPY --from=build-crictl /cri-tools/build/crictl /usr/local/bin/
COPY --from=build-crictl /_LICENSES/* /LICENSES/
# copy over CNI plugins build and install
RUN  mkdir -p /opt/cni/bin
COPY --from=build-cni /cni-plugins/bin/host-local /opt/cni/bin/
COPY --from=build-cni /cni-plugins/bin/loopback /opt/cni/bin/
COPY --from=build-cni /cni-plugins/bin/ptp /opt/cni/bin/
COPY --from=build-cni /cni-plugins/bin/portmap /opt/cni/bin/
COPY --from=build-cni /_LICENSES/* /LICENSES/
# copy over containerd-fuse-overlayfs and install
COPY --from=build-fuse-overlayfs /fuse-overlayfs-snapshotter/bin/containerd-fuse-overlayfs-grpc /usr/local/bin/
COPY --from=build-fuse-overlayfs /_LICENSES/* /LICENSES/

# squash down to one compressed layer, without any lingering whiteout files etc
FROM scratch
COPY --from=build / /
# add metadata, must be done after the squashing
# first tell systemd that it is in docker (it will check for the container env)
# https://systemd.io/CONTAINER_INTERFACE/
ENV container=docker
# systemd exits on SIGRTMIN+3, not SIGTERM (which re-executes it)
# https://bugzilla.redhat.com/show_bug.cgi?id=1201657
STOPSIGNAL SIGRTMIN+3
# NOTE: this is *only* for documentation, the entrypoint is overridden later
ENTRYPOINT [ "/usr/local/bin/entrypoint", "/sbin/init" ]
