Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 39 additions & 42 deletions bazel/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,65 +1,62 @@
FROM gcr.io/gcp-runtimes/ubuntu_20_0_4
FROM gcr.io/gcp-runtimes/ubuntu_22_0_4

ADD bazel.sh /builder/bazel.sh
ARG BAZEL_VERSION=latest
ARG DOCKER_VERSION=5:24.0.7-1~ubuntu.20.04~focal
ARG BAZEL_VERSION=7.3.2
ARG DOCKER_VERSION=5:24.0.7-1~ubuntu.22.04~jammy

RUN \
# This makes add-apt-repository available.
# Update package lists
apt-get update && \
apt-get -y install \
python \
python3 \
python-pkg-resources \
python3-pkg-resources \
software-properties-common \
unzip && \

# Install Git >2.0.1
add-apt-repository ppa:git-core/ppa && \
apt-get -y update && \
apt-get -y install git && \

# Install bazel (https://docs.bazel.build/versions/master/install-ubuntu.html)
apt-get -y install openjdk-8-jdk && \
echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list && \
curl https://bazel.build/bazel-release.pub.gpg | apt-key add - && \
apt-get update && \

apt-get -y install bazel-${BAZEL_VERSION} && \
apt-get -y upgrade bazel-${BAZEL_VERSION} && \

ln -s /usr/bin/bazel-${BAZEL_VERSION} /usr/bin/bazel && \

# Install Docker (https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#uninstall-old-versions)
apt-get -y install \
apt-transport-https \
unzip \
curl \
ca-certificates && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable edge" && \
ca-certificates \
git && \

# Install Java 11 (better compatibility with modern Bazel versions)
apt-get -y install openjdk-11-jdk-headless && \

# Install Bazel from binary release (https://github.com/bazelbuild/bazel/releases)
# This is more reliable than the deprecated APT repository
mkdir -p /tmp/bazel-install && \
curl -L https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-linux-x86_64 \
-o /tmp/bazel-install/bazel-installer.sh && \
chmod +x /tmp/bazel-install/bazel-installer.sh && \
/tmp/bazel-install/bazel-installer.sh --prefix=/usr/local && \
rm -rf /tmp/bazel-install && \

# Install Docker (https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#set-up-the-repository)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list && \
apt-get -y update && \
apt-get dist-upgrade -y && \
apt-get install -y docker-ce=${DOCKER_VERSION} docker-ce-cli=${DOCKER_VERSION} unzip && \
apt-get install -y docker-ce=${DOCKER_VERSION} docker-ce-cli=${DOCKER_VERSION} && \

# Cleanup
rm -rf \
/var/cache/debconf/* \
/var/lib/apt/lists/* \
/var/log/* \
/tmp/* \
/var/tmp/* && \
/var/tmp/* && \

mv /usr/bin/bazel /builder/bazel && \
mv /builder/bazel.sh /usr/bin/bazel && \
# Install bazel.sh wrapper
mv /builder/bazel.sh /usr/bin/bazel && \
chmod +x /usr/bin/bazel && \

# Unpack bazel for future use.
# Test Bazel installation
bazel version

# Store the Bazel outputs under /workspace so that the symlinks under bazel-bin (et al) are accessible
# to downstream build steps.
# Create workspace directory (will be mounted by Cloud Build)
RUN mkdir -p /workspace
RUN echo 'startup --output_base=/workspace/.bazel' > ~/.bazelrc

# Note: .bazelrc is no longer created statically here. Instead, bazel.sh
# sets up workspace configuration dynamically at runtime to handle permission
# and isolation issues across different Cloud Build execution contexts.

ENTRYPOINT ["bazel"]
46 changes: 44 additions & 2 deletions bazel/bazel.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
#!/bin/bash
set -e

# Initialize workspace configuration dynamically to handle permission and
# isolation issues across different Cloud Build execution contexts.
setup_bazelrc() {
# Use current working directory for output base (allows workspace isolation)
local BAZEL_OUTPUT_BASE="${BAZEL_OUTPUT_BASE:-.bazel}"

# Ensure the output base directory exists with proper permissions
mkdir -p "${BAZEL_OUTPUT_BASE}"

# Write bazelrc to /tmp to avoid permission issues with home directory
# Use --nohome_rc to prevent user-level .bazelrc from interfering
cat > /tmp/.bazelrc <<EOF
startup --nohome_rc
startup --output_base=${PWD}/${BAZEL_OUTPUT_BASE}
EOF

# Override HOME to prevent permission conflicts with root's home directory
export HOME=/tmp
}

# Handle workspace initialization
setup_workspace() {
# Record original working directory for later use
export WORKSPACE="${WORKSPACE:=$PWD}"

# If BAZEL_HOME is not set, use a workspace-local cache to avoid conflicts
if [[ -z "$BAZEL_HOME" ]]; then
export BAZEL_HOME="${WORKSPACE}/.bazel-home"
mkdir -p "${BAZEL_HOME}"
fi
}

# Set up environment before running Bazel
setup_bazelrc
setup_workspace

# Always write the build_event_text_file to a temp file we create.
readonly BUILD_EVENT_FILE="$(mktemp --tmpdir build_event_text_file.XXXXX)"
readonly BUILD_EVENT_FILE_FLAG="--build_event_text_file=${BUILD_EVENT_FILE}"
Expand All @@ -22,13 +58,13 @@ n=$(($#-$i+1))
# Insert our flag at index $i out of $n. Quoting is all required for expansion.
set -- "${@:1:$((i-1))}" "$BUILD_EVENT_FILE_FLAG" "${@:$i:$n}"

# Run bazel with the new command line and capture the exit code.
# Run bazel with the dynamically generated bazelrc file and capture the exit code.
#
# This script has -e enabled, so if bazel exits non-zero, we need to both
# capture the exit code *and* replace it with zero. That is the purpose of the
# short-circuiting OR operator.
EXIT_CODE=0
/builder/bazel "$@" || EXIT_CODE=$?
/usr/local/bin/bazel --bazelrc=/tmp/.bazelrc "$@" || EXIT_CODE=$?

# Parse out the UUID from the BEP output file and write it to
# $BUILDER_OUTPUT/output, whose first 4KB will be served inline in the Build.
Expand All @@ -39,6 +75,12 @@ EXIT_CODE=0
# parsing the text proto file using real protobuf definitions.
readonly INVOCATION_ID_FIELD="$(grep -Eo 'uuid: \".{36}\"$' "${BUILD_EVENT_FILE}" | head -n 1)"
readonly INVOCATION_ID="${INVOCATION_ID_FIELD:7:-1}"

# Ensure BUILDER_OUTPUT directory exists before writing
mkdir -p "${BUILDER_OUTPUT:-.}"
echo "{\"bazel.build/invocation_id\": \"${INVOCATION_ID}\"}" > "${BUILDER_OUTPUT}/output"

# Cleanup temporary files
rm -f "${BUILD_EVENT_FILE}"

exit "${EXIT_CODE}"
2 changes: 1 addition & 1 deletion docker/Dockerfile-versioned
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ARG DOCKER_VERSION=VERSION
RUN apt-get -y install \
docker-ce=${DOCKER_VERSION} \
docker-ce-cli=${DOCKER_VERSION} \
docker-compose docker-compose-plugin && \
docker-compose-plugin && \
apt-get clean

ENTRYPOINT ["/usr/bin/docker"]
28 changes: 22 additions & 6 deletions gke-deploy/core/resource/ready.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ func podIsReady(ctx context.Context, obj *Object) (bool, error) {
// podDisruptionBudget returns true if a deployed object with kind "PodDisruptionBudget" is ready.
// This returns true if the following bullets are true:
// * status.observedGeneration == metadata.generation
// * status.currentHealthy >= status.desiredHealthy
// * status.currentHealthy >= status.desiredHealthy (for spec.minAvailable PDBs)
//
// For PDBs using spec.maxUnavailable (supported since Kubernetes 1.17), desiredHealthy may be
// zero when no minimum is set. In that case readiness is determined by status.disruptionsAllowed
// being present, which the API server populates once the PDB is accepted and reconciled.
func podDisruptionBudgetIsReady(ctx context.Context, obj *Object) (bool, error) {
generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation")
if err != nil {
Expand All @@ -297,20 +301,32 @@ func podDisruptionBudgetIsReady(ctx context.Context, obj *Object) (bool, error)
return false, nil
}

desiredHealthy, ok, err := unstructured.NestedInt64(obj.Object, "status", "desiredHealthy")
desiredHealthy, desiredOk, err := unstructured.NestedInt64(obj.Object, "status", "desiredHealthy")
if err != nil {
return false, fmt.Errorf("failed to get status.desiredHealthy field: %v", err)
}

currentHealthy, ok, err := unstructured.NestedInt64(obj.Object, "status", "currentHealthy")
currentHealthy, currentOk, err := unstructured.NestedInt64(obj.Object, "status", "currentHealthy")
if err != nil {
return false, fmt.Errorf("failed to get status.currentHealthy field: %v", err)
}
if !ok || currentHealthy < desiredHealthy {
return false, nil

// spec.minAvailable path: desiredHealthy > 0, check currentHealthy satisfies it.
if desiredOk && desiredHealthy > 0 {
if !currentOk || currentHealthy < desiredHealthy {
return false, nil
}
return true, nil
}

return true, nil
// spec.maxUnavailable path (Kubernetes 1.17+): desiredHealthy is 0 or absent.
// The API server sets disruptionsAllowed once the PDB is reconciled; its presence
// confirms the resource is ready.
_, disruptionsAllowedOk, err := unstructured.NestedInt64(obj.Object, "status", "disruptionsAllowed")
if err != nil {
return false, fmt.Errorf("failed to get status.disruptionsAllowed field: %v", err)
}
return disruptionsAllowedOk, nil
}

// replicaSetIsReady returns true if a deployed object with kind "ReplicaSet" is ready.
Expand Down
80 changes: 48 additions & 32 deletions gke-deploy/core/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,54 +227,60 @@ func addCommentsToLines(s string, lineComments map[string]string) (string, error
func UpdateMatchingContainerImage(ctx context.Context, objs Objects, imageName, replace string) error {
matched := false
for _, obj := range objs {
var nestedFields []string
var containersFields []string
var initContainersFields []string

switch kind := ObjectKind(obj); kind {
case "CronJob":
nestedFields = []string{"spec", "jobTemplate", "spec", "template", "spec", "containers"}
containersFields = []string{"spec", "jobTemplate", "spec", "template", "spec", "containers"}
initContainersFields = []string{"spec", "jobTemplate", "spec", "template", "spec", "initContainers"}
case "Pod":
nestedFields = []string{"spec", "containers"}
containersFields = []string{"spec", "containers"}
initContainersFields = []string{"spec", "initContainers"}
case "DaemonSet", "Deployment", "Job", "ReplicaSet", "ReplicationController", "StatefulSet":
nestedFields = []string{"spec", "template", "spec", "containers"}
containersFields = []string{"spec", "template", "spec", "containers"}
initContainersFields = []string{"spec", "template", "spec", "initContainers"}
default:
continue
}

cons, ok, err := unstructured.NestedFieldNoCopy(obj.Object, nestedFields...)
if err != nil {
return fmt.Errorf("failed to get nested containers field: %v", err)
}
if !ok {
continue
}
consList, ok := cons.([]interface{})
if !ok {
return fmt.Errorf("failed to convert containers to list")
}

for _, con := range consList {
conMap, ok := con.(map[string]interface{})
if !ok {
return fmt.Errorf("failed to convert container to map")
}
im, ok, err := unstructured.NestedString(conMap, "image")
for _, nestedFields := range [][]string{containersFields, initContainersFields} {
cons, ok, err := unstructured.NestedFieldNoCopy(obj.Object, nestedFields...)
if err != nil {
return fmt.Errorf("failed to get image field: %v", err)
return fmt.Errorf("failed to get nested containers field: %v", err)
}
if !ok {
continue
}

ref, err := name.ParseReference(im)
if err != nil {
return fmt.Errorf("failed to parse reference from image %q: %v", im, err)
consList, ok := cons.([]interface{})
if !ok {
return fmt.Errorf("failed to convert containers to list")
}
if image.Name(ref) == imageName {
fmt.Printf("Updating container of resource: %v\n", obj)
if err := unstructured.SetNestedField(conMap, replace, "image"); err != nil {
return fmt.Errorf("failed to set image field: %v", err)

for _, con := range consList {
conMap, ok := con.(map[string]interface{})
if !ok {
return fmt.Errorf("failed to convert container to map")
}
im, ok, err := unstructured.NestedString(conMap, "image")
if err != nil {
return fmt.Errorf("failed to get image field: %v", err)
}
if !ok {
continue
}

ref, err := name.ParseReference(im)
if err != nil {
return fmt.Errorf("failed to parse reference from image %q: %v", im, err)
}
if image.Name(ref) == imageName {
fmt.Printf("Updating container of resource: %v\n", obj)
if err := unstructured.SetNestedField(conMap, replace, "image"); err != nil {
return fmt.Errorf("failed to set image field: %v", err)
}
matched = true
}
matched = true
}
}
}
Expand Down Expand Up @@ -689,6 +695,16 @@ func ObjectKind(obj *Object) string {
return obj.GetObjectKind().GroupVersionKind().Kind
}

// ObjectGroupVersionKind returns the group/kind format for kubectl commands (e.g., "middleware.traefik.io" or just "pod").
// For resources with a group, returns "kind.group". For core API resources, returns just "kind".
func ObjectGroupVersionKind(obj *Object) string {
gvk := obj.GetObjectKind().GroupVersionKind()
if gvk.Group == "" {
return gvk.Kind
}
return strings.ToLower(gvk.Kind) + "." + gvk.Group
}

// ObjectName returns the name of an object.
func ObjectName(obj *Object) (string, error) {
accessor, err := meta.Accessor(obj)
Expand Down
17 changes: 9 additions & 8 deletions gke-deploy/deployer/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,13 @@ func (d *Deployer) Apply(ctx context.Context, clusterName, clusterLocation, clus
}
if !exists {
fmt.Fprintf(os.Stderr, "\nWARNING: It is recommended that namespaces be created by an administrator. Creating namespace %q because it does not exist.\n\n", nsName)
objString, err := resource.EncodeToYAMLString(obj)
if err != nil {
return fmt.Errorf("failed to encode obj to string")
}
if err := cluster.ApplyConfigFromString(ctx, objString, "", d.Clients.Kubectl); err != nil {
return fmt.Errorf("failed to apply Namespace configuration file with name %q to cluster: %v", nsName, err)
}
}
objString, err := resource.EncodeToYAMLString(obj)
if err != nil {
return fmt.Errorf("failed to encode obj to string")
}
if err := cluster.ApplyConfigFromString(ctx, objString, "", d.Clients.Kubectl); err != nil {
return fmt.Errorf("failed to apply Namespace configuration file with name %q to cluster: %v", nsName, err)
}
} else {
// Delete namespace from list of objects to be deployed because it has already been deployed we do not want it to show up in the deployment summary.
Expand Down Expand Up @@ -471,6 +471,7 @@ func (d *Deployer) Apply(ctx context.Context, clusterName, clusterLocation, clus

for _, obj := range objs {
kind := resource.ObjectKind(obj)
gvk := resource.ObjectGroupVersionKind(obj)
name, err := resource.ObjectName(obj)
if err != nil {
return fmt.Errorf("failed to get name of object: %v", err)
Expand All @@ -485,7 +486,7 @@ func (d *Deployer) Apply(ctx context.Context, clusterName, clusterLocation, clus
} else {
objNamespace = namespace
}
deployedObj, err := cluster.GetDeployedObject(ctx, kind, name, objNamespace, d.Clients.Kubectl)
deployedObj, err := cluster.GetDeployedObject(ctx, gvk, name, objNamespace, d.Clients.Kubectl)
if err != nil {
return fmt.Errorf("failed to get configuration of deployed object with kind %q and name %q: %v", kind, name, err)
}
Expand Down
8 changes: 8 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Tool builder: `gcr.io/cloud-builders/go`

## ⚠️ DEPRECATED

**This builder is deprecated.** Please migrate to the official [`golang`](https://hub.docker.com/_/golang) image instead.

The `gcr.io/cloud-builders/go` image is no longer actively maintained and does not support current Go versions with security updates. The official `golang` image is actively maintained by the Docker community and provides up-to-date Go tooling and versions.

---

The `gcr.io/cloud-builders/go` image is maintained by the Cloud Build team, but
it may not support the most recent features or versions of Go. We also do not
provide historical pinned versions of Go.
Expand Down
Loading