From 3213c7fd133d2b61e8779f53a555b183d72ae602 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 01:22:42 +0000 Subject: [PATCH 1/2] build: migrate mage gobuild to a mise task Move mage's `Gobuild` target to a new mise task, `.mise/tasks/build/go`, as plain shell backed by shell/lib helpers (get_app_version, get_cgo_enabled, read_local_secret, get_box_field) -- the same pattern already used for the `version` and `docker-build` migrations. No Go code was needed for this migration, so `Gobuild` and the helpers only it used (readSecret, getAppVersion, getAppName, getLDFlagsStringFromMap) are removed from the mage codebase. `make gobuild` becomes a deprecation-warning wrapper around `mise run build:go`, matching the existing version/docker-build pattern; `make build` and `make devspace` call the mise task directly so they don't pay that warning's delay. The CircleCI cache-warming job now calls `mise run build:go` directly instead of through `make build`. CGO_ENABLED detection is deduplicated into a new shell/lib/bootstrap.sh helper, get_cgo_enabled, used by both shell/cgo-enabled.sh and the new task. Refs: DT-4635 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014x7tHL2QpGrPgZDfzLKXyp --- .mise/tasks/build/go | 78 ++++++++++++++++++++++++++++ orbs/shared/jobs/save_e2e_cache.yaml | 2 +- root/Magefile.go | 67 ------------------------ root/Makefile | 9 ++-- root/app.go | 55 -------------------- root/go.go | 9 ---- shell/cgo-enabled.sh | 7 +-- shell/lib/bootstrap.sh | 10 ++++ shell/lib/bootstrap_test.bats | 26 ++++++++++ shell/lib/secrets.sh | 42 +++++++++++++++ shell/lib/secrets_test.bats | 62 ++++++++++++++++++++++ 11 files changed, 225 insertions(+), 142 deletions(-) create mode 100755 .mise/tasks/build/go delete mode 100644 root/app.go create mode 100644 shell/lib/secrets.sh create mode 100644 shell/lib/secrets_test.bats diff --git a/.mise/tasks/build/go b/.mise/tasks/build/go new file mode 100755 index 000000000..70e578728 --- /dev/null +++ b/.mise/tasks/build/go @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +#MISE description="Builds the application binary via 'go build'." + +set -euo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +LIB_DIR="$DIR/../../../shell/lib" + +# shellcheck source=../../../shell/lib/bootstrap.sh +source "$LIB_DIR/bootstrap.sh" +# shellcheck source=../../../shell/lib/logging.sh +source "$LIB_DIR/logging.sh" +# shellcheck source=../../../shell/lib/box.sh +source "$LIB_DIR/box.sh" +# shellcheck source=../../../shell/lib/secrets.sh +source "$LIB_DIR/secrets.sh" + +if [[ -d cmd ]]; then + buildPath="./cmd" +elif [[ -d plugin ]]; then + buildPath="./plugin" +else + warn "This repository produces no artifacts (no 'cmd' or 'plugin' directory found)" + exit 0 +fi + +mkdir -p bin + +# CGO_ENABLED: honor an already-set value (Makefile override, or a direct +# caller like the CircleCI orb that doesn't go through Makefile at all); +# otherwise derive it from service.yaml's enableCgo argument via the same +# get_cgo_enabled helper shell/cgo-enabled.sh uses. +: "${CGO_ENABLED:=$(get_cgo_enabled)}" +export CGO_ENABLED + +appName="$(get_app_name)" + +read_secret_or_warn() { + local path="$1" label="$2" + local val + val="$(read_local_secret "$path" "$appName" 2>/dev/null || true)" + if [[ -z $val ]]; then + warn "Failed to get $label api key (did you run .bootstrap/shell/devconfig.sh?)" >&2 + fi + echo "$val" +} + +honeycombKey="$(read_secret_or_warn "honeycomb/apiKey" honeycomb)" +teleforkKey="$(read_secret_or_warn "telefork/api-keys/default" telefork)" + +ldflags="-X github.com/getoutreach/gobox/pkg/app.Version=$(get_app_version)" +ldflags+=" -X main.HoneycombTracingKey=${honeycombKey}" +ldflags+=" -X main.TeleforkAPIKey=${teleforkKey}" +if [[ -z ${DLV_PORT:-} ]]; then + ldflags+=" -w -s" +fi + +goFlags="" +if [[ -z ${KUBERNETES_SERVICE_HOST:-} ]]; then + goFlags="-tags=or_dev" +fi + +args=(build -v -o bin -ldflags "$ldflags") +if [[ -n ${GC_FLAGS:-} ]]; then + args+=(-gcflags "$GC_FLAGS") +fi +if [[ ${SKIP_TRIMPATH:-} != "true" ]]; then + args+=(-trimpath) +fi +args+=("$buildPath/...") + +envArgs=(GOFLAGS="$goFlags" GOPRIVATE="github.com/$(get_box_field org)/*") +if [[ -n ${BUILD_FOR_GOOS:-} ]]; then + envArgs+=(GOOS="$BUILD_FOR_GOOS") +fi + +info "Building..." +env "${envArgs[@]}" go "${args[@]}" diff --git a/orbs/shared/jobs/save_e2e_cache.yaml b/orbs/shared/jobs/save_e2e_cache.yaml index f25c76d55..11d806ac8 100644 --- a/orbs/shared/jobs/save_e2e_cache.yaml +++ b/orbs/shared/jobs/save_e2e_cache.yaml @@ -69,7 +69,7 @@ steps: install_e2e_tools: true - run: name: Build to populate Go build/module caches - command: make build + command: mise run --quiet build:go # E2E cache with mise tool installs and Go caches for machine-executor jobs - save_cache: key: v1-e2e-daily-cache-{{ arch }}-{{ epoch }} diff --git a/root/Magefile.go b/root/Magefile.go index d173abd82..4c17ec674 100644 --- a/root/Magefile.go +++ b/root/Magefile.go @@ -58,70 +58,3 @@ func ensureBinDirExists(cwd string) (string, error) { } return binDir, nil } - -// GoBuild builds a Go project -func Gobuild(ctx context.Context) error { - cwd, err := os.Getwd() - if err != nil { - return err - } - - // TODO(jaredallard)[DT-2796]: This is a hack to get around the fact that plugins - // still don't implement the commands framework. Can remove when DT-2796 is done. - _, cmdErr := os.Stat("cmd") - _, pluginDirErr := os.Stat("plugin") - if cmdErr != nil && pluginDirErr != nil { - log.Warn().Msg("This repository produces no artifacts (no 'cmd' or 'plugin' directory found)") - return nil - } - binDir, err := ensureBinDirExists(cwd) - if err != nil { - return err - } - - honeycombKey, err := readSecret(ctx, "honeycomb/apiKey") - if err != nil { - log.Warn().Err(err).Msg("Failed to get honeycomb api key (did you run .bootstrap/shell/devconfig.sh?)") - } - - teleforkKey, err := readSecret(ctx, "telefork/api-keys/default") - if err != nil { - log.Warn().Err(err).Msg("Failed to get telefork api key (did you run .bootstrap/shell/devconfig.sh?)") - } - - ldFlags := getLDFlagsStringFromMap(map[string]string{ - "github.com/getoutreach/gobox/pkg/app.Version": getAppVersion(), - "main.HoneycombTracingKey": string(honeycombKey), - "main.TeleforkAPIKey": string(teleforkKey), - }) - if os.Getenv("DLV_PORT") == "" { - // When not running in DLV, strip out symbols - ldFlags += "-w -s" - } - - log.Info().Msg("Building...") - - // TODO(jaredallard)[DT-2796]: This is a hack to get around the fact that plugins - // still don't implement the commands framework. Can remove when DT-2796 is done. - buildPath := "./cmd" - if pluginDirErr == nil { - buildPath = "./plugin" - } - - args := []string{"build", "-v", "-o", binDir, "-ldflags", ldFlags} - if gcFlags := os.Getenv("GC_FLAGS"); gcFlags != "" { - args = append(args, "-gcflags", gcFlags) - } - - // SKIP_TRIMPATH is used for devspace binary sync, where you want to have same file paths for delve to work correctly - if os.Getenv("SKIP_TRIMPATH") == "true" { - log.Debug().Msg("Skipping trimpath argument for go build") - } else { - // Build with -trimpath to ensure we have consistent module filenames embedded. - args = append(args, "-trimpath") - } - - args = append(args, buildPath+"/...") - - return runGoCommand(log, args...) -} diff --git a/root/Makefile b/root/Makefile index 54e6825df..ddc7132c5 100644 --- a/root/Makefile +++ b/root/Makefile @@ -107,7 +107,8 @@ pre-commit: fmt ## build: run codegen and build application binary .PHONY: build -build:: pre-build gobuild +build:: pre-build + @CGO_ENABLED=$(CGO_ENABLED) mise run --quiet build:go ## lint: run code linters .PHONY: lint @@ -157,10 +158,10 @@ gogenerate:: pre-gogenerate @$(LOG) info "Running gogenerate" @GOPROXY=$(GOPROXY) GOPRIVATE=$(GOPRIVATE) $(GO) generate ./... -## gobuild: build application binary +## gobuild: [DEPRECATED] build application binary .PHONY: gobuild gobuild: - @CGO_ENABLED=$(CGO_ENABLED) $(MAGE_CMD) gobuild + @./scripts/shell-wrapper.sh deprecated-task.sh gobuild build:go ## grpcui: run grpcui for an already locally running service .PHONY: grpcui @@ -181,7 +182,7 @@ devspace-watch: ## devspace: build sources for linux with debugging symbols. To be used with devspace using `devenv apps run --sync-binaries .` .PHONY: devspace devspace: - @DEVBOX_LOGFMT="$(LOGFMT)" BUILD_FOR_GOOS="linux" CGO_ENABLED=$(CGO_ENABLED) SKIP_TRIMPATH="true" SKIP_STARTING_APP="true" DLV_PORT=42097 GC_FLAGS="all=-N -l" $(MAGE_CMD) gobuild + @DEVBOX_LOGFMT="$(LOGFMT)" BUILD_FOR_GOOS="linux" CGO_ENABLED=$(CGO_ENABLED) SKIP_TRIMPATH="true" SKIP_STARTING_APP="true" DLV_PORT=42097 GC_FLAGS="all=-N -l" mise run --quiet build:go @DEVBOX_LOGFMT="$(LOGFMT)" BUILD_FOR_GOOS="linux" CGO_ENABLED=$(CGO_ENABLED) $(MAGE_CMD) e2etestbuild @echo "Use 'devenv apps run --sync-binaries .' to sync binaries to devspace pod" diff --git a/root/app.go b/root/app.go deleted file mode 100644 index ba033a996..000000000 --- a/root/app.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build mage - -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/getoutreach/gobox/pkg/cfg" - "github.com/magefile/mage/sh" -) - -// getAppVersion returns the current application version, or pseudo-version if -// not aligned with a tag -func getAppVersion() string { - version, err := sh.Output("git", "describe", "--match", "v[0-9]*", "--tags", "--always", "HEAD") - if err != nil { - return "0.0.0-dev" - } - - return version -} - -// getAppName returns the app name -func getAppName() string { - cwd, err := os.Getwd() - if err != nil { - return "unknown" - } - return filepath.Base(cwd) -} - -// readSecret reads a secret from well-defined paths on the user's machine -func readSecret(ctx context.Context, path string) (cfg.SecretData, error) { - appName := getAppName() - homeDir, err := os.UserHomeDir() - if err != nil { - return "", err - } - - lookupPaths := []string{ - "/run/secrets/outreach.io", - filepath.Join(homeDir, ".outreach", appName), - } - for _, p := range lookupPaths { - secretPath := filepath.Join(p, path) - if _, err := os.Stat(secretPath); err == nil { - return cfg.Secret{Path: secretPath}.Data(ctx) - } - } - - return "", fmt.Errorf("failed to find secret at any of %v", lookupPaths) -} diff --git a/root/go.go b/root/go.go index d84aba94b..925b85646 100644 --- a/root/go.go +++ b/root/go.go @@ -86,12 +86,3 @@ func runGoCommand(log zerolog.Logger, args ...string) error { return sh.RunWith(vars, "go", args...) } - -// getLDFlagsStringFromMap returns a string of all the ldflags from the given map -func getLDFlagsStringFromMap(ldflags map[string]string) string { - ldFlags := "" - for k, v := range ldflags { - ldFlags += fmt.Sprintf("-X %s=%s ", k, v) - } - return ldFlags -} diff --git a/shell/cgo-enabled.sh b/shell/cgo-enabled.sh index 4fa6c535f..93f72d957 100755 --- a/shell/cgo-enabled.sh +++ b/shell/cgo-enabled.sh @@ -4,13 +4,8 @@ # value for the CGO_ENABLED environment variable. Defaults to disabled. DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -YQ="$DIR/yq.sh" # shellcheck source=./lib/bootstrap.sh source "$DIR/lib/bootstrap.sh" -if [[ "$("$YQ" -r ".arguments.enableCgo" <"$(get_service_yaml)")" == "true" ]]; then - echo "1" -else - echo "0" -fi +get_cgo_enabled diff --git a/shell/lib/bootstrap.sh b/shell/lib/bootstrap.sh index 7e13ba581..e43a6d911 100755 --- a/shell/lib/bootstrap.sh +++ b/shell/lib/bootstrap.sh @@ -94,6 +94,16 @@ has_feature() { return 1 } +# get_cgo_enabled returns "1" if service.yaml has explicitly enabled cgo +# via its enableCgo argument, or "0" otherwise. +get_cgo_enabled() { + if has_feature "enableCgo"; then + echo "1" + else + echo "0" + fi +} + get_service_yaml() { if [[ -e "service.yaml" ]]; then echo "service.yaml" diff --git a/shell/lib/bootstrap_test.bats b/shell/lib/bootstrap_test.bats index d77df3d0a..670b9a0d0 100644 --- a/shell/lib/bootstrap_test.bats +++ b/shell/lib/bootstrap_test.bats @@ -68,6 +68,32 @@ EOF assert_output "baz" } +@test "get_cgo_enabled returns 1 when service.yaml enables cgo" { + cat >"$REPOPATH"/service.yaml <"$REPOPATH"/service.yaml <"$REPOPATH"/service.yaml <"$REPOPATH"/stencil.lock </dev/null 2>&1 && pwd)" + +# shellcheck source=./bootstrap.sh +source "$LIB_DIR/bootstrap.sh" + +# OUTREACH_SECRETS_DIR is the first well-known location read_local_secret +# checks. Overridable (like box.sh's BOXPATH) so tests don't have to write +# into the real /run/secrets/outreach.io. +OUTREACH_SECRETS_DIR="${OUTREACH_SECRETS_DIR:-/run/secrets/outreach.io}" + +# OUTREACH_LOCAL_SECRETS_DIR is the base of the second, fallback location +# read_local_secret checks. Defaults to ~/.outreach (like devconfig.sh's +# convention), but is independently overridable (like OUTREACH_SECRETS_DIR +# above) so tests can point it elsewhere without reassigning the real +# $HOME (which mise's shims resolve tools against). +OUTREACH_LOCAL_SECRETS_DIR="${OUTREACH_LOCAL_SECRETS_DIR:-$HOME/.outreach}" + +# read_local_secret reads a secret from the well-known local paths used by +# devconfig.sh: $OUTREACH_SECRETS_DIR/, falling back to +# $OUTREACH_LOCAL_SECRETS_DIR//. Prints nothing and returns +# non-zero if not found in either location. appName defaults to +# get_app_name, but can be passed explicitly to avoid re-invoking it when +# reading multiple secrets in the same script. +read_local_secret() { + local path="$1" + local appName="${2:-$(get_app_name)}" + + local candidates=( + "$OUTREACH_SECRETS_DIR/$path" + "$OUTREACH_LOCAL_SECRETS_DIR/$appName/$path" + ) + for candidate in "${candidates[@]}"; do + if [[ -e $candidate ]]; then + cat "$candidate" + return 0 + fi + done + return 1 +} diff --git a/shell/lib/secrets_test.bats b/shell/lib/secrets_test.bats new file mode 100644 index 000000000..3be518a14 --- /dev/null +++ b/shell/lib/secrets_test.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bats + +bats_load_library "bats-support/load.bash" +bats_load_library "bats-assert/load.bash" + +load secrets.sh +load test_helper.sh + +setup() { + REPOPATH=$(mktempdir devbase-lib-secrets-XXXXXX) + cd "$REPOPATH" || exit 1 + echo "name: myapp" >service.yaml + + OUTREACH_SECRETS_DIR=$(mktempdir devbase-lib-secrets-run-XXXXXX) + OUTREACH_LOCAL_SECRETS_DIR=$(mktempdir devbase-lib-secrets-home-XXXXXX) +} + +teardown() { + rm -rf "$REPOPATH" "$OUTREACH_SECRETS_DIR" "$OUTREACH_LOCAL_SECRETS_DIR" +} + +@test "read_local_secret finds a secret under OUTREACH_SECRETS_DIR" { + mkdir -p "$OUTREACH_SECRETS_DIR/honeycomb" + printf 'abc123' >"$OUTREACH_SECRETS_DIR/honeycomb/apiKey" + + run read_local_secret "honeycomb/apiKey" + assert_success + assert_output "abc123" +} + +@test "read_local_secret falls back to OUTREACH_LOCAL_SECRETS_DIR/" { + mkdir -p "$OUTREACH_LOCAL_SECRETS_DIR/myapp/telefork/api-keys" + printf 'xyz789' >"$OUTREACH_LOCAL_SECRETS_DIR/myapp/telefork/api-keys/default" + + run read_local_secret "telefork/api-keys/default" + assert_success + assert_output "xyz789" +} + +@test "read_local_secret prefers OUTREACH_SECRETS_DIR over the home fallback" { + mkdir -p "$OUTREACH_SECRETS_DIR/honeycomb" "$OUTREACH_LOCAL_SECRETS_DIR/myapp/honeycomb" + printf 'from-run-secrets' >"$OUTREACH_SECRETS_DIR/honeycomb/apiKey" + printf 'from-home' >"$OUTREACH_LOCAL_SECRETS_DIR/myapp/honeycomb/apiKey" + + run read_local_secret "honeycomb/apiKey" + assert_output "from-run-secrets" +} + +@test "read_local_secret fails when the secret isn't found anywhere" { + run read_local_secret "honeycomb/apiKey" + assert_failure + assert_output "" +} + +@test "read_local_secret accepts an explicit appName, skipping get_app_name" { + mkdir -p "$OUTREACH_LOCAL_SECRETS_DIR/otherapp/honeycomb" + printf 'explicit-app' >"$OUTREACH_LOCAL_SECRETS_DIR/otherapp/honeycomb/apiKey" + + run read_local_secret "honeycomb/apiKey" "otherapp" + assert_success + assert_output "explicit-app" +} From 4fce31ab7948d6accb406e8288c7bf83dc08e768 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 02:53:26 +0000 Subject: [PATCH 2/2] build: require bash 5+ in the build:go mise task Match the convention used by other executable shell entrypoints (e.g. .mise/tasks/stencil/post/circleci-orb-sync) by sourcing shell/lib/shell.sh and calling ensure_bash_5_or_greater before running any logic. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014x7tHL2QpGrPgZDfzLKXyp --- .mise/tasks/build/go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.mise/tasks/build/go b/.mise/tasks/build/go index 70e578728..bb62ed142 100755 --- a/.mise/tasks/build/go +++ b/.mise/tasks/build/go @@ -10,11 +10,15 @@ LIB_DIR="$DIR/../../../shell/lib" source "$LIB_DIR/bootstrap.sh" # shellcheck source=../../../shell/lib/logging.sh source "$LIB_DIR/logging.sh" +# shellcheck source=../../../shell/lib/shell.sh +source "$LIB_DIR/shell.sh" # shellcheck source=../../../shell/lib/box.sh source "$LIB_DIR/box.sh" # shellcheck source=../../../shell/lib/secrets.sh source "$LIB_DIR/secrets.sh" +ensure_bash_5_or_greater + if [[ -d cmd ]]; then buildPath="./cmd" elif [[ -d plugin ]]; then