|  | 
|  | 1 | +#!/usr/bin/env bash | 
|  | 2 | + | 
|  | 3 | +# Copyright 2025 Google LLC. | 
|  | 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 SCRIPT_DIR to the current directory of this file. | 
|  | 18 | +SCRIPT_DIR=$(cd -P "$(dirname "$0")" >/dev/null 2>&1 && pwd) | 
|  | 19 | +SCRIPT_FILE="${SCRIPT_DIR}/$(basename "$0")" | 
|  | 20 | + | 
|  | 21 | +## | 
|  | 22 | +## Local Development | 
|  | 23 | +## | 
|  | 24 | +## These functions should be used to run the local development process | 
|  | 25 | +## | 
|  | 26 | + | 
|  | 27 | +## clean - Cleans the build output | 
|  | 28 | +function clean() { | 
|  | 29 | +  if [[ -d '.tools' ]] ; then | 
|  | 30 | +    rm -rf .tools | 
|  | 31 | +  fi | 
|  | 32 | +} | 
|  | 33 | + | 
|  | 34 | +## build - Builds the project without running tests. | 
|  | 35 | +function build() { | 
|  | 36 | +   go build ./... | 
|  | 37 | +} | 
|  | 38 | + | 
|  | 39 | +## test - Runs local unit tests. | 
|  | 40 | +function test() { | 
|  | 41 | +  go test -v -race -cover -short | 
|  | 42 | +} | 
|  | 43 | + | 
|  | 44 | +## e2e - Runs end-to-end integration tests. | 
|  | 45 | +function e2e() { | 
|  | 46 | +  if [[ ! -f .envrc ]] ; then | 
|  | 47 | +    write_e2e_env .envrc | 
|  | 48 | +  fi | 
|  | 49 | +  source .envrc | 
|  | 50 | +  e2e_ci | 
|  | 51 | +} | 
|  | 52 | + | 
|  | 53 | +# e2e_ci - Run end-to-end integration tests in the CI system. | 
|  | 54 | +#   This assumes that the secrets in the env vars are already set. | 
|  | 55 | +function e2e_ci() { | 
|  | 56 | +  go test -race -v ./... | tee test_results.txt | 
|  | 57 | +} | 
|  | 58 | + | 
|  | 59 | +function get_golang_tool() { | 
|  | 60 | +  name="$1" | 
|  | 61 | +  github_repo="$2" | 
|  | 62 | +  package="$3" | 
|  | 63 | + | 
|  | 64 | +  # Download goimports tool | 
|  | 65 | +  version=$(curl -s "https://api.github.com/repos/$github_repo/tags" | jq -r '.[].name' | head -n 1) | 
|  | 66 | +  mkdir -p "$SCRIPT_DIR/.tools" | 
|  | 67 | +  cmd="$SCRIPT_DIR/.tools/$name" | 
|  | 68 | +  versioned_cmd="$SCRIPT_DIR/.tools/$name-$version" | 
|  | 69 | +  if [[ ! -f "$versioned_cmd" ]] ; then | 
|  | 70 | +    GOBIN="$SCRIPT_DIR/.tools" go install "$package@$version" | 
|  | 71 | +    mv "$cmd" "$versioned_cmd" | 
|  | 72 | +    if [[ -f "$cmd" ]] ; then | 
|  | 73 | +      unlink "$cmd" | 
|  | 74 | +    fi | 
|  | 75 | +    ln -s "$versioned_cmd" "$cmd" | 
|  | 76 | +  fi | 
|  | 77 | +} | 
|  | 78 | + | 
|  | 79 | +## fix - Fixes code format. | 
|  | 80 | +function fix() { | 
|  | 81 | +  # run code formatting | 
|  | 82 | +  get_golang_tool 'goimports' 'golang/tools' 'golang.org/x/tools/cmd/goimports' | 
|  | 83 | +  ".tools/goimports" -w . | 
|  | 84 | +  go mod tidy | 
|  | 85 | +  go fmt ./... | 
|  | 86 | +} | 
|  | 87 | + | 
|  | 88 | +## lint - runs the linters | 
|  | 89 | +function lint() { | 
|  | 90 | +  # run lint checks | 
|  | 91 | +  get_golang_tool 'golangci-lint' 'golangci/golangci-lint' 'github.com/golangci/golangci-lint/v2/cmd/golangci-lint' | 
|  | 92 | +  ".tools/golangci-lint" run --timeout 3m | 
|  | 93 | + | 
|  | 94 | +  # Check the commit includes a go.mod that is fully | 
|  | 95 | +  # up to date. | 
|  | 96 | +  fix | 
|  | 97 | +  if [[ -d "$SCRIPT_DIR/.git" ]] ; then | 
|  | 98 | +    git diff --exit-code | 
|  | 99 | +  fi | 
|  | 100 | +} | 
|  | 101 | + | 
|  | 102 | +# lint_ci - runs lint in the CI build job, exiting with an error code if lint fails. | 
|  | 103 | +function lint_ci() { | 
|  | 104 | +  lint # run lint | 
|  | 105 | +  git diff --exit-code # fail if any files changed | 
|  | 106 | +} | 
|  | 107 | + | 
|  | 108 | +## deps - updates project dependencies to latest | 
|  | 109 | +function deps() { | 
|  | 110 | +  go get -u ./... | 
|  | 111 | +  go get -t -u ./... | 
|  | 112 | + | 
|  | 113 | +  # Update the image label in the dockerfiles | 
|  | 114 | +  for n in Dockerfile Dockerfile.* ; do | 
|  | 115 | +    dockerfile_from_deps "$n" | 
|  | 116 | +  done | 
|  | 117 | +} | 
|  | 118 | + | 
|  | 119 | +# find | 
|  | 120 | +function dockerfile_from_deps() { | 
|  | 121 | +  # FROM gcr.io/distroless/static:nonroot@sha256:627d6c5a23ad24e6bdff827f16c7b60e0289029b0c79e9f7ccd54ae3279fb45f | 
|  | 122 | +  # curl -X GET https://gcr.io/v2/distroless/static/manifests/nonroot | 
|  | 123 | +  file=$1 | 
|  | 124 | + | 
|  | 125 | +  # Get the last FROM statement from the dockerfile | 
|  | 126 | +  # those ar | 
|  | 127 | +  fromLine=$(grep "FROM" $1 | tail -n1) | 
|  | 128 | +  imageUrl="${fromLine#FROM *}" | 
|  | 129 | + | 
|  | 130 | +  # If the image URL does not contain a hash, then don't do anything. | 
|  | 131 | +  if [[ $imageUrl != *@* ]] ; then | 
|  | 132 | +    echo "Image does not contain a digest, ignoring" | 
|  | 133 | +    return | 
|  | 134 | +  fi | 
|  | 135 | + | 
|  | 136 | +  oldDigest="${imageUrl#*@}" #after the '@' | 
|  | 137 | +  imageWithoutHash="${imageUrl%%@sha256*}" #before the '@sha256' | 
|  | 138 | +  imageName="${imageWithoutHash%%:*}" #before the ':' | 
|  | 139 | + | 
|  | 140 | +  imageLabel="${imageWithoutHash#*:}" #after the ':' | 
|  | 141 | +  # If none found, use "latest" as the label | 
|  | 142 | +  if [[ "$imageLabel" == "$imageName" ]] ; then | 
|  | 143 | +    imageLabel=latest | 
|  | 144 | +  fi | 
|  | 145 | + | 
|  | 146 | +  imageRepo="${imageName%%/*}" #first part of the image name path, may be a repo hostname | 
|  | 147 | +  if [[ "$imageRepo" == *.* ]]; then | 
|  | 148 | +    imageName="${imageName#*/}" # trim repo name host from imageName | 
|  | 149 | +    manifestUrl="https://${imageRepo}/v2/${imageName}/manifests/${imageLabel}" | 
|  | 150 | +    digest=$(curl -X GET "$manifestUrl" | \ | 
|  | 151 | +      jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest') | 
|  | 152 | + | 
|  | 153 | +  else | 
|  | 154 | +    # registry-1.docker.io requires a token | 
|  | 155 | +    docker_io_token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/alpine:pull" | jq -r .token) | 
|  | 156 | +    manifestUrl="https://registry-1.docker.io/v2/${imageName}/manifests/${imageLabel}" | 
|  | 157 | +    digest=$(curl -s -H "Authorization: Bearer $docker_io_token" \ | 
|  | 158 | +         -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \ | 
|  | 159 | +         https://registry-1.docker.io/v2/library/alpine/manifests/3 | \ | 
|  | 160 | +          jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest') | 
|  | 161 | +  fi | 
|  | 162 | + | 
|  | 163 | +  if [[ "$oldDigest" == "$digest" ]] ; then | 
|  | 164 | +    echo "No update to image to $file" | 
|  | 165 | +  else | 
|  | 166 | +    echo "Updating docker image to $file to $digest" | 
|  | 167 | +    set -x | 
|  | 168 | +    sed -i "" "s/$oldDigest/$digest/g" "$file" | 
|  | 169 | +  fi | 
|  | 170 | + | 
|  | 171 | +} | 
|  | 172 | + | 
|  | 173 | +# write_e2e_env - Loads secrets from the gcloud project and writes | 
|  | 174 | +#     them to target/e2e.env to run e2e tests. | 
|  | 175 | +function write_e2e_env(){ | 
|  | 176 | +  # All secrets used by the e2e tests in the form <env_name>=<secret_name> | 
|  | 177 | +  secret_vars=( | 
|  | 178 | +    MYSQL_CONNECTION_NAME=MYSQL_CONNECTION_NAME | 
|  | 179 | +    MYSQL_USER=MYSQL_USER | 
|  | 180 | +    MYSQL_PASS=MYSQL_PASS | 
|  | 181 | +    MYSQL_DB=MYSQL_DB | 
|  | 182 | +    MYSQL_MCP_CONNECTION_NAME=MYSQL_MCP_CONNECTION_NAME | 
|  | 183 | +    MYSQL_MCP_PASS=MYSQL_MCP_PASS | 
|  | 184 | +    POSTGRES_CONNECTION_NAME=POSTGRES_CONNECTION_NAME | 
|  | 185 | +    POSTGRES_USER=POSTGRES_USER | 
|  | 186 | +    POSTGRES_USER_IAM=POSTGRES_USER_IAM | 
|  | 187 | +    POSTGRES_PASS=POSTGRES_PASS | 
|  | 188 | +    POSTGRES_DB=POSTGRES_DB | 
|  | 189 | +    POSTGRES_CAS_CONNECTION_NAME=POSTGRES_CAS_CONNECTION_NAME | 
|  | 190 | +    POSTGRES_CAS_PASS=POSTGRES_CAS_PASS | 
|  | 191 | +    POSTGRES_CUSTOMER_CAS_CONNECTION_NAME=POSTGRES_CUSTOMER_CAS_CONNECTION_NAME | 
|  | 192 | +    POSTGRES_CUSTOMER_CAS_PASS=POSTGRES_CUSTOMER_CAS_PASS | 
|  | 193 | +    POSTGRES_CUSTOMER_CAS_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_DOMAIN_NAME | 
|  | 194 | +    POSTGRES_MCP_CONNECTION_NAME=POSTGRES_MCP_CONNECTION_NAME | 
|  | 195 | +    POSTGRES_MCP_PASS=POSTGRES_MCP_PASS | 
|  | 196 | +    SQLSERVER_CONNECTION_NAME=SQLSERVER_CONNECTION_NAME | 
|  | 197 | +    SQLSERVER_USER=SQLSERVER_USER | 
|  | 198 | +    SQLSERVER_PASS=SQLSERVER_PASS | 
|  | 199 | +    SQLSERVER_DB=SQLSERVER_DB | 
|  | 200 | +    IMPERSONATED_USER=IMPERSONATED_USER | 
|  | 201 | +  ) | 
|  | 202 | + | 
|  | 203 | +  if [[ -z "$TEST_PROJECT" ]] ; then | 
|  | 204 | +    echo "Set TEST_PROJECT environment variable to the project containing" | 
|  | 205 | +    echo "the e2e test suite secrets." | 
|  | 206 | +    exit 1 | 
|  | 207 | +  fi | 
|  | 208 | + | 
|  | 209 | +  local_user=$(gcloud auth list --format 'value(account)' | tr -d '\n') | 
|  | 210 | + | 
|  | 211 | +  echo "Getting test secrets from $TEST_PROJECT into $1" | 
|  | 212 | +  { | 
|  | 213 | +  for env_name in "${secret_vars[@]}" ; do | 
|  | 214 | +    env_var_name="${env_name%%=*}" | 
|  | 215 | +    secret_name="${env_name##*=}" | 
|  | 216 | +    set -x | 
|  | 217 | +    val=$(gcloud secrets versions access latest --project "$TEST_PROJECT" --secret="$secret_name") | 
|  | 218 | +    echo "export $env_var_name='$val'" | 
|  | 219 | +  done | 
|  | 220 | + | 
|  | 221 | +  # Set IAM User env vars to the local gcloud user | 
|  | 222 | +  echo "export MYSQL_IAM_USER='${local_user%%@*}'" | 
|  | 223 | +  echo "export POSTGRES_USER_IAM='$local_user'" | 
|  | 224 | +  } > "$1" | 
|  | 225 | + | 
|  | 226 | +} | 
|  | 227 | + | 
|  | 228 | +## help - prints the help details | 
|  | 229 | +## | 
|  | 230 | +function help() { | 
|  | 231 | +   # This will print the comments beginning with ## above each function | 
|  | 232 | +   # in this file. | 
|  | 233 | + | 
|  | 234 | +   echo "build.sh <command> <arguments>" | 
|  | 235 | +   echo | 
|  | 236 | +   echo "Commands to assist with local development and CI builds." | 
|  | 237 | +   echo | 
|  | 238 | +   echo "Commands:" | 
|  | 239 | +   echo | 
|  | 240 | +   grep -e '^##' "$SCRIPT_FILE" | sed -e 's/##/ /' | 
|  | 241 | +} | 
|  | 242 | + | 
|  | 243 | +set -euo pipefail | 
|  | 244 | + | 
|  | 245 | +# Check CLI Arguments | 
|  | 246 | +if [[ "$#" -lt 1 ]] ; then | 
|  | 247 | +  help | 
|  | 248 | +  exit 1 | 
|  | 249 | +fi | 
|  | 250 | + | 
|  | 251 | +cd "$SCRIPT_DIR" | 
|  | 252 | + | 
|  | 253 | +"$@" | 
|  | 254 | + | 
0 commit comments