257 lines
10 KiB
YAML
257 lines
10 KiB
YAML
# Tekton Kaniko Build Task for Bakery-IA CI/CD
|
|
# This task builds and pushes container images using Kaniko
|
|
# Supports environment-configurable base images via build-args
|
|
|
|
apiVersion: tekton.dev/v1beta1
|
|
kind: Task
|
|
metadata:
|
|
name: kaniko-build
|
|
namespace: {{ .Release.Namespace }}
|
|
labels:
|
|
app.kubernetes.io/name: {{ .Values.labels.app.name }}
|
|
app.kubernetes.io/component: build
|
|
spec:
|
|
workspaces:
|
|
- name: source
|
|
description: Workspace containing the source code
|
|
- name: docker-credentials
|
|
description: Docker registry credentials
|
|
params:
|
|
- name: services
|
|
type: string
|
|
description: Comma-separated list of services to build
|
|
- name: registry
|
|
type: string
|
|
description: Container registry URL for pushing built images
|
|
- name: git-revision
|
|
type: string
|
|
description: Git revision to tag images with
|
|
- name: base-registry
|
|
type: string
|
|
description: Base image registry URL (e.g., docker.io, ghcr.io/org)
|
|
default: "registry.bakewise.ai/bakery-admin"
|
|
- name: python-image
|
|
type: string
|
|
description: Python base image name and tag
|
|
default: "python_3.11-slim"
|
|
results:
|
|
- name: build-status
|
|
description: Status of the build operation
|
|
steps:
|
|
- name: build-and-push
|
|
image: gcr.io/kaniko-project/executor:v1.15.0-debug
|
|
# Note: Kaniko requires root to unpack image layers and perform chown operations
|
|
# This is a known requirement for container image building
|
|
securityContext:
|
|
runAsNonRoot: false
|
|
runAsUser: 0
|
|
allowPrivilegeEscalation: false
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
env:
|
|
- name: DOCKER_CONFIG
|
|
value: /tekton/home/.docker
|
|
script: |
|
|
#!/busybox/sh
|
|
set -e
|
|
|
|
# Set up Docker credentials from workspace
|
|
DOCKER_CREDS_PATH="$(workspaces.docker-credentials.path)"
|
|
echo "Setting up Docker credentials from: $DOCKER_CREDS_PATH"
|
|
mkdir -p /tekton/home/.docker
|
|
if [ -f "$DOCKER_CREDS_PATH/config.json" ]; then
|
|
cp "$DOCKER_CREDS_PATH/config.json" /tekton/home/.docker/config.json
|
|
echo "Docker config.json copied successfully"
|
|
elif [ -f "$DOCKER_CREDS_PATH/.dockerconfigjson" ]; then
|
|
cp "$DOCKER_CREDS_PATH/.dockerconfigjson" /tekton/home/.docker/config.json
|
|
echo "Docker .dockerconfigjson copied successfully"
|
|
else
|
|
echo "Warning: No docker credentials found in workspace"
|
|
ls -la "$DOCKER_CREDS_PATH/" || echo "Cannot list docker-credentials workspace"
|
|
fi
|
|
|
|
echo "==================================================================="
|
|
echo "Kaniko Build Configuration"
|
|
echo "==================================================================="
|
|
echo "Target Registry: $(params.registry)"
|
|
echo "Base Registry: $(params.base-registry)"
|
|
echo "Python Image: $(params.python-image)"
|
|
echo "Git Revision: $(params.git-revision)"
|
|
echo "Services param: $(params.services)"
|
|
echo "==================================================================="
|
|
|
|
# Trim whitespace and newlines from services param
|
|
SERVICES_PARAM=$(echo "$(params.services)" | tr -d '\n' | tr -d ' ')
|
|
WORKSPACE="$(workspaces.source.path)"
|
|
|
|
echo "Trimmed services param: '$SERVICES_PARAM'"
|
|
|
|
# Handle special cases for service discovery
|
|
# "all" = all services + gateway + frontend
|
|
# "services-and-gateway" = all services + gateway (no frontend) - used when shared/ changes
|
|
if [ "$SERVICES_PARAM" = "all" ] || [ "$SERVICES_PARAM" = "services-and-gateway" ]; then
|
|
if [ "$SERVICES_PARAM" = "all" ]; then
|
|
echo "Building all services (including frontend) - discovering from workspace..."
|
|
else
|
|
echo "Building services and gateway (shared/ changed) - discovering from workspace..."
|
|
fi
|
|
echo "Workspace contents:"
|
|
ls -la "$WORKSPACE/"
|
|
echo "Services directory contents:"
|
|
ls -la "$WORKSPACE/services/" || echo "No services directory"
|
|
|
|
SERVICES=""
|
|
# Find all services with Dockerfiles using ls
|
|
if [ -d "$WORKSPACE/services" ]; then
|
|
for svc_name in $(ls "$WORKSPACE/services/"); do
|
|
if [ -f "$WORKSPACE/services/$svc_name/Dockerfile" ]; then
|
|
if [ -z "$SERVICES" ]; then
|
|
SERVICES="$svc_name"
|
|
else
|
|
SERVICES="$SERVICES,$svc_name"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
# Add gateway if it has Dockerfile
|
|
if [ -f "$WORKSPACE/gateway/Dockerfile" ]; then
|
|
if [ -z "$SERVICES" ]; then
|
|
SERVICES="gateway"
|
|
else
|
|
SERVICES="$SERVICES,gateway"
|
|
fi
|
|
fi
|
|
# Add frontend ONLY for "all" (not for "services-and-gateway")
|
|
if [ "$SERVICES_PARAM" = "all" ] && [ -f "$WORKSPACE/frontend/Dockerfile.kubernetes" ]; then
|
|
if [ -z "$SERVICES" ]; then
|
|
SERVICES="frontend"
|
|
else
|
|
SERVICES="$SERVICES,frontend"
|
|
fi
|
|
fi
|
|
echo "Discovered services: $SERVICES"
|
|
else
|
|
SERVICES="$SERVICES_PARAM"
|
|
fi
|
|
|
|
# Build each service SEQUENTIALLY to avoid registry upload conflicts
|
|
# Track build results using files (variables don't persist across subshells)
|
|
# Note: Use /tekton/home instead of /tmp as Kaniko container doesn't have /tmp
|
|
BUILD_STATUS_FILE="/tekton/home/build_status"
|
|
echo "0" > "$BUILD_STATUS_FILE.success"
|
|
echo "0" > "$BUILD_STATUS_FILE.failed"
|
|
echo "" > "$BUILD_STATUS_FILE.failed_services"
|
|
|
|
# Convert comma-separated to newline-separated and iterate
|
|
# Using for loop instead of pipe to avoid subshell issues
|
|
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
|
|
|
|
for service in $SERVICES_LIST; do
|
|
service=$(echo "$service" | tr -d ' ') # Trim whitespace
|
|
if [ -n "$service" ] && [ "$service" != "none" ] && [ "$service" != "infrastructure" ] && [ "$service" != "shared" ]; then
|
|
echo ""
|
|
echo "==================================================================="
|
|
echo "Building service: $service"
|
|
echo "==================================================================="
|
|
|
|
# Determine Dockerfile path (services vs gateway vs frontend)
|
|
if [ "$service" = "gateway" ]; then
|
|
DOCKERFILE_PATH="$WORKSPACE/gateway/Dockerfile"
|
|
elif [ "$service" = "frontend" ]; then
|
|
DOCKERFILE_PATH="$WORKSPACE/frontend/Dockerfile.kubernetes"
|
|
else
|
|
DOCKERFILE_PATH="$WORKSPACE/services/$service/Dockerfile"
|
|
fi
|
|
|
|
# Check if Dockerfile exists
|
|
if [ ! -f "$DOCKERFILE_PATH" ]; then
|
|
echo "Warning: Dockerfile not found at $DOCKERFILE_PATH, skipping..."
|
|
continue
|
|
fi
|
|
|
|
# Build with retry logic to handle transient registry errors
|
|
RETRY_COUNT=0
|
|
MAX_RETRIES=2
|
|
BUILD_SUCCESS=false
|
|
|
|
while [ "$RETRY_COUNT" -le "$MAX_RETRIES" ] && [ "$BUILD_SUCCESS" = "false" ]; do
|
|
if [ "$RETRY_COUNT" -gt 0 ]; then
|
|
echo "Retry $RETRY_COUNT/$MAX_RETRIES for $service..."
|
|
# Wait before retry to let registry recover
|
|
sleep 10
|
|
fi
|
|
|
|
if /kaniko/executor \
|
|
--dockerfile="$DOCKERFILE_PATH" \
|
|
--destination="$(params.registry)/$service:$(params.git-revision)" \
|
|
--context="$WORKSPACE" \
|
|
--build-arg="BASE_REGISTRY=$(params.base-registry)" \
|
|
--build-arg="PYTHON_IMAGE=$(params.python-image)" \
|
|
--cache=true \
|
|
--cache-repo="$(params.registry)/cache" \
|
|
--cache-ttl=168h \
|
|
--push-retry=3 \
|
|
--image-fs-extract-retry=3; then
|
|
BUILD_SUCCESS=true
|
|
echo "Successfully built and pushed: $(params.registry)/$service:$(params.git-revision)"
|
|
# Increment success count
|
|
COUNT=$(cat "$BUILD_STATUS_FILE.success")
|
|
echo $((COUNT + 1)) > "$BUILD_STATUS_FILE.success"
|
|
else
|
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
echo "Build/push failed for $service (attempt $RETRY_COUNT)"
|
|
fi
|
|
done
|
|
|
|
if [ "$BUILD_SUCCESS" = "false" ]; then
|
|
echo "ERROR: Failed to build $service after $MAX_RETRIES retries"
|
|
# Increment failed count and record service name
|
|
COUNT=$(cat "$BUILD_STATUS_FILE.failed")
|
|
echo $((COUNT + 1)) > "$BUILD_STATUS_FILE.failed"
|
|
echo "$service" >> "$BUILD_STATUS_FILE.failed_services"
|
|
fi
|
|
|
|
# Small delay between services to let registry settle
|
|
# This prevents "offset mismatch" errors from concurrent uploads
|
|
echo "Waiting 5s before next build to let registry settle..."
|
|
sleep 5
|
|
fi
|
|
done
|
|
|
|
# Read final counts
|
|
SUCCESS_COUNT=$(cat "$BUILD_STATUS_FILE.success")
|
|
FAILED_COUNT=$(cat "$BUILD_STATUS_FILE.failed")
|
|
FAILED_SERVICES=$(cat "$BUILD_STATUS_FILE.failed_services" | tr '\n' ',' | sed 's/,$//')
|
|
|
|
echo ""
|
|
echo "==================================================================="
|
|
echo "Build Summary"
|
|
echo "==================================================================="
|
|
echo "Successful builds: $SUCCESS_COUNT"
|
|
echo "Failed builds: $FAILED_COUNT"
|
|
if [ -n "$FAILED_SERVICES" ]; then
|
|
echo "Failed services: $FAILED_SERVICES"
|
|
fi
|
|
echo "==================================================================="
|
|
|
|
# Set result based on outcome
|
|
if [ "$FAILED_COUNT" -gt 0 ]; then
|
|
if [ "$SUCCESS_COUNT" -gt 0 ]; then
|
|
echo "partial" > $(results.build-status.path)
|
|
echo "Build completed with some failures"
|
|
else
|
|
echo "failed" > $(results.build-status.path)
|
|
echo "All builds failed!"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "success" > $(results.build-status.path)
|
|
echo "All builds completed successfully!"
|
|
fi
|
|
resources:
|
|
limits:
|
|
cpu: 2000m
|
|
memory: 4Gi
|
|
requests:
|
|
cpu: 500m
|
|
memory: 1Gi |