k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/images/image-util.sh (about)

     1  #!/usr/bin/env bash
     2  
     3  # Copyright 2017 The Kubernetes Authors.
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #     http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  
    17  set -o errexit
    18  set -o nounset
    19  set -o pipefail
    20  
    21  TASK=${1}
    22  WHAT=${2}
    23  
    24  # docker buildx command is still experimental as of Docker 19.03.0
    25  export DOCKER_CLI_EXPERIMENTAL="enabled"
    26  
    27  # Connecting to a Remote Docker requires certificates for authentication, which can be found
    28  # at this path. By default, they can be found in the ${HOME} folder. We're expecting to find
    29  # here ".docker-${os_version}" folders which contains the necessary certificates.
    30  DOCKER_CERT_BASE_PATH="${DOCKER_CERT_BASE_PATH:-${HOME}}"
    31  
    32  KUBE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
    33  source "${KUBE_ROOT}/hack/lib/logging.sh"
    34  source "${KUBE_ROOT}/hack/lib/util.sh"
    35  
    36  # Mapping of go ARCH to actual architectures shipped part of multiarch/qemu-user-static project
    37  declare -A QEMUARCHS=( ["amd64"]="x86_64" ["arm"]="arm" ["arm64"]="aarch64" ["ppc64le"]="ppc64le" ["s390x"]="s390x" )
    38  
    39  # NOTE(claudiub): In the test image build jobs, this script is not being run in a git repository,
    40  # which would cause git log to fail. Instead, we can use the GIT_COMMIT_ID set in cloudbuild.yaml.
    41  GIT_COMMIT_ID=$(git log -1 --format=%h || echo "${GIT_COMMIT_ID}")
    42  windows_os_versions=(1809 ltsc2022)
    43  declare -A WINDOWS_OS_VERSIONS_MAP
    44  
    45  initWindowsOsVersions() {
    46    for os_version in "${windows_os_versions[@]}"; do
    47      img_base="mcr.microsoft.com/windows/nanoserver:${os_version}"
    48      # we use awk to also trim the quotes around the OS version string.
    49      full_version=$(docker manifest inspect "${img_base}" | grep "os.version" | head -n 1 | awk -F\" '{print $4}') || true
    50      WINDOWS_OS_VERSIONS_MAP["${os_version}"]="${full_version}"
    51    done
    52  }
    53  
    54  initWindowsOsVersions
    55  
    56  # Returns list of all supported architectures from BASEIMAGE file
    57  listOsArchs() {
    58    local image=${1}
    59    cut -d "=" -f 1 "${image}"/BASEIMAGE
    60  }
    61  
    62  splitOsArch() {
    63    local image=${1}
    64    local os_arch=${2}
    65  
    66    if [[ $os_arch =~ .*/.*/.* ]]; then
    67      # for Windows, we have to support both LTS and SAC channels, so we're building multiple Windows images.
    68      # the format for this case is: OS/ARCH/OS_VERSION.
    69      os_name=$(echo "$os_arch" | cut -d "/" -f 1)
    70      arch=$(echo "$os_arch" | cut -d "/" -f 2)
    71      os_version=$(echo "$os_arch" | cut -d "/" -f 3)
    72      suffix="$os_name-$arch-$os_version"
    73    elif [[ $os_arch =~ .*/.* ]]; then
    74      os_name=$(echo "$os_arch" | cut -d "/" -f 1)
    75      arch=$(echo "$os_arch" | cut -d "/" -f 2)
    76      os_version=""
    77      suffix="$os_name-$arch"
    78    else
    79      echo "The BASEIMAGE file for the ${image} image is not properly formatted. Expected entries to start with 'os/arch', found '${os_arch}' instead."
    80      exit 1
    81    fi
    82  }
    83  
    84  # Returns baseimage need to used in Dockerfile for any given architecture
    85  getBaseImage() {
    86    os_arch=$1
    87    grep "${os_arch}=" BASEIMAGE | cut -d= -f2
    88  }
    89  
    90  # This function will build test image for all the architectures
    91  # mentioned in BASEIMAGE file. In the absence of BASEIMAGE file,
    92  # it will build for all the supported arch list - amd64, arm,
    93  # arm64, ppc64le, s390x
    94  build() {
    95    local image=${1}
    96    local img_folder=${1}
    97    local output_type=${2}
    98    docker_version_check
    99  
   100    if [[ -f "${img_folder}/BASEIMAGE" ]]; then
   101      os_archs=$(listOsArchs "$image")
   102    else
   103      # prepend linux/ to the QEMUARCHS items.
   104      os_archs=$(printf 'linux/%s\n' "${!QEMUARCHS[@]}")
   105    fi
   106  
   107    # image tag
   108    TAG=$(<"${img_folder}/VERSION")
   109  
   110    alias_name="$(cat "${img_folder}/ALIAS" 2>/dev/null || true)"
   111    if [[ -n "${alias_name}" ]]; then
   112      echo "Found an alias for '${image}'. Building / tagging image as '${alias_name}.'"
   113      image="${alias_name}"
   114    fi
   115  
   116    kube::util::ensure-gnu-sed
   117    kube::util::ensure-docker-buildx
   118  
   119    for os_arch in ${os_archs}; do
   120      splitOsArch "${image}" "${os_arch}"
   121      if [[ "${os_name}" == "windows" && "${output_type}" == "docker" ]]; then
   122        echo "Cannot build the image '${image}' for ${os_arch}. Built Windows container images need to be pushed to a registry."
   123        continue
   124      fi
   125  
   126      echo "Building image for ${image} OS/ARCH: ${os_arch}..."
   127  
   128      # Create a temporary directory for every architecture and copy the image content
   129      # and build the image from temporary directory
   130      mkdir -p "${KUBE_ROOT}"/_tmp
   131      temp_dir=$(mktemp -d "${KUBE_ROOT}"/_tmp/test-images-build.XXXXXX)
   132      kube::util::trap_add "rm -rf ${temp_dir}" EXIT
   133  
   134      cp -r "${img_folder}"/* "${temp_dir}"
   135      if [[ -f ${img_folder}/Makefile ]]; then
   136        # make bin will take care of all the prerequisites needed
   137        # for building the docker image
   138        make -C "${img_folder}" bin OS="${os_name}" ARCH="${arch}" TARGET="${temp_dir}"
   139      fi
   140      pushd "${temp_dir}"
   141  
   142      # NOTE(claudiub): Some Windows images might require their own Dockerfile
   143      # while simpler ones will not. If we're building for Windows, check if
   144      # "Dockerfile_windows" exists or not.
   145      dockerfile_name="Dockerfile"
   146      if [[ "$os_name" = "windows" && -f "Dockerfile_windows" ]]; then
   147        dockerfile_name="Dockerfile_windows"
   148      fi
   149  
   150      base_image=""
   151      if [[ -f BASEIMAGE ]]; then
   152        base_image=$(getBaseImage "${os_arch}" | "${SED}" "s|REGISTRY|${REGISTRY}|g")
   153        "${SED}" -i "s|BASEARCH|${arch}|g" $dockerfile_name
   154      fi
   155  
   156      # Only the cross-build on x86 is guaranteed by far, other arches like aarch64 doesn't support cross-build
   157      # thus, there is no need to tackle a disability feature on those platforms, and also help to prevent from
   158      # ending up a wrong image tag on non-amd64 platforms.
   159      build_arch=$(uname -m)
   160      if [[ ${build_arch} = 'x86_64' ]]; then
   161          # copy the qemu-*-static binary to docker image to build the multi architecture image on x86 platform
   162          if grep -q 'CROSS_BUILD_' Dockerfile; then
   163            if [[ "${arch}" = 'amd64' ]]; then
   164              "${SED}" -i '/CROSS_BUILD_/d' Dockerfile
   165            else
   166              "${SED}" -i "s|QEMUARCH|${QEMUARCHS[$arch]}|g" Dockerfile
   167              # Register qemu-*-static for all supported processors except the current one
   168              echo 'Registering qemu-*-static binaries in the kernel'
   169              local sudo=""
   170              if [[ $(id -u) -ne 0 ]]; then
   171  	            sudo="sudo"
   172              fi
   173              ${sudo} docker run --rm --privileged tonistiigi/binfmt:latest --install all
   174              curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/"${QEMUVERSION}"/x86_64_qemu-"${QEMUARCHS[$arch]}"-static.tar.gz | tar -xz -C "${temp_dir}"
   175              # Ensure we don't get surprised by umask settings
   176              chmod 0755 "${temp_dir}/qemu-${QEMUARCHS[$arch]}-static"
   177              "${SED}" -i 's/CROSS_BUILD_//g' Dockerfile
   178            fi
   179          fi
   180      elif [[ "${QEMUARCHS[$arch]}" != "${build_arch}" ]]; then
   181  		echo "skip cross-build $arch on non-supported platform ${build_arch}."
   182  		popd
   183          continue
   184      else
   185          "${SED}" -i '/CROSS_BUILD_/d' Dockerfile
   186      fi
   187  
   188      # `--provenance=false --sbom=false` is set to avoid creating a manifest list: https://github.com/kubernetes/kubernetes/issues/123266
   189      docker buildx build --progress=plain --no-cache --pull --output=type="${output_type}" --platform "${os_name}/${arch}" --provenance=false --sbom=false \
   190          --build-arg BASEIMAGE="${base_image}" --build-arg REGISTRY="${REGISTRY}" --build-arg OS_VERSION="${os_version}" \
   191          -t "${REGISTRY}/${image}:${TAG}-${suffix}" -f "${dockerfile_name}" \
   192  	--label "image_version=${TAG}" --label "commit_id=${GIT_COMMIT_ID}" \
   193  	--label "git_url=https://github.com/kubernetes/kubernetes/tree/${GIT_COMMIT_ID}/test/images/${img_folder}" .
   194  
   195      popd
   196    done
   197  }
   198  
   199  docker_version_check() {
   200    # docker manifest annotate --os-version has been introduced in 20.10.0,
   201    # so we need to make sure we have it.
   202    docker_version=$(docker version --format '{{.Client.Version}}' | cut -d"-" -f1)
   203    if [[ ${docker_version} != 20.10.0 && ${docker_version} < 20.10.0 ]]; then
   204      echo "Minimum docker version 20.10.0 is required for annotating the OS Version in the manifest list images: ${docker_version}]"
   205      exit 1
   206    fi
   207  }
   208  
   209  # This function will push the docker images
   210  push() {
   211    local image=${1}
   212    docker_version_check
   213  
   214    TAG=$(<"${image}"/VERSION)
   215    if [[ -f ${image}/BASEIMAGE ]]; then
   216      os_archs=$(listOsArchs "$image")
   217    else
   218      # prepend linux/ to the QEMUARCHS items.
   219      os_archs=$(printf 'linux/%s\n' "${!QEMUARCHS[@]}")
   220    fi
   221  
   222    pushd "${image}"
   223    alias_name="$(cat ALIAS 2>/dev/null || true)"
   224    if [[ -n "${alias_name}" ]]; then
   225      echo "Found an alias for '${image}'. Pushing image as '${alias_name}.'"
   226      image="${alias_name}"
   227    fi
   228  
   229    kube::util::ensure-gnu-sed
   230  
   231    # reset manifest list; needed in case multiple images are being built / pushed.
   232    manifest=()
   233    # Make os_archs list into image manifest. Eg: 'linux/amd64 linux/ppc64le' to '${REGISTRY}/${image}:${TAG}-linux-amd64 ${REGISTRY}/${image}:${TAG}-linux-ppc64le'
   234    while IFS='' read -r line; do manifest+=("$line"); done < <(echo "$os_archs" | "${SED}" "s~\/~-~g" | "${SED}" -e "s~[^ ]*~$REGISTRY\/$image:$TAG\-&~g")
   235    docker manifest create --amend "${REGISTRY}/${image}:${TAG}" "${manifest[@]}"
   236  
   237    for os_arch in ${os_archs}; do
   238      splitOsArch "${image}" "${os_arch}"
   239  
   240      # For Windows images, we also need to include the "os.version" in the manifest list, so the Windows node
   241      # can pull the proper image it needs.
   242      if [[ "$os_name" = "windows" ]]; then
   243        full_version="${WINDOWS_OS_VERSIONS_MAP[$os_version]}"
   244        docker manifest annotate --os "${os_name}" --arch "${arch}" --os-version "${full_version}" "${REGISTRY}/${image}:${TAG}" "${REGISTRY}/${image}:${TAG}-${suffix}"
   245      else
   246        docker manifest annotate --os "${os_name}" --arch "${arch}" "${REGISTRY}/${image}:${TAG}" "${REGISTRY}/${image}:${TAG}-${suffix}"
   247      fi
   248    done
   249    popd
   250    docker manifest push --purge "${REGISTRY}/${image}:${TAG}"
   251  }
   252  
   253  # This function is for building AND pushing images. Useful if ${WHAT} is "all-conformance".
   254  # This will allow images to be pushed immediately after they've been built.
   255  build_and_push() {
   256    local image=${1}
   257    build "${image}" "registry"
   258    push "${image}"
   259  }
   260  
   261  # This function is for building the go code
   262  bin() {
   263    local arch_prefix=""
   264    if [[ "${ARCH:-}" == "arm" ]]; then
   265      arch_prefix="GOARM=${GOARM:-7}"
   266    fi
   267    for SRC in "$@";
   268    do
   269    docker run --rm -v "${TARGET}:${TARGET}:Z" -v "${KUBE_ROOT}":/go/src/k8s.io/kubernetes:Z \
   270          golang:"${GOLANG_VERSION}" \
   271          /bin/bash -c "\
   272                  cd /go/src/k8s.io/kubernetes/test/images/${SRC_DIR} && \
   273                  CGO_ENABLED=0 ${arch_prefix} GOOS=${OS} GOARCH=${ARCH} go build -a -installsuffix cgo --ldflags \"-w ${LD_FLAGS:-}\" -o ${TARGET}/${SRC} ./$(dirname "${SRC}")"
   274    done
   275  }
   276  
   277  shift
   278  
   279  if [[ "${WHAT}" == "all-conformance" ]]; then
   280    # NOTE(claudiub): Building *ALL* the images under the kubernetes/test/images folder takes an extremely
   281    # long time (especially some images), and some images are rarely used and rarely updated, so there's
   282    # no point in rebuilding all of them every time. This will only build the Conformance-related images.
   283    # Discussed during Conformance Office Hours Meeting (2019.12.17):
   284    # https://docs.google.com/document/d/1W31nXh9RYAb_VaYkwuPLd1hFxuRX3iU0DmaQ4lkCsX8/edit#heading=h.l87lu17xm9bh
   285    shift
   286    conformance_images=("busybox" "agnhost" "jessie-dnsutils" "kitten" "nautilus" "nonewprivs" "resource-consumer" "sample-apiserver")
   287    for image in "${conformance_images[@]}"; do
   288      "${TASK}" "${image}" "$@"
   289    done
   290  else
   291    "${TASK}" "$@"
   292  fi