Files
ai-for-dummies/submitted-skills/Pedro Aranha/skills/ndo-repro/ndo-ship.sh
T
2026-09-04 00:34:53 -03:00

278 lines
11 KiB
Bash

#!/usr/bin/env bash
# Build a NDO service locally with Docker, push to artifactory, point a k8s deployment at it.
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/env.sh
source "$HERE/lib/env.sh"
REG="${NDO_REGISTRY:-artifactorycn.netcracker.com:17009}"
ART_USER="${NDO_ARTIFACTORY_USER:-$USER}"
PLATFORM="${NDO_PLATFORM:-linux/amd64}"
PROJECTS="${NDO_PROJECTS:-$HOME/projects}"
ENV_ALIAS="${NDO_ENV:-}"
SVC=""; DIR=""; TAG=""; TICKET=""; DFILE=""; TARGET="release"
YES=0; NOCACHE=0; SKIP_TESTS=0; TIMEOUT="10m"
die() { echo "ERROR: $*" >&2; exit 1; }
say() { echo "==> $*" >&2; }
usage() {
cat <<'USAGE'
ndo-ship.sh — local build → artifactory → k8s deploy for NDO services.
Commands:
doctor check docker/buildx/registry-login/kubectl
login docker login to artifactory (interactive)
tag <service> print the image ref that would be built
test <service> run unit tests only (maven, or docker --target test)
build <service> build the image (runs unit tests first unless --skip-tests)
push <service> push the last built (or --tag'd) image
deploy <service> -e ENV point <service>-v1 at the image + wait for rollout [needs --yes]
ship <service> -e ENV test → build → push → deploy → rollout wait [needs --yes]
status <service> -e ENV deployed image, replicas, pod state
rollback <service> -e ENV restore the image recorded before the last deploy [needs --yes]
pullsecret <service> -e ENV attach local docker creds as an imagePullSecret (ImagePullBackOff fix) [needs --yes]
Options:
-e, --env ALIAS target env (see: ndo-api.sh env ls). Ambiguous short names are rejected.
-t, --tag TAG image tag (default: UTC timestamp, always unique)
--ticket N UNM number for the repo name (default: parsed from git branch)
-d, --dir PATH service repo (default: $NDO_PROJECTS/<service>)
-f, --file FILE dockerfile (default: Dockerfile_local, falls back to Dockerfile)
--target STAGE build target (default: release; ignored if the dockerfile has no stages)
--platform P default linux/amd64 — do NOT drop this on an arm64 Mac
--skip-tests skip unit tests in build/ship
--no-cache docker build --no-cache
--timeout D rollout wait (default 10m)
-y, --yes confirm a cluster-mutating command (deploy/ship/rollback/pullsecret)
Image ref: $REG/<artifactory-user>/<service>_unm_<ticket>:<tag>
Env overrides: NDO_REGISTRY NDO_ARTIFACTORY_USER NDO_PLATFORM NDO_PROJECTS NDO_ENV
USAGE
}
parse_opts() {
while [ $# -gt 0 ]; do
case "$1" in
-e|--env) ENV_ALIAS="$2"; shift 2 ;;
-t|--tag) TAG="$2"; shift 2 ;;
--ticket) TICKET="$2"; shift 2 ;;
-d|--dir) DIR="$2"; shift 2 ;;
-f|--file) DFILE="$2"; shift 2 ;;
--target) TARGET="$2"; shift 2 ;;
--platform) PLATFORM="$2"; shift 2 ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
--skip-tests) SKIP_TESTS=1; shift ;;
--no-cache) NOCACHE=1; shift ;;
-y|--yes) YES=1; shift ;;
-*) die "unknown option $1" ;;
*) [ -z "$SVC" ] && SVC="$1" || die "unexpected arg $1"; shift ;;
esac
done
}
need_svc() { [ -n "$SVC" ] || die "no service given"; }
svc_dir() {
need_svc
[ -n "$DIR" ] || DIR="$PROJECTS/$SVC"
[ -d "$DIR" ] || die "service repo not found: $DIR (use --dir)"
echo "$DIR"
}
dockerfile() {
local d; d="$(svc_dir)"
if [ -n "$DFILE" ]; then [ -f "$d/$DFILE" ] || [ -f "$DFILE" ] || die "dockerfile not found: $DFILE"; echo "$DFILE"; return; fi
if [ -f "$d/Dockerfile_local" ]; then echo "Dockerfile_local"; return; fi
echo "Dockerfile"
echo "no Dockerfile_local in $d — using Dockerfile. If the build pulls shared/external artifacts, create Dockerfile_local (see reference/dockerfile-local.md)." >&2
}
ticket() {
[ -n "$TICKET" ] && { echo "$TICKET"; return; }
local d b; d="$(svc_dir)"
b=$(git -C "$d" branch --show-current 2>/dev/null || true)
if [[ "$b" =~ [Uu][Nn][Mm][-_]?([0-9]+) ]]; then echo "${BASH_REMATCH[1]}"; else echo "local"; fi
}
image_ref() {
need_svc
local t; t="${TAG:-$(date -u +%Y%m%d-%H%M%S)}"
echo "$REG/$ART_USER/${SVC}_unm_$(ticket):$t"
}
last_image_file() { mkdir -p "$NDO_CACHE/last-image"; echo "$NDO_CACHE/last-image/$SVC"; }
resolve_image() {
if [ -n "$TAG" ]; then image_ref; return; fi
local f; f="$(last_image_file)"
[ -s "$f" ] || die "no image built yet for $SVC — run 'build' first or pass --tag"
cat "$f"
}
confirm() {
[ "$YES" -eq 1 ] || die "'$1' mutates shared env '$ENV_ALIAS' (context $NDO_CTX, ns $NDO_NS). Re-run with --yes once the user has approved."
}
container_name() {
local names first
names=$(kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\n"}{end}')
if grep -qx "$SVC" <<<"$names"; then echo "$SVC"; else first=$(head -1 <<<"$names"); [ -n "$first" ] || die "no containers in $SVC-v1"; echo "$first"; fi
}
current_image() {
kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
-o jsonpath='{.spec.template.spec.containers[0].image}'
}
rollback_file() { mkdir -p "$NDO_CACHE/rollback"; echo "$NDO_CACHE/rollback/$(tr '/' '_' <<<"$ENV_ALIAS")__$SVC"; }
is_maven() { [ -f "$(svc_dir)/pom.xml" ]; }
is_go() { [ -f "$(svc_dir)/go.mod" ]; }
has_stages() { grep -qiE '^[[:space:]]*FROM .* AS ' "$(svc_dir)/$(dockerfile)"; }
copies_target() { grep -qE 'COPY .*target/' "$(svc_dir)/$(dockerfile)"; }
mvn_env() {
export JAVA_HOME="${JAVA_HOME:-/Library/Java/JavaVirtualMachines/jdk-25.0.2.jdk/Contents/Home}"
export PATH="$JAVA_HOME/bin:$PATH"
}
run_tests() {
local d; d="$(svc_dir)"
if is_maven; then
say "maven unit tests ($SVC)"
( mvn_env; cd "$d" && mvn -B test )
elif is_go && grep -qiE '^[[:space:]]*FROM .* AS test' "$d/$(dockerfile)"; then
say "docker test stage ($SVC)"
docker build --platform "$PLATFORM" -f "$d/$(dockerfile)" --target test -t "$SVC-test:local" "$d"
elif is_go; then
say "go test ($SVC)"
( cd "$d" && go test ./... )
else
say "no unit-test runner detected for $SVC — skipping"
fi
}
do_build() {
local d df img args=()
d="$(svc_dir)"; df="$(dockerfile)"; img="$(image_ref)"
[ "$SKIP_TESTS" -eq 1 ] || run_tests
# Java services copy target/*.jar into the image — package first.
if is_maven && copies_target; then
say "mvn package -DskipTests (jar for the image layer)"
( mvn_env; cd "$d" && mvn -B -DskipTests package )
fi
args=(build --platform "$PLATFORM" -f "$d/$df" -t "$img")
has_stages && grep -qiE "^[[:space:]]*FROM .* AS $TARGET\$" "$d/$df" && args+=(--target "$TARGET")
[ "$NOCACHE" -eq 1 ] && args+=(--no-cache)
args+=("$d")
say "docker ${args[*]}"
docker "${args[@]}"
echo "$img" > "$(last_image_file)"
echo "$img"
}
do_push() {
local img; img="$(resolve_image)"
say "docker push $img"
docker push "$img"
echo "$img"
}
do_deploy() {
local img c prev
env_resolve "$ENV_ALIAS"
confirm deploy
img="$(resolve_image)"
c="$(container_name)"
prev="$(current_image)"
echo "$prev" > "$(rollback_file)"
say "rollback point saved: $prev"
say "set image $SVC-v1/$c=$img (ctx=$NDO_CTX ns=$NDO_NS)"
kubectl --context="$NDO_CTX" -n "$NDO_NS" set image "deploy/$SVC-v1" "$c=$img"
kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT" || {
echo "--- rollout failed; pod events ---" >&2
kubectl --context="$NDO_CTX" -n "$NDO_NS" get pod -l "app=$SVC-v1" \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\t"}{range .status.containerStatuses[*]}{.state}{end}{"\n"}{end}' >&2
echo "ImagePullBackOff => node has no creds for $REG. Fix: ndo-ship.sh pullsecret $SVC -e $ENV_ALIAS --yes" >&2
return 1
}
do_status
}
do_status() {
env_resolve "$ENV_ALIAS"
need_svc
echo "env : $ENV_ALIAS (ctx=$NDO_CTX ns=$NDO_NS)"
echo "image : $(current_image)"
kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
-o custom-columns='READY:.status.readyReplicas,DESIRED:.spec.replicas,UPDATED:.status.updatedReplicas'
kubectl --context="$NDO_CTX" -n "$NDO_NS" get pod -l "app=$SVC-v1" \
-o custom-columns='POD:.metadata.name,PHASE:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount,AGE:.metadata.creationTimestamp'
}
do_rollback() {
local f prev c
env_resolve "$ENV_ALIAS"
confirm rollback
f="$(rollback_file)"
[ -s "$f" ] || die "no rollback point recorded for $SVC on $ENV_ALIAS"
prev="$(cat "$f")"; c="$(container_name)"
say "restoring $prev"
kubectl --context="$NDO_CTX" -n "$NDO_NS" set image "deploy/$SVC-v1" "$c=$prev"
kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT"
}
do_pullsecret() {
env_resolve "$ENV_ALIAS"
confirm pullsecret
local sec=ndo-repro-artifactory pw
pw=$(printf '%s' "$REG" | docker-credential-osxkeychain get 2>/dev/null \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["Secret"])') || die "no local docker creds for $REG — run: ndo-ship.sh login"
kubectl --context="$NDO_CTX" -n "$NDO_NS" create secret docker-registry "$sec" \
--docker-server="$REG" --docker-username="$ART_USER" --docker-password="$pw" \
--dry-run=client -o yaml | kubectl --context="$NDO_CTX" -n "$NDO_NS" apply -f -
unset pw
kubectl --context="$NDO_CTX" -n "$NDO_NS" patch deploy "$SVC-v1" \
-p "{\"spec\":{\"template\":{\"spec\":{\"imagePullSecrets\":[{\"name\":\"$sec\"}]}}}}"
kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT"
}
do_doctor() {
printf 'docker : %s\n' "$(docker version --format '{{.Server.Version}}' 2>&1 | head -1)"
printf 'context : %s\n' "$(docker context show 2>/dev/null)"
printf 'buildx : %s\n' "$(docker buildx version 2>&1 | head -1)"
printf 'host arch : %s (build platform %s)\n' "$(uname -m)" "$PLATFORM"
if printf '%s' "$REG" | docker-credential-osxkeychain get >/dev/null 2>&1; then
printf 'registry : logged in to %s as %s\n' "$REG" "$ART_USER"
else
printf 'registry : NOT logged in to %s — run: ndo-ship.sh login\n' "$REG"
fi
printf 'envs : %s\n' "$(awk -F'\t' '!/^#/&&NF>=4' "$(env_file)" | wc -l | tr -d ' ') registered"
[ -n "$ENV_ALIAS" ] && { env_resolve "$ENV_ALIAS"; printf 'env %-10s: ctx=%s ns=%s\n gw=%s\n' "$ENV_ALIAS" "$NDO_CTX" "$NDO_NS" "$NDO_GW"; \
kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy -o name >/dev/null 2>&1 \
&& echo 'kube access : ok' || echo 'kube access : FAILED (VPN down or creds expired)'; }
return 0
}
CMD="${1:-}"; shift || true
case "$CMD" in
doctor) parse_opts "$@"; do_doctor ;;
login) docker login "$REG" ;;
tag) parse_opts "$@"; image_ref ;;
test) parse_opts "$@"; run_tests ;;
build) parse_opts "$@"; do_build ;;
push) parse_opts "$@"; do_push ;;
deploy) parse_opts "$@"; do_deploy ;;
status) parse_opts "$@"; do_status ;;
rollback) parse_opts "$@"; do_rollback ;;
pullsecret) parse_opts "$@"; do_pullsecret ;;
ship) parse_opts "$@"; env_resolve "$ENV_ALIAS"; confirm ship
do_build >/dev/null; TAG=""; do_push >/dev/null; do_deploy ;;
""|-h|--help|help) usage ;;
*) die "unknown command: $CMD (see --help)" ;;
esac