# 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, context path, and image name # Folder names are: auth, tenant, gateway, frontend, alert_processor, etc. # Image names MUST match what's in the Kubernetes manifests exactly # The manifests use the folder name directly (with underscores preserved) # # CONTEXT_PATH is important - some Dockerfiles expect the context to be their directory # (e.g., frontend expects context=frontend/), while services expect context=workspace root if [ "$service" = "gateway" ]; then DOCKERFILE_PATH="$WORKSPACE/gateway/Dockerfile" CONTEXT_PATH="$WORKSPACE" IMAGE_NAME="gateway" elif [ "$service" = "frontend" ]; then # Frontend Dockerfile expects context to be the frontend/ directory # because it does COPY package*.json ./ and COPY nginx.conf etc. DOCKERFILE_PATH="$WORKSPACE/frontend/Dockerfile.kubernetes" CONTEXT_PATH="$WORKSPACE/frontend" IMAGE_NAME="frontend" else DOCKERFILE_PATH="$WORKSPACE/services/$service/Dockerfile" CONTEXT_PATH="$WORKSPACE" # Use folder name directly - matches manifest image references # e.g., auth, tenant, ai_insights, alert_processor, demo_session, external IMAGE_NAME="$service" fi # Check if Dockerfile exists if [ ! -f "$DOCKERFILE_PATH" ]; then echo "Warning: Dockerfile not found at $DOCKERFILE_PATH, skipping..." continue fi echo "Building $service -> Image: $IMAGE_NAME" # 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 $IMAGE_NAME..." # Wait before retry to let registry recover sleep 10 fi echo "Context: $CONTEXT_PATH" if /kaniko/executor \ --dockerfile="$DOCKERFILE_PATH" \ --destination="$(params.registry)/$IMAGE_NAME:$(params.git-revision)" \ --context="$CONTEXT_PATH" \ --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)/$IMAGE_NAME:$(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 $IMAGE_NAME (attempt $RETRY_COUNT)" fi done if [ "$BUILD_SUCCESS" = "false" ]; then echo "ERROR: Failed to build $IMAGE_NAME 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 # IMPORTANT: Use echo -n (no newline) to avoid trailing newline in results # Trailing newlines cause Tekton when expressions to fail matching if [ "$FAILED_COUNT" -gt 0 ]; then if [ "$SUCCESS_COUNT" -gt 0 ]; then echo -n "partial" > $(results.build-status.path) echo "Build completed with some failures" else echo -n "failed" > $(results.build-status.path) echo "All builds failed!" exit 1 fi else echo -n "success" > $(results.build-status.path) echo "All builds completed successfully!" fi resources: limits: cpu: 2000m memory: 8Gi requests: cpu: 500m memory: 2Gi