
This check is too restrictive if building into multiple _output targets, such as the way anago produces releases When branching we build essentially 2 copies of the same thing (GKE requirement) the second build will always fail because the docker image is the same.
714 lines
26 KiB
Bash
Executable File
714 lines
26 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Copyright 2014 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.
|
|
|
|
# Common utilities, variables and checks for all build scripts.
|
|
set -o errexit
|
|
set -o nounset
|
|
set -o pipefail
|
|
|
|
DOCKER_OPTS=${DOCKER_OPTS:-""}
|
|
DOCKER=(docker ${DOCKER_OPTS})
|
|
DOCKER_HOST=${DOCKER_HOST:-""}
|
|
DOCKER_MACHINE_NAME=${DOCKER_MACHINE_NAME:-"kube-dev"}
|
|
readonly DOCKER_MACHINE_DRIVER=${DOCKER_MACHINE_DRIVER:-"virtualbox --virtualbox-memory 4096 --virtualbox-cpu-count -1"}
|
|
|
|
# This will canonicalize the path
|
|
KUBE_ROOT=$(cd $(dirname "${BASH_SOURCE}")/.. && pwd -P)
|
|
|
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
|
|
|
# Set KUBE_BUILD_PPC64LE to y to build for ppc64le in addition to other
|
|
# platforms.
|
|
# TODO(IBM): remove KUBE_BUILD_PPC64LE and reenable ppc64le compilation by
|
|
# default when
|
|
# https://github.com/kubernetes/kubernetes/issues/30384 and
|
|
# https://github.com/kubernetes/kubernetes/issues/25886 are fixed.
|
|
# The majority of the logic is in hack/lib/golang.sh.
|
|
readonly KUBE_BUILD_PPC64LE="${KUBE_BUILD_PPC64LE:-n}"
|
|
|
|
# Constants
|
|
readonly KUBE_BUILD_IMAGE_REPO=kube-build
|
|
readonly KUBE_BUILD_IMAGE_CROSS_TAG="$(cat ${KUBE_ROOT}/build-tools/build-image/cross/VERSION)"
|
|
|
|
# This version number is used to cause everyone to rebuild their data containers
|
|
# and build image. This is especially useful for automated build systems like
|
|
# Jenkins.
|
|
#
|
|
# Increment/change this number if you change the build image (anything under
|
|
# build-tools/build-image) or change the set of volumes in the data container.
|
|
readonly KUBE_BUILD_IMAGE_VERSION_BASE="$(cat ${KUBE_ROOT}/build-tools/build-image/VERSION)"
|
|
readonly KUBE_BUILD_IMAGE_VERSION="${KUBE_BUILD_IMAGE_VERSION_BASE}-${KUBE_BUILD_IMAGE_CROSS_TAG}"
|
|
|
|
# Here we map the output directories across both the local and remote _output
|
|
# directories:
|
|
#
|
|
# *_OUTPUT_ROOT - the base of all output in that environment.
|
|
# *_OUTPUT_SUBPATH - location where golang stuff is built/cached. Also
|
|
# persisted across docker runs with a volume mount.
|
|
# *_OUTPUT_BINPATH - location where final binaries are placed. If the remote
|
|
# is really remote, this is the stuff that has to be copied
|
|
# back.
|
|
# OUT_DIR can come in from the Makefile, so honor it.
|
|
readonly LOCAL_OUTPUT_ROOT="${KUBE_ROOT}/${OUT_DIR:-_output}"
|
|
readonly LOCAL_OUTPUT_SUBPATH="${LOCAL_OUTPUT_ROOT}/dockerized"
|
|
readonly LOCAL_OUTPUT_BINPATH="${LOCAL_OUTPUT_SUBPATH}/bin"
|
|
readonly LOCAL_OUTPUT_GOPATH="${LOCAL_OUTPUT_SUBPATH}/go"
|
|
readonly LOCAL_OUTPUT_IMAGE_STAGING="${LOCAL_OUTPUT_ROOT}/images"
|
|
|
|
# This is a symlink to binaries for "this platform" (e.g. build tools).
|
|
readonly THIS_PLATFORM_BIN="${LOCAL_OUTPUT_ROOT}/bin"
|
|
|
|
readonly REMOTE_ROOT="/go/src/${KUBE_GO_PACKAGE}"
|
|
readonly REMOTE_OUTPUT_ROOT="${REMOTE_ROOT}/_output"
|
|
readonly REMOTE_OUTPUT_SUBPATH="${REMOTE_OUTPUT_ROOT}/dockerized"
|
|
readonly REMOTE_OUTPUT_BINPATH="${REMOTE_OUTPUT_SUBPATH}/bin"
|
|
readonly REMOTE_OUTPUT_GOPATH="${REMOTE_OUTPUT_SUBPATH}/go"
|
|
|
|
# This is the port on the workstation host to expose RSYNC on. Set this if you
|
|
# are doing something fancy with ssh tunneling.
|
|
readonly KUBE_RSYNC_PORT="${KUBE_RSYNC_PORT:-}"
|
|
|
|
# This is the port that rsync is running on *inside* the container. This may be
|
|
# mapped to KUBE_RSYNC_PORT via docker networking.
|
|
readonly KUBE_CONTAINER_RSYNC_PORT=8730
|
|
|
|
# Get the set of master binaries that run in Docker (on Linux)
|
|
# Entry format is "<name-of-binary>,<base-image>".
|
|
# Binaries are placed in /usr/local/bin inside the image.
|
|
#
|
|
# $1 - server architecture
|
|
kube::build::get_docker_wrapped_binaries() {
|
|
case $1 in
|
|
"amd64")
|
|
local targets=(
|
|
kube-apiserver,busybox
|
|
kube-controller-manager,busybox
|
|
kube-scheduler,busybox
|
|
kube-proxy,gcr.io/google_containers/debian-iptables-amd64:v4
|
|
);;
|
|
"arm")
|
|
local targets=(
|
|
kube-apiserver,armel/busybox
|
|
kube-controller-manager,armel/busybox
|
|
kube-scheduler,armel/busybox
|
|
kube-proxy,gcr.io/google_containers/debian-iptables-arm:v4
|
|
);;
|
|
"arm64")
|
|
local targets=(
|
|
kube-apiserver,aarch64/busybox
|
|
kube-controller-manager,aarch64/busybox
|
|
kube-scheduler,aarch64/busybox
|
|
kube-proxy,gcr.io/google_containers/debian-iptables-arm64:v4
|
|
);;
|
|
"ppc64le")
|
|
local targets=(
|
|
kube-apiserver,ppc64le/busybox
|
|
kube-controller-manager,ppc64le/busybox
|
|
kube-scheduler,ppc64le/busybox
|
|
kube-proxy,gcr.io/google_containers/debian-iptables-ppc64le:v4
|
|
);;
|
|
esac
|
|
|
|
echo "${targets[@]}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Basic setup functions
|
|
|
|
# Verify that the right utilities and such are installed for building Kube. Set
|
|
# up some dynamic constants.
|
|
#
|
|
# Vars set:
|
|
# KUBE_ROOT_HASH
|
|
# KUBE_BUILD_IMAGE_TAG_BASE
|
|
# KUBE_BUILD_IMAGE_TAG
|
|
# KUBE_BUILD_IMAGE
|
|
# KUBE_BUILD_CONTAINER_NAME_BASE
|
|
# KUBE_BUILD_CONTAINER_NAME
|
|
# KUBE_DATA_CONTAINER_NAME_BASE
|
|
# KUBE_DATA_CONTAINER_NAME
|
|
# KUBE_RSYNC_CONTAINER_NAME_BASE
|
|
# KUBE_RSYNC_CONTAINER_NAME
|
|
# DOCKER_MOUNT_ARGS
|
|
# LOCAL_OUTPUT_BUILD_CONTEXT
|
|
function kube::build::verify_prereqs() {
|
|
kube::log::status "Verifying Prerequisites...."
|
|
kube::build::ensure_tar || return 1
|
|
kube::build::ensure_rsync || return 1
|
|
kube::build::ensure_docker_in_path || return 1
|
|
if kube::build::is_osx; then
|
|
kube::build::docker_available_on_osx || return 1
|
|
fi
|
|
kube::build::ensure_docker_daemon_connectivity || return 1
|
|
|
|
if (( ${KUBE_VERBOSE} > 6 )); then
|
|
kube::log::status "Docker Version:"
|
|
"${DOCKER[@]}" version | kube::log::info_from_stdin
|
|
fi
|
|
|
|
KUBE_ROOT_HASH=$(kube::build::short_hash "${HOSTNAME:-}:${KUBE_ROOT}")
|
|
KUBE_BUILD_IMAGE_TAG_BASE="build-${KUBE_ROOT_HASH}"
|
|
KUBE_BUILD_IMAGE_TAG="${KUBE_BUILD_IMAGE_TAG_BASE}-${KUBE_BUILD_IMAGE_VERSION}"
|
|
KUBE_BUILD_IMAGE="${KUBE_BUILD_IMAGE_REPO}:${KUBE_BUILD_IMAGE_TAG}"
|
|
KUBE_BUILD_CONTAINER_NAME_BASE="kube-build-${KUBE_ROOT_HASH}"
|
|
KUBE_BUILD_CONTAINER_NAME="${KUBE_BUILD_CONTAINER_NAME_BASE}-${KUBE_BUILD_IMAGE_VERSION}"
|
|
KUBE_RSYNC_CONTAINER_NAME_BASE="kube-rsync-${KUBE_ROOT_HASH}"
|
|
KUBE_RSYNC_CONTAINER_NAME="${KUBE_RSYNC_CONTAINER_NAME_BASE}-${KUBE_BUILD_IMAGE_VERSION}"
|
|
KUBE_DATA_CONTAINER_NAME_BASE="kube-build-data-${KUBE_ROOT_HASH}"
|
|
KUBE_DATA_CONTAINER_NAME="${KUBE_DATA_CONTAINER_NAME_BASE}-${KUBE_BUILD_IMAGE_VERSION}"
|
|
DOCKER_MOUNT_ARGS=(--volumes-from "${KUBE_DATA_CONTAINER_NAME}")
|
|
LOCAL_OUTPUT_BUILD_CONTEXT="${LOCAL_OUTPUT_IMAGE_STAGING}/${KUBE_BUILD_IMAGE}"
|
|
|
|
kube::version::get_version_vars
|
|
kube::version::save_version_vars "${KUBE_ROOT}/.dockerized-kube-version-defs"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Utility functions
|
|
|
|
function kube::build::docker_available_on_osx() {
|
|
if [[ -z "${DOCKER_HOST}" ]]; then
|
|
if [[ -S "/var/run/docker.sock" ]]; then
|
|
kube::log::status "Using Docker for MacOS"
|
|
return 0
|
|
fi
|
|
|
|
kube::log::status "No docker host is set. Checking options for setting one..."
|
|
if [[ -z "$(which docker-machine)" ]]; then
|
|
kube::log::status "It looks like you're running Mac OS X, yet neither Docker for Mac nor docker-machine can be found."
|
|
kube::log::status "See: https://docs.docker.com/engine/installation/mac/ for installation instructions."
|
|
return 1
|
|
elif [[ -n "$(which docker-machine)" ]]; then
|
|
kube::build::prepare_docker_machine
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function kube::build::prepare_docker_machine() {
|
|
kube::log::status "docker-machine was found."
|
|
docker-machine inspect "${DOCKER_MACHINE_NAME}" &> /dev/null || {
|
|
kube::log::status "Creating a machine to build Kubernetes"
|
|
docker-machine create --driver ${DOCKER_MACHINE_DRIVER} \
|
|
--engine-env HTTP_PROXY="${KUBERNETES_HTTP_PROXY:-}" \
|
|
--engine-env HTTPS_PROXY="${KUBERNETES_HTTPS_PROXY:-}" \
|
|
--engine-env NO_PROXY="${KUBERNETES_NO_PROXY:-127.0.0.1}" \
|
|
"${DOCKER_MACHINE_NAME}" > /dev/null || {
|
|
kube::log::error "Something went wrong creating a machine."
|
|
kube::log::error "Try the following: "
|
|
kube::log::error "docker-machine create -d ${DOCKER_MACHINE_DRIVER} ${DOCKER_MACHINE_NAME}"
|
|
return 1
|
|
}
|
|
}
|
|
docker-machine start "${DOCKER_MACHINE_NAME}" &> /dev/null
|
|
# it takes `docker-machine env` a few seconds to work if the machine was just started
|
|
local docker_machine_out
|
|
while ! docker_machine_out=$(docker-machine env "${DOCKER_MACHINE_NAME}" 2>&1); do
|
|
if [[ ${docker_machine_out} =~ "Error checking TLS connection" ]]; then
|
|
echo ${docker_machine_out}
|
|
docker-machine regenerate-certs ${DOCKER_MACHINE_NAME}
|
|
else
|
|
sleep 1
|
|
fi
|
|
done
|
|
eval $(docker-machine env "${DOCKER_MACHINE_NAME}")
|
|
kube::log::status "A Docker host using docker-machine named '${DOCKER_MACHINE_NAME}' is ready to go!"
|
|
return 0
|
|
}
|
|
|
|
function kube::build::is_osx() {
|
|
[[ "$(uname)" == "Darwin" ]]
|
|
}
|
|
|
|
function kube::build::is_gnu_sed() {
|
|
[[ $(sed --version 2>&1) == *GNU* ]]
|
|
}
|
|
|
|
function kube::build::ensure_rsync() {
|
|
if [[ -z "$(which rsync)" ]]; then
|
|
kube::log::error "Can't find 'rsync' in PATH, please fix and retry."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function kube::build::update_dockerfile() {
|
|
if kube::build::is_gnu_sed; then
|
|
sed_opts=(-i)
|
|
else
|
|
sed_opts=(-i '')
|
|
fi
|
|
sed "${sed_opts[@]}" "s/KUBE_BUILD_IMAGE_CROSS_TAG/${KUBE_BUILD_IMAGE_CROSS_TAG}/" "${LOCAL_OUTPUT_BUILD_CONTEXT}/Dockerfile"
|
|
}
|
|
|
|
function kube::build::ensure_docker_in_path() {
|
|
if [[ -z "$(which docker)" ]]; then
|
|
kube::log::error "Can't find 'docker' in PATH, please fix and retry."
|
|
kube::log::error "See https://docs.docker.com/installation/#installation for installation instructions."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function kube::build::ensure_docker_daemon_connectivity {
|
|
if ! "${DOCKER[@]}" info > /dev/null 2>&1 ; then
|
|
cat <<'EOF' >&2
|
|
Can't connect to 'docker' daemon. please fix and retry.
|
|
|
|
Possible causes:
|
|
- Docker Daemon not started
|
|
- Linux: confirm via your init system
|
|
- macOS w/ docker-machine: run `docker-machine ls` and `docker-machine start <name>`
|
|
- macOS w/ Docker for Mac: Check the menu bar and start the Docker application
|
|
- DOCKER_HOST hasn't been set or is set incorrectly
|
|
- Linux: domain socket is used, DOCKER_* should be unset. In Bash run `unset ${!DOCKER_*}`
|
|
- macOS w/ docker-machine: run `eval "$(docker-machine env <name>)"`
|
|
- macOS w/ Docker for Mac: domain socket is used, DOCKER_* should be unset. In Bash run `unset ${!DOCKER_*}`
|
|
- Other things to check:
|
|
- Linux: User isn't in 'docker' group. Add and relogin.
|
|
- Something like 'sudo usermod -a -G docker ${USER}'
|
|
- RHEL7 bug and workaround: https://bugzilla.redhat.com/show_bug.cgi?id=1119282#c8
|
|
EOF
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function kube::build::ensure_tar() {
|
|
if [[ -n "${TAR:-}" ]]; then
|
|
return
|
|
fi
|
|
|
|
# Find gnu tar if it is available, bomb out if not.
|
|
TAR=tar
|
|
if which gtar &>/dev/null; then
|
|
TAR=gtar
|
|
else
|
|
if which gnutar &>/dev/null; then
|
|
TAR=gnutar
|
|
fi
|
|
fi
|
|
if ! "${TAR}" --version | grep -q GNU; then
|
|
echo " !!! Cannot find GNU tar. Build on Linux or install GNU tar"
|
|
echo " on Mac OS X (brew install gnu-tar)."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function kube::build::has_docker() {
|
|
which docker &> /dev/null
|
|
}
|
|
|
|
# Detect if a specific image exists
|
|
#
|
|
# $1 - image repo name
|
|
# #2 - image tag
|
|
function kube::build::docker_image_exists() {
|
|
[[ -n $1 && -n $2 ]] || {
|
|
kube::log::error "Internal error. Image not specified in docker_image_exists."
|
|
exit 2
|
|
}
|
|
|
|
[[ $("${DOCKER[@]}" images -q "${1}:${2}") ]]
|
|
}
|
|
|
|
# Delete all images that match a tag prefix except for the "current" version
|
|
#
|
|
# $1: The image repo/name
|
|
# $2: The tag base. We consider any image that matches $2*
|
|
# $3: The current image not to delete if provided
|
|
function kube::build::docker_delete_old_images() {
|
|
# In Docker 1.12, we can replace this with
|
|
# docker images "$1" --format "{{.Tag}}"
|
|
for tag in $("${DOCKER[@]}" images ${1} | tail -n +2 | awk '{print $2}') ; do
|
|
if [[ "${tag}" != "${2}"* ]] ; then
|
|
V=3 kube::log::status "Keeping image ${1}:${tag}"
|
|
continue
|
|
fi
|
|
|
|
if [[ -z "${3:-}" || "${tag}" != "${3}" ]] ; then
|
|
V=2 kube::log::status "Deleting image ${1}:${tag}"
|
|
"${DOCKER[@]}" rmi "${1}:${tag}" >/dev/null
|
|
else
|
|
V=3 kube::log::status "Keeping image ${1}:${tag}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Stop and delete all containers that match a pattern
|
|
#
|
|
# $1: The base container prefix
|
|
# $2: The current container to keep, if provided
|
|
function kube::build::docker_delete_old_containers() {
|
|
# In Docker 1.12 we can replace this line with
|
|
# docker ps -a --format="{{.Names}}"
|
|
for container in $("${DOCKER[@]}" ps -a | tail -n +2 | awk '{print $NF}') ; do
|
|
if [[ "${container}" != "${1}"* ]] ; then
|
|
V=3 kube::log::status "Keeping container ${container}"
|
|
continue
|
|
fi
|
|
if [[ -z "${2:-}" || "${container}" != "${2}" ]] ; then
|
|
V=2 kube::log::status "Deleting container ${container}"
|
|
kube::build::destroy_container "${container}"
|
|
else
|
|
V=3 kube::log::status "Keeping container ${container}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Takes $1 and computes a short has for it. Useful for unique tag generation
|
|
function kube::build::short_hash() {
|
|
[[ $# -eq 1 ]] || {
|
|
kube::log::error "Internal error. No data based to short_hash."
|
|
exit 2
|
|
}
|
|
|
|
local short_hash
|
|
if which md5 >/dev/null 2>&1; then
|
|
short_hash=$(md5 -q -s "$1")
|
|
else
|
|
short_hash=$(echo -n "$1" | md5sum)
|
|
fi
|
|
echo ${short_hash:0:10}
|
|
}
|
|
|
|
# Pedantically kill, wait-on and remove a container. The -f -v options
|
|
# to rm don't actually seem to get the job done, so force kill the
|
|
# container, wait to ensure it's stopped, then try the remove. This is
|
|
# a workaround for bug https://github.com/docker/docker/issues/3968.
|
|
function kube::build::destroy_container() {
|
|
"${DOCKER[@]}" kill "$1" >/dev/null 2>&1 || true
|
|
"${DOCKER[@]}" wait "$1" >/dev/null 2>&1 || true
|
|
"${DOCKER[@]}" rm -f -v "$1" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Building
|
|
|
|
|
|
function kube::build::clean() {
|
|
if kube::build::has_docker ; then
|
|
kube::build::docker_delete_old_containers "${KUBE_BUILD_CONTAINER_NAME_BASE}"
|
|
kube::build::docker_delete_old_containers "${KUBE_RSYNC_CONTAINER_NAME_BASE}"
|
|
kube::build::docker_delete_old_containers "${KUBE_DATA_CONTAINER_NAME_BASE}"
|
|
kube::build::docker_delete_old_images "${KUBE_BUILD_IMAGE_REPO}" "${KUBE_BUILD_IMAGE_TAG_BASE}"
|
|
|
|
V=2 kube::log::status "Cleaning all untagged docker images"
|
|
"${DOCKER[@]}" rmi $("${DOCKER[@]}" images -q --filter 'dangling=true') 2> /dev/null || true
|
|
fi
|
|
|
|
kube::log::status "Removing _output directory"
|
|
rm -rf "${LOCAL_OUTPUT_ROOT}"
|
|
}
|
|
|
|
# Set up the context directory for the kube-build image and build it.
|
|
function kube::build::build_image() {
|
|
mkdir -p "${LOCAL_OUTPUT_BUILD_CONTEXT}"
|
|
|
|
cp /etc/localtime "${LOCAL_OUTPUT_BUILD_CONTEXT}/"
|
|
|
|
cp build-tools/build-image/Dockerfile "${LOCAL_OUTPUT_BUILD_CONTEXT}/Dockerfile"
|
|
cp build-tools/build-image/rsyncd.sh "${LOCAL_OUTPUT_BUILD_CONTEXT}/"
|
|
dd if=/dev/urandom bs=512 count=1 2>/dev/null | LC_ALL=C tr -dc 'A-Za-z0-9' | dd bs=32 count=1 2>/dev/null > "${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password"
|
|
chmod go= "${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password"
|
|
|
|
kube::build::update_dockerfile
|
|
kube::build::docker_build "${KUBE_BUILD_IMAGE}" "${LOCAL_OUTPUT_BUILD_CONTEXT}" 'false'
|
|
|
|
# Clean up old versions of everything
|
|
kube::build::docker_delete_old_containers "${KUBE_BUILD_CONTAINER_NAME_BASE}" "${KUBE_BUILD_CONTAINER_NAME}"
|
|
kube::build::docker_delete_old_containers "${KUBE_RSYNC_CONTAINER_NAME_BASE}" "${KUBE_RSYNC_CONTAINER_NAME}"
|
|
kube::build::docker_delete_old_containers "${KUBE_DATA_CONTAINER_NAME_BASE}" "${KUBE_DATA_CONTAINER_NAME}"
|
|
kube::build::docker_delete_old_images "${KUBE_BUILD_IMAGE_REPO}" "${KUBE_BUILD_IMAGE_TAG_BASE}" "${KUBE_BUILD_IMAGE_TAG}"
|
|
|
|
kube::build::ensure_data_container
|
|
kube::build::sync_to_container
|
|
}
|
|
|
|
# Build a docker image from a Dockerfile.
|
|
# $1 is the name of the image to build
|
|
# $2 is the location of the "context" directory, with the Dockerfile at the root.
|
|
# $3 is the value to set the --pull flag for docker build; true by default
|
|
function kube::build::docker_build() {
|
|
local -r image=$1
|
|
local -r context_dir=$2
|
|
local -r pull="${3:-true}"
|
|
local -ra build_cmd=("${DOCKER[@]}" build -t "${image}" "--pull=${pull}" "${context_dir}")
|
|
|
|
kube::log::status "Building Docker image ${image}"
|
|
local docker_output
|
|
docker_output=$("${build_cmd[@]}" 2>&1) || {
|
|
cat <<EOF >&2
|
|
+++ Docker build command failed for ${image}
|
|
|
|
${docker_output}
|
|
|
|
To retry manually, run:
|
|
|
|
${build_cmd[*]}
|
|
|
|
EOF
|
|
return 1
|
|
}
|
|
}
|
|
|
|
function kube::build::ensure_data_container() {
|
|
# If the data container exists AND exited successfully, we can use it.
|
|
# Otherwise nuke it and start over.
|
|
local ret=0
|
|
local code=$(docker inspect \
|
|
-f '{{.State.ExitCode}}' \
|
|
"${KUBE_DATA_CONTAINER_NAME}" 2>/dev/null || ret=$?)
|
|
if [[ "${ret}" == 0 && "${code}" != 0 ]]; then
|
|
kube::build::destroy_container "${KUBE_DATA_CONTAINER_NAME}"
|
|
ret=1
|
|
fi
|
|
if [[ "${ret}" != 0 ]]; then
|
|
kube::log::status "Creating data container ${KUBE_DATA_CONTAINER_NAME}"
|
|
# We have to ensure the directory exists, or else the docker run will
|
|
# create it as root.
|
|
mkdir -p "${LOCAL_OUTPUT_GOPATH}"
|
|
# We want this to run as root to be able to chown, so non-root users can
|
|
# later use the result as a data container. This run both creates the data
|
|
# container and chowns the GOPATH.
|
|
#
|
|
# The data container creates volumes for all of the directories that store
|
|
# intermediates for the Go build. This enables incremental builds across
|
|
# Docker sessions. The *_cgo paths are re-compiled versions of the go std
|
|
# libraries for true static building.
|
|
local -ra docker_cmd=(
|
|
"${DOCKER[@]}" run
|
|
--volume "${REMOTE_ROOT}" # white-out the whole output dir
|
|
--volume /usr/local/go/pkg/linux_386_cgo
|
|
--volume /usr/local/go/pkg/linux_amd64_cgo
|
|
--volume /usr/local/go/pkg/linux_arm_cgo
|
|
--volume /usr/local/go/pkg/linux_arm64_cgo
|
|
--volume /usr/local/go/pkg/linux_ppc64le_cgo
|
|
--volume /usr/local/go/pkg/darwin_amd64_cgo
|
|
--volume /usr/local/go/pkg/darwin_386_cgo
|
|
--volume /usr/local/go/pkg/windows_amd64_cgo
|
|
--volume /usr/local/go/pkg/windows_386_cgo
|
|
--name "${KUBE_DATA_CONTAINER_NAME}"
|
|
--hostname "${HOSTNAME}"
|
|
"${KUBE_BUILD_IMAGE}"
|
|
chown -R $(id -u).$(id -g)
|
|
"${REMOTE_ROOT}"
|
|
/usr/local/go/pkg/
|
|
)
|
|
"${docker_cmd[@]}"
|
|
fi
|
|
}
|
|
|
|
# Run a command in the kube-build image. This assumes that the image has
|
|
# already been built.
|
|
function kube::build::run_build_command() {
|
|
kube::log::status "Running build command..."
|
|
kube::build::run_build_command_ex "${KUBE_BUILD_CONTAINER_NAME}" -- "$@"
|
|
}
|
|
|
|
# Run a command in the kube-build image. This assumes that the image has
|
|
# already been built.
|
|
#
|
|
# Arguments are in the form of
|
|
# <container name> <extra docker args> -- <command>
|
|
function kube::build::run_build_command_ex() {
|
|
[[ $# != 0 ]] || { echo "Invalid input - please specify a container name." >&2; return 4; }
|
|
local container_name="${1}"
|
|
shift
|
|
|
|
local -a docker_run_opts=(
|
|
"--name=${container_name}"
|
|
"--user=$(id -u):$(id -g)"
|
|
"--hostname=${HOSTNAME}"
|
|
"${DOCKER_MOUNT_ARGS[@]}"
|
|
)
|
|
|
|
local detach=false
|
|
|
|
[[ $# != 0 ]] || { echo "Invalid input - please specify docker arguments followed by --." >&2; return 4; }
|
|
# Everything before "--" is an arg to docker
|
|
until [ -z "${1-}" ] ; do
|
|
if [[ "$1" == "--" ]]; then
|
|
shift
|
|
break
|
|
fi
|
|
docker_run_opts+=("$1")
|
|
if [[ "$1" == "-d" || "$1" == "--detach" ]] ; then
|
|
detach=true
|
|
fi
|
|
shift
|
|
done
|
|
|
|
# Everything after "--" is the command to run
|
|
[[ $# != 0 ]] || { echo "Invalid input - please specify a command to run." >&2; return 4; }
|
|
local -a cmd=()
|
|
until [ -z "${1-}" ] ; do
|
|
cmd+=("$1")
|
|
shift
|
|
done
|
|
|
|
docker_run_opts+=(
|
|
--env "KUBE_FASTBUILD=${KUBE_FASTBUILD:-false}"
|
|
--env "KUBE_BUILDER_OS=${OSTYPE:-notdetected}"
|
|
--env "KUBE_BUILD_PPC64LE=${KUBE_BUILD_PPC64LE}" # TODO(IBM): remove
|
|
)
|
|
|
|
# If we have stdin we can run interactive. This allows things like 'shell.sh'
|
|
# to work. However, if we run this way and don't have stdin, then it ends up
|
|
# running in a daemon-ish mode. So if we don't have a stdin, we explicitly
|
|
# attach stderr/stdout but don't bother asking for a tty.
|
|
if [[ -t 0 ]]; then
|
|
docker_run_opts+=(--interactive --tty)
|
|
elif [[ "${detach}" == false ]]; then
|
|
docker_run_opts+=(--attach=stdout --attach=stderr)
|
|
fi
|
|
|
|
local -ra docker_cmd=(
|
|
"${DOCKER[@]}" run "${docker_run_opts[@]}" "${KUBE_BUILD_IMAGE}")
|
|
|
|
# Clean up container from any previous run
|
|
kube::build::destroy_container "${container_name}"
|
|
"${docker_cmd[@]}" "${cmd[@]}"
|
|
if [[ "${detach}" == false ]]; then
|
|
kube::build::destroy_container "${container_name}"
|
|
fi
|
|
}
|
|
|
|
function kube::build::rsync_probe {
|
|
# Wait unil rsync is up and running.
|
|
local tries=20
|
|
while (( ${tries} > 0 )) ; do
|
|
if rsync "rsync://k8s@${1}:${2}/" \
|
|
--password-file="${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password" \
|
|
&> /dev/null ; then
|
|
return 0
|
|
fi
|
|
tries=$(( ${tries} - 1))
|
|
sleep 0.1
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# Start up the rsync container in the backgound. This should be explicitly
|
|
# stoped with kube::build::stop_rsyncd_container.
|
|
#
|
|
# This will set the global var KUBE_RSYNC_ADDR to the effective port that the
|
|
# rsync daemon can be reached out.
|
|
function kube::build::start_rsyncd_container() {
|
|
kube::build::stop_rsyncd_container
|
|
V=3 kube::log::status "Starting rsyncd container"
|
|
kube::build::run_build_command_ex \
|
|
"${KUBE_RSYNC_CONTAINER_NAME}" -p 127.0.0.1:${KUBE_RSYNC_PORT}:${KUBE_CONTAINER_RSYNC_PORT} -d \
|
|
-- /rsyncd.sh >/dev/null
|
|
|
|
local mapped_port
|
|
if ! mapped_port=$("${DOCKER[@]}" port "${KUBE_RSYNC_CONTAINER_NAME}" ${KUBE_CONTAINER_RSYNC_PORT} 2> /dev/null | cut -d: -f 2) ; then
|
|
kube:log:error "Could not get effective rsync port"
|
|
return 1
|
|
fi
|
|
|
|
local container_ip
|
|
container_ip=$("${DOCKER[@]}" inspect --format '{{ .NetworkSettings.IPAddress }}' "${KUBE_RSYNC_CONTAINER_NAME}")
|
|
|
|
# Sometimes we can reach rsync through localhost and a NAT'd port. Other
|
|
# times (when we are running in another docker container on the Jenkins
|
|
# machines) we have to talk directly to the container IP. There is no one
|
|
# strategy that works in all cases so we test to figure out which situation we
|
|
# are in.
|
|
if kube::build::rsync_probe 127.0.0.1 ${mapped_port}; then
|
|
KUBE_RSYNC_ADDR="127.0.0.1:${mapped_port}"
|
|
return 0
|
|
elif kube::build::rsync_probe "${container_ip}" ${KUBE_CONTAINER_RSYNC_PORT}; then
|
|
KUBE_RSYNC_ADDR="${container_ip}:${KUBE_CONTAINER_RSYNC_PORT}"
|
|
return 0
|
|
fi
|
|
|
|
kube::log::error "Could not connect to rsync container. See build-tools/README.md for setting up remote Docker engine."
|
|
return 1
|
|
}
|
|
|
|
function kube::build::stop_rsyncd_container() {
|
|
V=3 kube::log::status "Stopping any currently running rsyncd container"
|
|
unset KUBE_RSYNC_ADDR
|
|
kube::build::destroy_container "${KUBE_RSYNC_CONTAINER_NAME}"
|
|
}
|
|
|
|
# This will launch rsyncd in a container and then sync the source tree to the
|
|
# container over the local network.
|
|
function kube::build::sync_to_container() {
|
|
kube::log::status "Syncing sources to container"
|
|
|
|
kube::build::start_rsyncd_container
|
|
|
|
local rsync_extra=""
|
|
if (( ${KUBE_VERBOSE} >= 6 )); then
|
|
rsync_extra="-iv"
|
|
fi
|
|
|
|
# rsync filters are a bit confusing. Here we are syncing everything except
|
|
# output only directories and things that are not necessary like the git
|
|
# directory. The '- /' filter prevents rsync from trying to set the
|
|
# uid/gid/perms on the root of the sync tree.
|
|
V=3 kube::log::status "Running rsync"
|
|
rsync ${rsync_extra} \
|
|
--archive \
|
|
--delete \
|
|
--prune-empty-dirs \
|
|
--password-file="${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password" \
|
|
--filter='- /.git/' \
|
|
--filter='- /.make/' \
|
|
--filter='- /_tmp/' \
|
|
--filter='- /_output/' \
|
|
--filter='- /' \
|
|
"${KUBE_ROOT}/" "rsync://k8s@${KUBE_RSYNC_ADDR}/k8s/"
|
|
|
|
kube::build::stop_rsyncd_container
|
|
}
|
|
|
|
# Copy all build results back out.
|
|
function kube::build::copy_output() {
|
|
kube::log::status "Syncing out of container"
|
|
|
|
kube::build::start_rsyncd_container
|
|
|
|
local rsync_extra=""
|
|
if (( ${KUBE_VERBOSE} >= 6 )); then
|
|
rsync_extra="-iv"
|
|
fi
|
|
|
|
# The filter syntax for rsync is a little obscure. It filters on files and
|
|
# directories. If you don't go in to a directory you won't find any files
|
|
# there. Rules are evaluated in order. The last two rules are a little
|
|
# magic. '+ */' says to go in to every directory and '- /**' says to ignore
|
|
# any file or directory that isn't already specifically allowed.
|
|
#
|
|
# We are looking to copy out all of the built binaries along with various
|
|
# generated files.
|
|
V=3 kube::log::status "Running rsync"
|
|
rsync ${rsync_extra} \
|
|
--archive \
|
|
--prune-empty-dirs \
|
|
--password-file="${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password" \
|
|
--filter='- /vendor/' \
|
|
--filter='- /_temp/' \
|
|
--filter='+ /_output/dockerized/bin/**' \
|
|
--filter='+ zz_generated.*' \
|
|
--filter='+ generated.proto' \
|
|
--filter='+ *.pb.go' \
|
|
--filter='+ */' \
|
|
--filter='- /**' \
|
|
"rsync://k8s@${KUBE_RSYNC_ADDR}/k8s/" "${KUBE_ROOT}"
|
|
|
|
kube::build::stop_rsyncd_container
|
|
}
|