Skip to content

Commit

Permalink
Make web ui robust and reproducible.
Browse files Browse the repository at this point in the history
  • Loading branch information
jackgr committed May 27, 2015
1 parent ee82d46 commit f259f76
Show file tree
Hide file tree
Showing 43 changed files with 805 additions and 113 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ network_closure.sh
# Web UI
www/master/node_modules/
www/master/npm-debug.log
www/master/shared/config/development.json

# Karma output
www/test_out
6 changes: 4 additions & 2 deletions build/build-image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ ENV GOARCH amd64
# work around 64MB tmpfs size in Docker 1.6
ENV TMPDIR /tmp.k8s

# Get the code coverage tool and godep
# Get the code coverage tool, godep and binary data file builder
RUN mkdir $TMPDIR && \
go get golang.org/x/tools/cmd/cover github.com/tools/godep
go get golang.org/x/tools/cmd/cover && \
go get github.com/tools/godep && \
go get github.com/jteeuwen/go-bindata/...

# We use rsync to copy some binaries around. It is faster (0.3s vs. 1.1s) on my
# machine vs. `install`
Expand Down
124 changes: 98 additions & 26 deletions build/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,26 @@ readonly KUBE_GCS_LATEST_CONTENTS=${KUBE_GCS_LATEST_CONTENTS:-}

# Constants
readonly KUBE_BUILD_IMAGE_REPO=kube-build
readonly KUBE_BUILD_UI_IMAGE_REPO=kube-build-ui
# These get set in verify_prereqs with a unique hash based on KUBE_ROOT
# KUBE_BUILD_IMAGE_TAG=<hash>
# KUBE_BUILD_IMAGE="${KUBE_BUILD_IMAGE_REPO}:${KUBE_BUILD_IMAGE_TAG}"
# KUBE_BUILD_CONTAINER_NAME=kube-build-<hash>
# KUBE_BUILD_UI_IMAGE="${KUBE_BUILD_UI_IMAGE_REPO}:${KUBE_BUILD_IMAGE_TAG}"
# KUBE_BUILD_UI_CONTAINER_NAME=kube-build-ui-<hash>
readonly KUBE_BUILD_IMAGE_CROSS_TAG=cross
readonly KUBE_BUILD_IMAGE_CROSS="${KUBE_BUILD_IMAGE_REPO}:${KUBE_BUILD_IMAGE_CROSS_TAG}"
readonly KUBE_BUILD_GOLANG_REPO=golang
readonly KUBE_BUILD_NODE_REPO=node
readonly KUBE_BUILD_GOLANG_VERSION=1.4
readonly KUBE_BUILD_NODE_VERSION=0.12.3
# KUBE_BUILD_DATA_CONTAINER_NAME=kube-build-data-<hash>

# 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
# *_OUTPUT_SUBPATH - location where 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
Expand All @@ -71,22 +77,29 @@ readonly LOCAL_OUTPUT_IMAGE_STAGING="${LOCAL_OUTPUT_ROOT}/images"

readonly OUTPUT_BINPATH="${CUSTOM_OUTPUT_BINPATH:-$LOCAL_OUTPUT_BINPATH}"

readonly REMOTE_OUTPUT_ROOT="/go/src/${KUBE_GO_PACKAGE}/_output"
readonly REMOTE_SOURCE_ROOT="/go/src/${KUBE_GO_PACKAGE}"
readonly REMOTE_OUTPUT_ROOT="${REMOTE_SOURCE_ROOT}/_output"
readonly REMOTE_OUTPUT_SUBPATH="${REMOTE_OUTPUT_ROOT}/dockerized"
readonly REMOTE_OUTPUT_BINPATH="${REMOTE_OUTPUT_SUBPATH}/bin"

readonly DOCKER_MOUNT_ARGS_BASE=(--volume "${OUTPUT_BINPATH}:${REMOTE_OUTPUT_BINPATH}")
# DOCKER_MOUNT_ARGS=("${DOCKER_MOUNT_ARGS_BASE[@]}" --volumes-from "${KUBE_BUILD_DATA_CONTAINER_NAME}")

# We create a Docker data container to cache incremental build artifacts. We
# need to cache both the go tree in _output and the go tree under Godeps.
# need to cache both the go tree in _output and the go tree under Godeps. We
# also need to cache the output from the ui build.
readonly REMOTE_OUTPUT_GOPATH="${REMOTE_OUTPUT_SUBPATH}/go"
readonly REMOTE_GODEP_GOPATH="/go/src/${KUBE_GO_PACKAGE}/Godeps/_workspace/pkg"
readonly REMOTE_GODEP_GOPATH="${REMOTE_SOURCE_ROOT}/Godeps/_workspace/pkg"
readonly REMOTE_OUTPUT_WWWPATH="${REMOTE_OUTPUT_SUBPATH}/www"
readonly DOCKER_DATA_MOUNT_ARGS=(
--volume "${REMOTE_OUTPUT_GOPATH}"
--volume "${REMOTE_GODEP_GOPATH}"
--volume "${REMOTE_OUTPUT_WWWPATH}"
)

# Use the smallest image common to all build containers.
readonly KUBE_BUILD_DATA_IMAGE=buildpack-deps:jessie-scm

# This is where the final release artifacts are created locally
readonly RELEASE_STAGE="${LOCAL_OUTPUT_ROOT}/release-stage"
readonly RELEASE_DIR="${LOCAL_OUTPUT_ROOT}/release-tars"
Expand Down Expand Up @@ -114,6 +127,8 @@ readonly KUBE_DOCKER_WRAPPED_BINARIES=(
# KUBE_BUILD_IMAGE_TAG
# KUBE_BUILD_IMAGE
# KUBE_BUILD_CONTAINER_NAME
# KUBE_BUILD_UI_IMAGE
# KUBE_BUILD_UI_CONTAINER_NAME
# KUBE_BUILD_DATA_CONTAINER_NAME
# DOCKER_MOUNT_ARGS
function kube::build::verify_prereqs() {
Expand Down Expand Up @@ -167,9 +182,9 @@ function kube::build::verify_prereqs() {

# On OS X, set boot2docker env vars for the 'clean' target if boot2docker is running
if kube::build::is_osx && kube::build::has_docker ; then
if [[ ! -z "$(which boot2docker)" ]]; then
if [[ $(boot2docker status) == "running" ]]; then
if [[ -z "$DOCKER_HOST" ]]; then
if [[ -z "$DOCKER_HOST" ]]; then
if [[ ! -z "$(which boot2docker)" ]]; then
if [[ $(boot2docker status) == "running" ]]; then
kube::log::status "Setting boot2docker env variables"
$(boot2docker shellinit)
fi
Expand All @@ -183,6 +198,9 @@ function kube::build::verify_prereqs() {
KUBE_BUILD_IMAGE_TAG="build-${KUBE_ROOT_HASH}"
KUBE_BUILD_IMAGE="${KUBE_BUILD_IMAGE_REPO}:${KUBE_BUILD_IMAGE_TAG}"
KUBE_BUILD_CONTAINER_NAME="kube-build-${KUBE_ROOT_HASH}"
KUBE_BUILD_UI_IMAGE_TAG="${KUBE_BUILD_IMAGE_TAG}"
KUBE_BUILD_UI_IMAGE="${KUBE_BUILD_UI_IMAGE_REPO}:${KUBE_BUILD_UI_IMAGE_TAG}"
KUBE_BUILD_UI_CONTAINER_NAME="kube-build-ui-${KUBE_ROOT_HASH}"
KUBE_BUILD_DATA_CONTAINER_NAME="kube-build-data-${KUBE_ROOT_HASH}"
DOCKER_MOUNT_ARGS=("${DOCKER_MOUNT_ARGS_BASE[@]}" --volumes-from "${KUBE_BUILD_DATA_CONTAINER_NAME}")
}
Expand Down Expand Up @@ -287,10 +305,10 @@ function kube::build::build_image_built() {
kube::build::docker_image_exists "${KUBE_BUILD_IMAGE_REPO}" "${KUBE_BUILD_IMAGE_TAG}"
}

function kube::build::ensure_golang() {
kube::build::docker_image_exists golang "${KUBE_BUILD_GOLANG_VERSION}" || {
function kube::build::ensure_docker_image() {
kube::build::docker_image_exists $1 $2 || {
[[ ${KUBE_SKIP_CONFIRMATIONS} =~ ^[yY]$ ]] || {
echo "You don't have a local copy of the golang docker image. This image is 450MB."
echo "You don't have a local copy of the $1 docker image."
read -p "Download it now? [y/n] " -r
echo
[[ $REPLY =~ ^[yY]$ ]] || {
Expand All @@ -299,11 +317,19 @@ function kube::build::ensure_golang() {
}
}

kube::log::status "Pulling docker image: golang:${KUBE_BUILD_GOLANG_VERSION}"
"${DOCKER[@]}" pull golang:${KUBE_BUILD_GOLANG_VERSION}
kube::log::status "Pulling docker image: $1:$2"
"${DOCKER[@]}" pull $1:$2
}
}

function kube::build::ensure_golang() {
kube::build::ensure_docker_image "${KUBE_BUILD_GOLANG_REPO}" "${KUBE_BUILD_GOLANG_VERSION}"
}

function kube::build::ensure_node() {
kube::build::ensure_docker_image "${KUBE_BUILD_NODE_REPO}" "${KUBE_BUILD_NODE_VERSION}"
}

# Set up the context directory for the kube-build image and build it.
function kube::build::build_image() {
local -r build_context_dir="${LOCAL_OUTPUT_IMAGE_STAGING}/${KUBE_BUILD_IMAGE}"
Expand All @@ -316,6 +342,7 @@ function kube::build::build_image() {
Godeps/_workspace/src
Godeps/Godeps.json
hack
hooks
LICENSE
pkg
plugin
Expand All @@ -340,12 +367,36 @@ function kube::build::build_image() {
function kube::build::build_image_cross() {
kube::build::ensure_golang

local -r build_context_dir="${LOCAL_OUTPUT_ROOT}/images/${KUBE_BUILD_IMAGE}/cross"
local -r build_context_dir="${LOCAL_OUTPUT_IMAGE_STAGING}/${KUBE_BUILD_IMAGE}/cross"
mkdir -p "${build_context_dir}"
cp build/build-image/cross/Dockerfile ${build_context_dir}/Dockerfile
kube::build::docker_build "${KUBE_BUILD_IMAGE_CROSS}" "${build_context_dir}"
}

# Set up the context directory for the kube-build-ui image and build it.
function kube::build::build_ui_image() {
kube::build::ensure_node

local -r build_context_dir="${LOCAL_OUTPUT_IMAGE_STAGING}/${KUBE_BUILD_UI_IMAGE}"
local -r source=(
build
hack
hooks
LICENSE
README.md
www
)

mkdir -p "${build_context_dir}"
tar czf "${build_context_dir}/kube-ui-source.tar.gz" "${source[@]}"

kube::version::get_version_vars
kube::version::save_version_vars "${build_context_dir}/kube-version-defs"

cp build/ui/build-image/Dockerfile ${build_context_dir}/Dockerfile
kube::build::docker_build "${KUBE_BUILD_UI_IMAGE}" "${build_context_dir}"
}

# 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.
Expand Down Expand Up @@ -382,37 +433,44 @@ function kube::build::clean_images() {
kube::build::has_docker || return 0

kube::build::clean_image "${KUBE_BUILD_IMAGE}"
kube::build::clean_image "${KUBE_BUILD_UI_IMAGE}"

kube::log::status "Cleaning all other untagged docker images"
"${DOCKER[@]}" rmi $("${DOCKER[@]}" images -q --filter 'dangling=true') 2> /dev/null || true
}

# Build the data container to cache incremental build artifacts.
function kube::build::ensure_data_container() {
if ! "${DOCKER[@]}" inspect "${KUBE_BUILD_DATA_CONTAINER_NAME}" >/dev/null 2>&1; then
kube::log::status "Creating data container"
local -ra docker_cmd=(
"${DOCKER[@]}" run
"${DOCKER_DATA_MOUNT_ARGS[@]}"
--name "${KUBE_BUILD_DATA_CONTAINER_NAME}"
"${KUBE_BUILD_IMAGE}"
"${KUBE_BUILD_DATA_IMAGE}"
true
)
"${docker_cmd[@]}"
fi
}

# Run a command in the kube-build image. This assumes that the image has
# already been built. This will sync out all output data from the build.
function kube::build::run_build_command() {
kube::log::status "Running build command...."
[[ $# != 0 ]] || { echo "Invalid input." >&2; return 4; }
# Run a command in the supplied image. This assumes that the image has
# already been built.
# $1 is the name of the container to create
# $2 is the name of the image to use.
function kube::build::run_containerized_command() {
[[ $# > 2 ]] || { echo "Invalid input." >&2; return 4; }

local docker_image="$1"
local docker_container="$2"
shift 2

kube::build::ensure_data_container
kube::build::prepare_output
kube::build::prepare_output

local -a docker_run_opts=(
"--name=${KUBE_BUILD_CONTAINER_NAME}"
"${DOCKER_MOUNT_ARGS[@]}"
"--name=$docker_container"
"${DOCKER_MOUNT_ARGS[@]}"
)

# If we have stdin we can run interactive. This allows things like 'shell.sh'
Expand All @@ -426,19 +484,33 @@ function kube::build::run_build_command() {
fi

local -ra docker_cmd=(
"${DOCKER[@]}" run "${docker_run_opts[@]}" "${KUBE_BUILD_IMAGE}")
"${DOCKER[@]}" run "${docker_run_opts[@]}" "$docker_image")

# Clean up container from any previous run
kube::build::destroy_container "${KUBE_BUILD_CONTAINER_NAME}"
kube::build::destroy_container "$docker_container"
"${docker_cmd[@]}" "$@"
kube::build::destroy_container "${KUBE_BUILD_CONTAINER_NAME}"
kube::build::destroy_container "$docker_container"
}

# Run a command in the kube-build image. This assumes that the image has
# already been built. This will sync out all output data from the build.
function kube::build::run_build_command() {
kube::log::status "Running build command...."
kube::build::run_containerized_command "${KUBE_BUILD_IMAGE}" "${KUBE_BUILD_CONTAINER_NAME}" "$@"
}

# Run a command in the kube-build-ui image. This assumes that the image has
# already been built.
function kube::build::run_build_ui_command() {
kube::log::status "Running build ui command...."
kube::build::run_containerized_command "${KUBE_BUILD_UI_IMAGE}" "${KUBE_BUILD_UI_CONTAINER_NAME}" "$@"
}

# Test if the output directory is remote (and can only be accessed through
# docker) or if it is "local" and we can access the output without going through
# docker.
function kube::build::is_output_remote() {
rm -f "${LOCAL_OUTPUT_SUBPATH}/test_for_remote"
rm -f "${LOCAL_OUTPUT_BINPATH}/test_for_remote"
kube::build::run_build_command touch "${REMOTE_OUTPUT_BINPATH}/test_for_remote"

[[ ! -e "${LOCAL_OUTPUT_BINPATH}/test_for_remote" ]]
Expand Down
4 changes: 4 additions & 0 deletions build/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ source "$KUBE_ROOT/build/common.sh"
KUBE_RELEASE_RUN_TESTS=${KUBE_RELEASE_RUN_TESTS-y}

kube::build::verify_prereqs

kube::build::build_ui_image
kube::build::run_build_ui_command hack/ui/build-www.sh

kube::build::build_image
kube::build::run_build_command hack/build-cross.sh

Expand Down
38 changes: 38 additions & 0 deletions build/ui/build-image/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2014 Google Inc. All rights reserved.
#
# 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.

# This file creates a standard build environment for building the Kubernetes WebUI

FROM node:0.12.3
MAINTAINER Jack Greenfield <jackgr@google.com>

# work around 64MB tmpfs size in Docker 1.6
ENV TMPDIR /tmp.k8s-ui
RUN mkdir $TMPDIR

# Mark this as a kube-build-ui container
RUN touch /kube-build-ui-image

WORKDIR /go/src/github.com/GoogleCloudPlatform/kubernetes

# Propagate the git tree version into the build image
ADD kube-version-defs /kube-version-defs
ENV KUBE_GIT_VERSION_FILE /kube-version-defs

# Make output from the dockerized build go someplace else
ENV KUBE_OUTPUT_SUBPATH _output/dockerized

# Upload Kubernetes WebUI source
ADD kube-ui-source.tar.gz /go/src/github.com/GoogleCloudPlatform/kubernetes

30 changes: 30 additions & 0 deletions build/ui/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# 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.

# Run a command in the Docker ui build container. Typically this will be one of
# the commands in `hack/ui`. When running in the ui build container the user is sure
# to have a consistent reproducible build environment.

set -o errexit
set -o nounset
set -o pipefail

KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
source "$KUBE_ROOT/build/common.sh"

kube::build::verify_prereqs
kube::build::build_ui_image
kube::build::run_build_ui_command "$@"
Loading

0 comments on commit f259f76

Please sign in to comment.