Add new infra architecture

This commit is contained in:
Urtzi Alfaro
2026-01-19 11:55:17 +01:00
parent 21d35ea92b
commit 35f164f0cd
311 changed files with 13241 additions and 3700 deletions

View File

@@ -0,0 +1,293 @@
# Bakery-IA CI/CD Implementation
This directory contains the configuration for the production-grade CI/CD system for Bakery-IA using Gitea, Tekton, and Flux CD.
## Architecture Overview
```mermaid
graph TD
A[Developer] -->|Push Code| B[Gitea]
B -->|Webhook| C[Tekton Pipelines]
C -->|Build/Test| D[Gitea Registry]
D -->|New Image| E[Flux CD]
E -->|kubectl apply| F[MicroK8s Cluster]
F -->|Metrics| G[SigNoz]
```
## Directory Structure
```
infrastructure/ci-cd/
├── gitea/ # Gitea configuration (Git server + registry)
│ └── values.yaml # Helm values for Gitea (ingress now in main config)
├── tekton/ # Tekton CI/CD pipeline configuration
│ ├── tasks/ # Individual pipeline tasks
│ │ ├── git-clone.yaml
│ │ ├── detect-changes.yaml
│ │ ├── kaniko-build.yaml
│ │ └── update-gitops.yaml
│ ├── pipelines/ # Pipeline definitions
│ │ └── ci-pipeline.yaml
│ └── triggers/ # Webhook trigger configuration
│ ├── trigger-template.yaml
│ ├── trigger-binding.yaml
│ ├── event-listener.yaml
│ └── gitlab-interceptor.yaml
├── flux/ # Flux CD GitOps configuration
│ ├── git-repository.yaml # Git repository source
│ └── kustomization.yaml # Deployment kustomization
├── monitoring/ # Monitoring configuration
│ └── otel-collector.yaml # OpenTelemetry collector
└── README.md # This file
```
## Deployment Instructions
### Phase 1: Infrastructure Setup
1. **Deploy Gitea**:
```bash
# Add Helm repo
microk8s helm repo add gitea https://dl.gitea.io/charts
# Create namespace
microk8s kubectl create namespace gitea
# Install Gitea
microk8s helm install gitea gitea/gitea \
-n gitea \
-f infrastructure/ci-cd/gitea/values.yaml
# Note: Gitea ingress is now included in the main ingress configuration
# No separate ingress needs to be applied
```
2. **Deploy Tekton**:
```bash
# Create namespace
microk8s kubectl create namespace tekton-pipelines
# Install Tekton Pipelines
microk8s kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
# Install Tekton Triggers
microk8s kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
# Apply Tekton configurations
microk8s kubectl apply -f infrastructure/ci-cd/tekton/tasks/
microk8s kubectl apply -f infrastructure/ci-cd/tekton/pipelines/
microk8s kubectl apply -f infrastructure/ci-cd/tekton/triggers/
```
3. **Deploy Flux CD** (already enabled in MicroK8s):
```bash
# Verify Flux installation
microk8s kubectl get pods -n flux-system
# Apply Flux configurations using kustomize
microk8s kubectl apply -k infrastructure/ci-cd/flux/
```
### Phase 2: Configuration
1. **Set up Gitea webhook**:
- Go to your Gitea repository settings
- Add webhook with URL: `http://tekton-triggers.tekton-pipelines.svc.cluster.local:8080`
- Use the secret from `gitea-webhook-secret`
2. **Configure registry credentials**:
```bash
# Create registry credentials secret
microk8s kubectl create secret docker-registry gitea-registry-credentials \
-n tekton-pipelines \
--docker-server=gitea.bakery-ia.local:5000 \
--docker-username=your-username \
--docker-password=your-password
```
3. **Configure Git credentials for Flux**:
```bash
# Create Git credentials secret
microk8s kubectl create secret generic gitea-credentials \
-n flux-system \
--from-literal=username=your-username \
--from-literal=password=your-password
```
### Phase 3: Monitoring
```bash
# Apply OpenTelemetry configuration
microk8s kubectl apply -f infrastructure/ci-cd/monitoring/otel-collector.yaml
```
## Usage
### Triggering a Pipeline
1. **Manual trigger**:
```bash
# Create a PipelineRun manually
microk8s kubectl create -f - <<EOF
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: manual-ci-run
namespace: tekton-pipelines
spec:
pipelineRef:
name: bakery-ia-ci
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
- name: docker-credentials
secret:
secretName: gitea-registry-credentials
params:
- name: git-url
value: "http://gitea.bakery-ia.local/bakery/bakery-ia.git"
- name: git-revision
value: "main"
EOF
```
2. **Automatic trigger**: Push code to the repository and the webhook will trigger the pipeline automatically.
### Monitoring Pipeline Runs
```bash
# List all PipelineRuns
microk8s kubectl get pipelineruns -n tekton-pipelines
# View logs for a specific PipelineRun
microk8s kubectl logs -n tekton-pipelines <pipelinerun-pod> -c <step-name>
# View Tekton dashboard
microk8s kubectl port-forward -n tekton-pipelines svc/tekton-dashboard 9097:9097
```
## Troubleshooting
### Common Issues
1. **Pipeline not triggering**:
- Check Gitea webhook logs
- Verify EventListener pods are running
- Check TriggerBinding configuration
2. **Build failures**:
- Check Kaniko logs for build errors
- Verify Dockerfile paths are correct
- Ensure registry credentials are valid
3. **Flux not applying changes**:
- Check GitRepository status
- Verify Kustomization reconciliation
- Check Flux logs for errors
### Debugging Commands
```bash
# Check Tekton controller logs
microk8s kubectl logs -n tekton-pipelines -l app=tekton-pipelines-controller
# Check Flux reconciliation
microk8s kubectl get kustomizations -n flux-system -o yaml
# Check Gitea webhook delivery
microk8s kubectl logs -n tekton-pipelines -l app=tekton-triggers-controller
```
## Security Considerations
1. **Secrets Management**:
- Use Kubernetes secrets for sensitive data
- Rotate credentials regularly
- Use RBAC for namespace isolation
2. **Network Security**:
- Configure network policies
- Use internal DNS names
- Restrict ingress access
3. **Registry Security**:
- Enable image scanning
- Use image signing
- Implement cleanup policies
## Maintenance
### Upgrading Components
```bash
# Upgrade Tekton
microk8s kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
# Upgrade Flux
microk8s helm upgrade fluxcd fluxcd/flux2 -n flux-system
# Upgrade Gitea
microk8s helm upgrade gitea gitea/gitea -n gitea -f infrastructure/ci-cd/gitea/values.yaml
```
### Backup Procedures
```bash
# Backup Gitea
microk8s kubectl exec -n gitea gitea-0 -- gitea dump -c /data/gitea/conf/app.ini
# Backup Flux configurations
microk8s kubectl get all -n flux-system -o yaml > flux-backup.yaml
# Backup Tekton configurations
microk8s kubectl get all -n tekton-pipelines -o yaml > tekton-backup.yaml
```
## Performance Optimization
1. **Resource Management**:
- Set appropriate resource limits
- Limit concurrent builds
- Use node selectors for build pods
2. **Caching**:
- Configure Kaniko cache
- Use persistent volumes for dependencies
- Cache Docker layers
3. **Parallelization**:
- Build independent services in parallel
- Use matrix builds for different architectures
- Optimize task dependencies
## Integration with Existing System
The CI/CD system integrates with:
- **SigNoz**: For monitoring and observability
- **MicroK8s**: For cluster management
- **Existing Kubernetes manifests**: In `infrastructure/kubernetes/`
- **Current services**: All 19 microservices in `services/`
## Migration Plan
1. **Phase 1**: Set up infrastructure (Gitea, Tekton, Flux)
2. **Phase 2**: Configure pipelines and triggers
3. **Phase 3**: Test with non-critical services
4. **Phase 4**: Gradual rollout to all services
5. **Phase 5**: Decommission old deployment methods
## Support
For issues with the CI/CD system:
- Check logs and monitoring first
- Review the troubleshooting section
- Consult the original implementation plan
- Refer to component documentation:
- [Tekton Documentation](https://tekton.dev/docs/)
- [Flux CD Documentation](https://fluxcd.io/docs/)
- [Gitea Documentation](https://docs.gitea.io/)

View File

@@ -0,0 +1,76 @@
# Flux Kustomization for Bakery-IA Production Deployment
# This resource tells Flux how to deploy the application
#
# Prerequisites:
# 1. Flux CD must be installed: flux install
# 2. GitRepository 'bakery-ia' must be created and ready
# 3. Secret 'gitea-credentials' must exist in flux-system namespace
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: bakery-ia-prod
namespace: flux-system
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: flux
spec:
# Wait for GitRepository to be ready before reconciling
dependsOn: []
interval: 5m
path: ./infrastructure/environments/prod
prune: true
sourceRef:
kind: GitRepository
name: bakery-ia
targetNamespace: bakery-ia
timeout: 10m
retryInterval: 1m
wait: true
# Health checks for critical services
healthChecks:
# Core Infrastructure
- apiVersion: apps/v1
kind: Deployment
name: gateway
namespace: bakery-ia
# Authentication & Authorization
- apiVersion: apps/v1
kind: Deployment
name: auth-service
namespace: bakery-ia
- apiVersion: apps/v1
kind: Deployment
name: tenant-service
namespace: bakery-ia
# Core Business Services
- apiVersion: apps/v1
kind: Deployment
name: inventory-service
namespace: bakery-ia
- apiVersion: apps/v1
kind: Deployment
name: orders-service
namespace: bakery-ia
- apiVersion: apps/v1
kind: Deployment
name: pos-service
namespace: bakery-ia
# Data Services
- apiVersion: apps/v1
kind: Deployment
name: forecasting-service
namespace: bakery-ia
- apiVersion: apps/v1
kind: Deployment
name: notification-service
namespace: bakery-ia
# Post-build variable substitution
postBuild:
substituteFrom:
- kind: ConfigMap
name: bakery-ia-config
optional: true
- kind: Secret
name: bakery-ia-secrets
optional: true

View File

@@ -0,0 +1,16 @@
# Flux GitRepository for Bakery-IA
# This resource tells Flux where to find the Git repository
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: bakery-ia
namespace: flux-system
spec:
interval: 1m
url: http://gitea.bakery-ia.local/bakery/bakery-ia.git
ref:
branch: main
secretRef:
name: gitea-credentials
timeout: 60s

View File

@@ -0,0 +1,25 @@
# Kustomize build configuration for Flux resources
# This file is used to build and apply the Flux resources
#
# IMPORTANT: Apply resources in this order:
# 1. Install Flux CD first: flux install
# 2. Apply this kustomization: kubectl apply -k infrastructure/cicd/flux/
#
# The GitRepository must be ready before the Flux Kustomization can reconcile.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Resources to apply in order (namespace and secrets first, then sources, then kustomizations)
resources:
- namespace.yaml
- git-repository.yaml
- flux-kustomization.yaml
# Common labels for all resources
commonLabels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: flux
app.kubernetes.io/managed-by: kustomize
# Note: Do NOT set namespace here as resources already have explicit namespaces

View File

@@ -0,0 +1,15 @@
# Flux System Namespace
# This namespace is required for Flux CD components
# It should be created before any Flux resources are applied
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
labels:
app.kubernetes.io/name: flux
app.kubernetes.io/component: system
kubernetes.io/metadata.name: flux-system
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

View File

@@ -0,0 +1,44 @@
# Gitea Ingress Configuration
# Routes external traffic to Gitea service for web UI and Git HTTP access
#
# Prerequisites:
# - Gitea must be deployed in the 'gitea' namespace
# - Ingress controller must be installed (nginx, traefik, etc.)
# - For HTTPS: cert-manager with a ClusterIssuer named 'letsencrypt-prod' or 'local-ca-issuer'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea-ingress
namespace: gitea
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/component: ingress
app.kubernetes.io/part-of: bakery-ia-cicd
annotations:
# For nginx ingress controller
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
# For traefik ingress controller
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
# For TLS with cert-manager (uncomment for HTTPS)
# cert-manager.io/cluster-issuer: "local-ca-issuer"
spec:
ingressClassName: nginx
# Uncomment for HTTPS
# tls:
# - hosts:
# - gitea.bakery-ia.local
# secretName: gitea-tls
rules:
- host: gitea.bakery-ia.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea-http
port:
number: 3000

View File

@@ -0,0 +1,48 @@
#!/bin/bash
# Setup Gitea Admin Secret
#
# This script creates the Kubernetes secret required for Gitea admin credentials.
# Run this BEFORE installing Gitea with Helm.
#
# Usage:
# ./setup-admin-secret.sh [password]
#
# If password is not provided, a random one will be generated.
set -e
KUBECTL="kubectl"
NAMESPACE="gitea"
# Check if running in microk8s
if command -v microk8s &> /dev/null; then
KUBECTL="microk8s kubectl"
fi
# Get or generate password
if [ -n "$1" ]; then
ADMIN_PASSWORD="$1"
else
ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 20)
echo "Generated admin password: $ADMIN_PASSWORD"
fi
# Create namespace if it doesn't exist
$KUBECTL create namespace "$NAMESPACE" --dry-run=client -o yaml | $KUBECTL apply -f -
# Create the secret
$KUBECTL create secret generic gitea-admin-secret \
--namespace "$NAMESPACE" \
--from-literal=username=bakery-admin \
--from-literal=password="$ADMIN_PASSWORD" \
--dry-run=client -o yaml | $KUBECTL apply -f -
echo ""
echo "Gitea admin secret created successfully!"
echo ""
echo "Admin credentials:"
echo " Username: bakery-admin"
echo " Password: $ADMIN_PASSWORD"
echo ""
echo "Now install Gitea with:"
echo " helm install gitea gitea/gitea -n gitea -f infrastructure/cicd/gitea/values.yaml"

View File

@@ -0,0 +1,83 @@
# Gitea Helm values configuration for Bakery-IA CI/CD
# This configuration sets up Gitea with registry support and appropriate storage
#
# Installation:
# helm repo add gitea https://dl.gitea.io/charts
# kubectl create namespace gitea
# helm install gitea gitea/gitea -n gitea -f infrastructure/cicd/gitea/values.yaml
#
# NOTE: The namespace is determined by the -n flag during helm install, not in this file.
service:
http:
type: ClusterIP
port: 3000
ssh:
type: ClusterIP
port: 2222
persistence:
enabled: true
size: 10Gi
# Use standard storage class (works with Kind's default provisioner)
# For microk8s: storageClass: "microk8s-hostpath"
# For Kind: leave empty or use "standard"
storageClass: ""
gitea:
admin:
username: bakery-admin
# IMPORTANT: Override this with --set gitea.admin.password=<secure-password>
# or use existingSecret
password: ""
email: admin@bakery-ia.local
existingSecret: gitea-admin-secret
config:
server:
DOMAIN: gitea.bakery-ia.local
SSH_DOMAIN: gitea.bakery-ia.local
# Use HTTP internally; TLS termination happens at ingress
ROOT_URL: http://gitea.bakery-ia.local
HTTP_PORT: 3000
# For external HTTPS access via ingress, set:
# ROOT_URL: https://gitea.bakery-ia.local
repository:
ENABLE_PUSH_CREATE_USER: true
ENABLE_PUSH_CREATE_ORG: true
packages:
ENABLED: true
webhook:
ALLOWED_HOST_LIST: "*"
# Allow internal cluster URLs for Tekton EventListener
SKIP_TLS_VERIFY: true
service:
DISABLE_REGISTRATION: false
REQUIRE_SIGNIN_VIEW: false
# Use embedded SQLite for simpler local development
# For production, enable postgresql
postgresql:
enabled: false
# Use embedded in-memory cache for local dev
redis-cluster:
enabled: false
# Resource configuration for local development
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
# Init containers timeout
initContainers:
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

View File

@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- tekton/
- ../../namespaces/flux-system.yaml
# Gitea is managed via Helm, so we don't include it directly here
# The Gitea Helm chart is deployed separately and referenced in the ingress
# Flux configuration is a Flux Kustomization resource, not a kustomize config

View File

@@ -0,0 +1,222 @@
# Workspace and PipelineRun Cleanup for Bakery-IA CI/CD
# This CronJob cleans up old PipelineRuns and PVCs to prevent storage exhaustion
---
# ServiceAccount for cleanup job
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-cleanup-sa
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
---
# ClusterRole for cleanup operations
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tekton-cleanup-role
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
rules:
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "taskruns"]
verbs: ["get", "list", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "delete"]
---
# ClusterRoleBinding for cleanup
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-cleanup-binding
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
subjects:
- kind: ServiceAccount
name: tekton-cleanup-sa
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-cleanup-role
apiGroup: rbac.authorization.k8s.io
---
# CronJob to clean up old PipelineRuns
apiVersion: batch/v1
kind: CronJob
metadata:
name: tekton-pipelinerun-cleanup
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
spec:
# Run every 6 hours
schedule: "0 */6 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
ttlSecondsAfterFinished: 3600
template:
metadata:
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
spec:
serviceAccountName: tekton-cleanup-sa
restartPolicy: OnFailure
containers:
- name: cleanup
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
#!/bin/sh
set -e
echo "============================================"
echo "Tekton Cleanup Job"
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "============================================"
# Configuration
NAMESPACE="tekton-pipelines"
MAX_AGE_HOURS=24
KEEP_RECENT=10
echo ""
echo "Configuration:"
echo " Namespace: $NAMESPACE"
echo " Max Age: ${MAX_AGE_HOURS} hours"
echo " Keep Recent: $KEEP_RECENT"
echo ""
# Get current timestamp
CURRENT_TIME=$(date +%s)
# Clean up completed PipelineRuns older than MAX_AGE_HOURS
echo "Cleaning up old PipelineRuns..."
# Get all completed PipelineRuns
COMPLETED_RUNS=$(kubectl get pipelineruns -n "$NAMESPACE" \
--no-headers \
-o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[0].reason,AGE:.metadata.creationTimestamp \
2>/dev/null | grep -E "Succeeded|Failed" || true)
DELETED_COUNT=0
echo "$COMPLETED_RUNS" | while read -r line; do
if [ -z "$line" ]; then
continue
fi
RUN_NAME=$(echo "$line" | awk '{print $1}')
RUN_TIME=$(echo "$line" | awk '{print $3}')
if [ -z "$RUN_NAME" ] || [ -z "$RUN_TIME" ]; then
continue
fi
# Convert timestamp to seconds
RUN_TIMESTAMP=$(date -d "$RUN_TIME" +%s 2>/dev/null || echo "0")
if [ "$RUN_TIMESTAMP" = "0" ]; then
continue
fi
# Calculate age in hours
AGE_SECONDS=$((CURRENT_TIME - RUN_TIMESTAMP))
AGE_HOURS=$((AGE_SECONDS / 3600))
if [ "$AGE_HOURS" -gt "$MAX_AGE_HOURS" ]; then
echo "Deleting PipelineRun: $RUN_NAME (age: ${AGE_HOURS}h)"
kubectl delete pipelinerun "$RUN_NAME" -n "$NAMESPACE" --ignore-not-found=true
DELETED_COUNT=$((DELETED_COUNT + 1))
fi
done
echo "Deleted $DELETED_COUNT old PipelineRuns"
# Clean up orphaned PVCs (PVCs without associated PipelineRuns)
echo ""
echo "Cleaning up orphaned PVCs..."
ORPHANED_PVCS=$(kubectl get pvc -n "$NAMESPACE" \
-l tekton.dev/pipelineRun \
--no-headers \
-o custom-columns=NAME:.metadata.name,PIPELINERUN:.metadata.labels.tekton\\.dev/pipelineRun \
2>/dev/null || true)
echo "$ORPHANED_PVCS" | while read -r line; do
if [ -z "$line" ]; then
continue
fi
PVC_NAME=$(echo "$line" | awk '{print $1}')
PR_NAME=$(echo "$line" | awk '{print $2}')
if [ -z "$PVC_NAME" ]; then
continue
fi
# Check if associated PipelineRun exists
if ! kubectl get pipelinerun "$PR_NAME" -n "$NAMESPACE" > /dev/null 2>&1; then
echo "Deleting orphaned PVC: $PVC_NAME (PipelineRun $PR_NAME not found)"
kubectl delete pvc "$PVC_NAME" -n "$NAMESPACE" --ignore-not-found=true
fi
done
# Clean up completed/failed pods older than 1 hour
echo ""
echo "Cleaning up old completed pods..."
kubectl delete pods -n "$NAMESPACE" \
--field-selector=status.phase=Succeeded \
--ignore-not-found=true 2>/dev/null || true
kubectl delete pods -n "$NAMESPACE" \
--field-selector=status.phase=Failed \
--ignore-not-found=true 2>/dev/null || true
echo ""
echo "============================================"
echo "Cleanup complete"
echo "============================================"
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
---
# ConfigMap for cleanup configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: cleanup-config
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: cleanup
data:
# Maximum age of completed PipelineRuns to keep (in hours)
MAX_AGE_HOURS: "24"
# Number of recent PipelineRuns to keep regardless of age
KEEP_RECENT: "10"
# Cleanup schedule (cron format)
CLEANUP_SCHEDULE: "0 */6 * * *"

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cleanup.yaml

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pipeline-config.yaml

View File

@@ -0,0 +1,41 @@
# CI/CD Pipeline Configuration for Bakery-IA
# This ConfigMap contains configurable values for the CI/CD pipeline
#
# IMPORTANT: When changing REGISTRY_URL, also update:
# - infrastructure/cicd/tekton/triggers/trigger-template.yaml (registry-url default)
# - infrastructure/cicd/tekton/secrets/secrets.yaml (registry credentials)
apiVersion: v1
kind: ConfigMap
metadata:
name: pipeline-config
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: config
data:
# Container Registry Configuration
# Change this to your actual registry URL
# Also update trigger-template.yaml and secrets when changing this!
REGISTRY_URL: "gitea.bakery-ia.local:5000"
# Git Configuration
GIT_BRANCH: "main"
GIT_USER_NAME: "bakery-ia-ci"
GIT_USER_EMAIL: "ci@bakery-ia.local"
# Build Configuration
BUILD_CACHE_TTL: "24h"
BUILD_VERBOSITY: "info"
# Test Configuration
SKIP_TESTS: "false"
SKIP_LINT: "false"
# Deployment Configuration
DEPLOY_NAMESPACE: "bakery-ia"
FLUX_NAMESPACE: "flux-system"
# Workspace Configuration
WORKSPACE_SIZE: "5Gi"
WORKSPACE_STORAGE_CLASS: "standard"

View File

@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac/
- secrets/
- configs/
- tasks/
- triggers/
- pipelines/
- cleanup/

View File

@@ -0,0 +1,149 @@
# Main CI Pipeline for Bakery-IA
# This pipeline orchestrates the build, test, and deploy process
# Includes: fetch -> detect changes -> test -> build -> update gitops
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: bakery-ia-ci
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
spec:
workspaces:
- name: shared-workspace
description: Shared workspace for source code
- name: docker-credentials
description: Docker registry credentials
- name: git-credentials
description: Git credentials for pushing GitOps updates
optional: true
params:
- name: git-url
type: string
description: Repository URL
- name: git-revision
type: string
description: Git revision/commit hash
- name: registry
type: string
description: Container registry URL
- name: git-branch
type: string
description: Target branch for GitOps updates
default: "main"
- name: skip-tests
type: string
description: Skip tests if "true"
default: "false"
- name: dry-run
type: string
description: Dry run mode - don't push changes
default: "false"
tasks:
# Stage 1: Fetch source code
- name: fetch-source
taskRef:
name: git-clone
workspaces:
- name: output
workspace: shared-workspace
params:
- name: url
value: $(params.git-url)
- name: revision
value: $(params.git-revision)
# Stage 2: Detect which services changed
- name: detect-changes
runAfter: [fetch-source]
taskRef:
name: detect-changed-services
workspaces:
- name: source
workspace: shared-workspace
# Stage 3: Run tests on changed services
- name: run-tests
runAfter: [detect-changes]
taskRef:
name: run-tests
when:
- input: "$(tasks.detect-changes.results.changed-services)"
operator: notin
values: ["none", "infrastructure"]
- input: "$(params.skip-tests)"
operator: notin
values: ["true"]
workspaces:
- name: source
workspace: shared-workspace
params:
- name: services
value: $(tasks.detect-changes.results.changed-services)
- name: skip-tests
value: $(params.skip-tests)
# Stage 4: Build and push container images
- name: build-and-push
runAfter: [run-tests]
taskRef:
name: kaniko-build
when:
- input: "$(tasks.detect-changes.results.changed-services)"
operator: notin
values: ["none", "infrastructure"]
workspaces:
- name: source
workspace: shared-workspace
- name: docker-credentials
workspace: docker-credentials
params:
- name: services
value: $(tasks.detect-changes.results.changed-services)
- name: registry
value: $(params.registry)
- name: git-revision
value: $(params.git-revision)
# Stage 5: Update GitOps manifests
- name: update-gitops-manifests
runAfter: [build-and-push]
taskRef:
name: update-gitops
when:
- input: "$(tasks.detect-changes.results.changed-services)"
operator: notin
values: ["none", "infrastructure"]
- input: "$(tasks.build-and-push.results.build-status)"
operator: in
values: ["success", "partial"]
workspaces:
- name: source
workspace: shared-workspace
- name: git-credentials
workspace: git-credentials
params:
- name: services
value: $(tasks.detect-changes.results.changed-services)
- name: registry
value: $(params.registry)
- name: git-revision
value: $(params.git-revision)
- name: git-branch
value: $(params.git-branch)
- name: dry-run
value: $(params.dry-run)
# Final tasks that run regardless of pipeline success/failure
finally:
- name: pipeline-summary
taskRef:
name: pipeline-summary
params:
- name: changed-services
value: $(tasks.detect-changes.results.changed-services)
- name: git-revision
value: $(params.git-revision)

View File

@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ci-pipeline.yaml
- prod-deploy-pipeline.yaml

View File

@@ -0,0 +1,118 @@
# Production Deployment Pipeline for Bakery-IA
# This pipeline handles production deployments with manual approval gate
# It should be triggered after the CI pipeline succeeds
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: bakery-ia-prod-deploy
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
app.kubernetes.io/environment: production
spec:
workspaces:
- name: shared-workspace
description: Shared workspace for source code
- name: git-credentials
description: Git credentials for pushing GitOps updates
optional: true
params:
- name: git-url
type: string
description: Repository URL
- name: git-revision
type: string
description: Git revision/commit hash to deploy
- name: services
type: string
description: Comma-separated list of services to deploy
- name: registry
type: string
description: Container registry URL
- name: approver
type: string
description: Name of the person who approved this deployment
default: "automated"
- name: approval-ticket
type: string
description: Ticket/issue number for deployment approval
default: "N/A"
tasks:
# Stage 1: Fetch source code
- name: fetch-source
taskRef:
name: git-clone
workspaces:
- name: output
workspace: shared-workspace
params:
- name: url
value: $(params.git-url)
- name: revision
value: $(params.git-revision)
# Stage 2: Verify images exist in registry
- name: verify-images
runAfter: [fetch-source]
taskRef:
name: verify-images
params:
- name: services
value: $(params.services)
- name: registry
value: $(params.registry)
- name: git-revision
value: $(params.git-revision)
# Stage 3: Pre-deployment validation
- name: pre-deploy-validation
runAfter: [verify-images]
taskRef:
name: pre-deploy-validation
workspaces:
- name: source
workspace: shared-workspace
params:
- name: services
value: $(params.services)
- name: environment
value: "production"
# Stage 4: Update production manifests
- name: update-prod-manifests
runAfter: [pre-deploy-validation]
taskRef:
name: update-gitops
workspaces:
- name: source
workspace: shared-workspace
- name: git-credentials
workspace: git-credentials
params:
- name: services
value: $(params.services)
- name: registry
value: $(params.registry)
- name: git-revision
value: $(params.git-revision)
- name: git-branch
value: "main"
- name: dry-run
value: "false"
finally:
- name: deployment-summary
taskRef:
name: prod-deployment-summary
params:
- name: services
value: $(params.services)
- name: git-revision
value: $(params.git-revision)
- name: approver
value: $(params.approver)
- name: approval-ticket
value: $(params.approval-ticket)

View File

@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac.yaml
- resource-quota.yaml

View File

@@ -0,0 +1,159 @@
# Tekton RBAC Configuration for Bakery-IA CI/CD
# This file defines ServiceAccounts, Roles, and RoleBindings for Tekton
---
# ServiceAccount for Tekton Triggers EventListener
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-triggers-sa
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
---
# ServiceAccount for Pipeline execution
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-pipeline-sa
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
---
# ClusterRole for Tekton Triggers to create PipelineRuns
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tekton-triggers-role
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
rules:
# Ability to create PipelineRuns from triggers
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "taskruns"]
verbs: ["create", "get", "list", "watch"]
# Ability to read pipelines and tasks
- apiGroups: ["tekton.dev"]
resources: ["pipelines", "tasks", "clustertasks"]
verbs: ["get", "list", "watch"]
# Ability to manage PVCs for workspaces
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["create", "get", "list", "watch", "delete"]
# Ability to read secrets for credentials
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
# Ability to read configmaps
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
# Ability to manage events for logging
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
---
# ClusterRoleBinding for Tekton Triggers
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-triggers-binding
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-triggers-role
apiGroup: rbac.authorization.k8s.io
---
# ClusterRole for Pipeline execution (needed for git operations and deployments)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tekton-pipeline-role
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
rules:
# Ability to read/update deployments for GitOps
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch", "update"]
# Ability to read secrets for credentials
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
# Ability to read configmaps
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
# Ability to manage pods for build operations
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
# ClusterRoleBinding for Pipeline execution
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-pipeline-binding
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: pipeline
subjects:
- kind: ServiceAccount
name: tekton-pipeline-sa
namespace: tekton-pipelines
roleRef:
kind: ClusterRole
name: tekton-pipeline-role
apiGroup: rbac.authorization.k8s.io
---
# Role for EventListener to access triggers resources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tekton-triggers-eventlistener-role
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
rules:
- apiGroups: ["triggers.tekton.dev"]
resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers", "interceptors"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
---
# RoleBinding for EventListener
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-triggers-eventlistener-binding
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
namespace: tekton-pipelines
roleRef:
kind: Role
name: tekton-triggers-eventlistener-role
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,64 @@
# ResourceQuota for Tekton Pipelines Namespace
# Prevents resource exhaustion from runaway pipeline runs
#
# This quota limits:
# - Total CPU and memory that can be requested/used
# - Number of concurrent pods
# - Number of PVCs for workspaces
apiVersion: v1
kind: ResourceQuota
metadata:
name: tekton-pipelines-quota
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: quota
spec:
hard:
# Limit total CPU
requests.cpu: "8"
limits.cpu: "16"
# Limit total memory
requests.memory: "16Gi"
limits.memory: "32Gi"
# Limit number of pods (controls concurrent pipeline tasks)
pods: "20"
# Limit PVCs (controls workspace storage)
persistentvolumeclaims: "10"
# Limit storage
requests.storage: "50Gi"
---
# LimitRange to set defaults and limits for individual pods
# Ensures every pod has resource requests/limits
apiVersion: v1
kind: LimitRange
metadata:
name: tekton-pipelines-limits
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: quota
spec:
limits:
# Default limits for containers
- type: Container
default:
cpu: "1"
memory: "1Gi"
defaultRequest:
cpu: "100m"
memory: "256Mi"
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "50m"
memory: "64Mi"
# Limits for PVCs
- type: PersistentVolumeClaim
max:
storage: "10Gi"
min:
storage: "1Gi"

View File

@@ -0,0 +1,4 @@
# Ignore generated secrets
.webhook-secret
*-actual.yaml
sealed-secrets.yaml

View File

@@ -0,0 +1,167 @@
#!/bin/bash
# Generate CI/CD Secrets for Bakery-IA
#
# This script creates Kubernetes secrets required for the CI/CD pipeline.
# Run this script once during initial setup.
#
# Usage:
# ./generate-secrets.sh [options]
#
# Options:
# --registry-url Container registry URL (default: gitea.bakery-ia.local:5000)
# --gitea-user Gitea username (will prompt if not provided)
# --gitea-password Gitea password (will prompt if not provided)
# --dry-run Print commands without executing
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
REGISTRY_URL="${REGISTRY_URL:-gitea.bakery-ia.local:5000}"
DRY_RUN=false
KUBECTL="kubectl"
# Check if running in microk8s
if command -v microk8s &> /dev/null; then
KUBECTL="microk8s kubectl"
fi
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--registry-url)
REGISTRY_URL="$2"
shift 2
;;
--gitea-user)
GITEA_USERNAME="$2"
shift 2
;;
--gitea-password)
GITEA_PASSWORD="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
echo "=========================================="
echo " Bakery-IA CI/CD Secrets Generator"
echo "=========================================="
echo ""
# Prompt for credentials if not provided
if [ -z "$GITEA_USERNAME" ]; then
read -p "Enter Gitea username: " GITEA_USERNAME
fi
if [ -z "$GITEA_PASSWORD" ]; then
read -s -p "Enter Gitea password: " GITEA_PASSWORD
echo ""
fi
# Generate webhook secret
WEBHOOK_SECRET=$(openssl rand -hex 32)
echo ""
echo -e "${YELLOW}Configuration:${NC}"
echo " Registry URL: $REGISTRY_URL"
echo " Gitea User: $GITEA_USERNAME"
echo " Webhook Secret: ${WEBHOOK_SECRET:0:8}..."
echo ""
# Function to create secret
create_secret() {
local cmd="$1"
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}[DRY-RUN]${NC} $cmd"
else
eval "$cmd"
fi
}
# Ensure namespaces exist
echo -e "${GREEN}Creating namespaces if they don't exist...${NC}"
create_secret "$KUBECTL create namespace tekton-pipelines --dry-run=client -o yaml | $KUBECTL apply -f -"
create_secret "$KUBECTL create namespace flux-system --dry-run=client -o yaml | $KUBECTL apply -f -"
echo ""
echo -e "${GREEN}Creating secrets...${NC}"
# 1. Webhook Secret
echo " Creating gitea-webhook-secret..."
create_secret "$KUBECTL create secret generic gitea-webhook-secret \
--namespace tekton-pipelines \
--from-literal=secretToken='$WEBHOOK_SECRET' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# 2. Registry Credentials (docker-registry type)
echo " Creating gitea-registry-credentials..."
create_secret "$KUBECTL create secret docker-registry gitea-registry-credentials \
--namespace tekton-pipelines \
--docker-server='$REGISTRY_URL' \
--docker-username='$GITEA_USERNAME' \
--docker-password='$GITEA_PASSWORD' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# 3. Git Credentials for Tekton
echo " Creating gitea-git-credentials..."
create_secret "$KUBECTL create secret generic gitea-git-credentials \
--namespace tekton-pipelines \
--from-literal=username='$GITEA_USERNAME' \
--from-literal=password='$GITEA_PASSWORD' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# 4. Flux Git Credentials
echo " Creating gitea-credentials for Flux..."
create_secret "$KUBECTL create secret generic gitea-credentials \
--namespace flux-system \
--from-literal=username='$GITEA_USERNAME' \
--from-literal=password='$GITEA_PASSWORD' \
--dry-run=client -o yaml | $KUBECTL apply -f -"
# Label all secrets
echo ""
echo -e "${GREEN}Adding labels to secrets...${NC}"
for ns in tekton-pipelines flux-system; do
for secret in gitea-webhook-secret gitea-registry-credentials gitea-git-credentials gitea-credentials; do
if $KUBECTL get secret "$secret" -n "$ns" &> /dev/null; then
create_secret "$KUBECTL label secret $secret -n $ns app.kubernetes.io/name=bakery-ia-cicd --overwrite 2>/dev/null || true"
fi
done
done
echo ""
echo "=========================================="
echo -e "${GREEN}Secrets created successfully!${NC}"
echo "=========================================="
echo ""
echo -e "${YELLOW}IMPORTANT:${NC} Save this webhook secret for Gitea webhook configuration:"
echo ""
echo " Webhook Secret: $WEBHOOK_SECRET"
echo ""
echo "Configure this in Gitea:"
echo " 1. Go to Repository Settings > Webhooks"
echo " 2. Add webhook with URL: http://el-bakery-ia-listener.tekton-pipelines.svc.cluster.local:8080"
echo " 3. Set Secret to the webhook secret above"
echo " 4. Select events: Push"
echo ""
# Save webhook secret to a file for reference (gitignored)
if [ "$DRY_RUN" = false ]; then
echo "$WEBHOOK_SECRET" > "$(dirname "$0")/.webhook-secret"
chmod 600 "$(dirname "$0")/.webhook-secret"
echo "Webhook secret saved to .webhook-secret (gitignored)"
fi

View File

@@ -0,0 +1,19 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secrets.yaml
# Note: In production, use sealed-secrets or external-secrets-operator
# to manage secrets securely. The secrets.yaml file contains placeholder
# values that must be replaced before deployment.
#
# Example using sealed-secrets:
# 1. Install sealed-secrets controller
# 2. Create SealedSecret resources instead of plain Secrets
# 3. Commit the SealedSecret manifests to Git (safe to commit)
#
# Example using external-secrets-operator:
# 1. Install external-secrets-operator
# 2. Configure a SecretStore (AWS Secrets Manager, HashiCorp Vault, etc.)
# 3. Create ExternalSecret resources that reference the SecretStore

View File

@@ -0,0 +1,79 @@
# CI/CD Secrets Template for Tekton Pipelines
#
# DO NOT commit this file with actual credentials!
# Use the generate-secrets.sh script to create secrets safely.
#
# For production, use one of these approaches:
# 1. Sealed Secrets: kubeseal < secrets.yaml > sealed-secrets.yaml
# 2. External Secrets Operator: Configure with your secret store
# 3. Manual creation: kubectl create secret ... (see generate-secrets.sh)
---
# Secret for Gitea webhook validation
# Used by EventListener to validate incoming webhooks
apiVersion: v1
kind: Secret
metadata:
name: gitea-webhook-secret
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
type: Opaque
stringData:
# Generate with: openssl rand -hex 32
secretToken: "${WEBHOOK_SECRET_TOKEN}"
---
# Secret for Gitea container registry credentials
# Used by Kaniko to push images to Gitea registry
apiVersion: v1
kind: Secret
metadata:
name: gitea-registry-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: build
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"${REGISTRY_URL}": {
"username": "${GITEA_USERNAME}",
"password": "${GITEA_PASSWORD}"
}
}
}
---
# Secret for Git credentials (used by pipeline to push GitOps updates)
apiVersion: v1
kind: Secret
metadata:
name: gitea-git-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: gitops
type: Opaque
stringData:
username: "${GITEA_USERNAME}"
password: "${GITEA_PASSWORD}"
---
# Secret for Flux GitRepository access
# Used by Flux to pull from Gitea repository
apiVersion: v1
kind: Secret
metadata:
name: gitea-credentials
namespace: flux-system
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: flux
type: Opaque
stringData:
username: "${GITEA_USERNAME}"
password: "${GITEA_PASSWORD}"

View File

@@ -0,0 +1,98 @@
# CI/CD Secrets for Tekton Pipelines
#
# WARNING: This file contains EXAMPLE values only!
# DO NOT use these values in production.
#
# To create actual secrets, use ONE of these methods:
#
# Method 1 (Recommended): Use the generate-secrets.sh script
# ./generate-secrets.sh --gitea-user <username> --gitea-password <password>
#
# Method 2: Create secrets manually with kubectl
# kubectl create secret generic gitea-webhook-secret \
# --namespace tekton-pipelines \
# --from-literal=secretToken="$(openssl rand -hex 32)"
#
# Method 3: Use Sealed Secrets for GitOps
# kubeseal < secrets-template.yaml > sealed-secrets.yaml
#
# Method 4: Use External Secrets Operator
# Configure ESO to pull from your secret store (Vault, AWS SM, etc.)
---
# Example Secret for Gitea webhook validation
# Used by EventListener to validate incoming webhooks
apiVersion: v1
kind: Secret
metadata:
name: gitea-webhook-secret
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: Opaque
stringData:
# Generate with: openssl rand -hex 32
secretToken: "example-webhook-token-do-not-use-in-production"
---
# Example Secret for Gitea container registry credentials
# Used by Kaniko to push images to Gitea registry
apiVersion: v1
kind: Secret
metadata:
name: gitea-registry-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: build
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"gitea.bakery-ia.local:5000": {
"username": "example-user",
"password": "example-password"
}
}
}
---
# Example Secret for Git credentials (used by pipeline to push GitOps updates)
apiVersion: v1
kind: Secret
metadata:
name: gitea-git-credentials
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: gitops
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: Opaque
stringData:
username: "example-user"
password: "example-password"
---
# Example Secret for Flux GitRepository access
# Used by Flux to pull from Gitea repository
apiVersion: v1
kind: Secret
metadata:
name: gitea-credentials
namespace: flux-system
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: flux
annotations:
note: "EXAMPLE - Replace with actual secret using generate-secrets.sh"
type: Opaque
stringData:
username: "example-user"
password: "example-password"

View File

@@ -0,0 +1,154 @@
# Tekton Detect Changed Services Task for Bakery-IA CI/CD
# This task identifies which services have changed in the repository
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: detect-changed-services
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: detect
spec:
workspaces:
- name: source
description: Source code workspace
params:
- name: base-ref
type: string
description: Base reference for comparison (default HEAD~1)
default: "HEAD~1"
results:
- name: changed-services
description: Comma-separated list of changed services
- name: changed-files-count
description: Number of files changed
steps:
- name: detect
image: alpine/git:2.43.0
script: |
#!/bin/sh
set -e
SOURCE_PATH="$(workspaces.source.path)"
BASE_REF="$(params.base-ref)"
cd "$SOURCE_PATH"
echo "============================================"
echo "Detect Changed Services"
echo "============================================"
echo "Base ref: $BASE_REF"
echo "============================================"
# Get list of changed files compared to base reference
echo ""
echo "Detecting changed files..."
# Try to get diff, fall back to listing all files if this is the first commit
CHANGED_FILES=$(git diff --name-only "$BASE_REF" HEAD 2>/dev/null || git ls-tree -r HEAD --name-only)
FILE_COUNT=$(echo "$CHANGED_FILES" | grep -c "." || echo "0")
echo "Found $FILE_COUNT changed files"
echo "$FILE_COUNT" > $(results.changed-files-count.path)
if [ "$FILE_COUNT" = "0" ]; then
echo "No files changed"
echo "none" > $(results.changed-services.path)
exit 0
fi
echo ""
echo "Changed files:"
echo "$CHANGED_FILES" | head -20
if [ "$FILE_COUNT" -gt 20 ]; then
echo "... and $((FILE_COUNT - 20)) more files"
fi
# Map files to services using simple shell (no bash arrays)
echo ""
echo "Mapping files to services..."
CHANGED_SERVICES=""
# Process each file
echo "$CHANGED_FILES" | while read -r file; do
if [ -z "$file" ]; then
continue
fi
# Check services directory
if echo "$file" | grep -q "^services/"; then
SERVICE=$(echo "$file" | cut -d'/' -f2)
if [ -n "$SERVICE" ] && ! echo "$CHANGED_SERVICES" | grep -q "$SERVICE"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="$SERVICE"
else
CHANGED_SERVICES="$CHANGED_SERVICES,$SERVICE"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
# Check frontend
if echo "$file" | grep -q "^frontend/"; then
if ! echo "$CHANGED_SERVICES" | grep -q "frontend"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="frontend"
else
CHANGED_SERVICES="$CHANGED_SERVICES,frontend"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
# Check gateway
if echo "$file" | grep -q "^gateway/"; then
if ! echo "$CHANGED_SERVICES" | grep -q "gateway"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="gateway"
else
CHANGED_SERVICES="$CHANGED_SERVICES,gateway"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
# Check infrastructure
if echo "$file" | grep -q "^infrastructure/"; then
if ! echo "$CHANGED_SERVICES" | grep -q "infrastructure"; then
if [ -z "$CHANGED_SERVICES" ]; then
CHANGED_SERVICES="infrastructure"
else
CHANGED_SERVICES="$CHANGED_SERVICES,infrastructure"
fi
echo "$CHANGED_SERVICES" > /tmp/services.txt
fi
fi
done
# Read the accumulated services
if [ -f /tmp/services.txt ]; then
CHANGED_SERVICES=$(cat /tmp/services.txt)
fi
echo ""
echo "============================================"
# Output result
if [ -z "$CHANGED_SERVICES" ]; then
echo "No service changes detected"
echo "none" > $(results.changed-services.path)
else
echo "Detected changes in services: $CHANGED_SERVICES"
echo "$CHANGED_SERVICES" > $(results.changed-services.path)
fi
echo "============================================"
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

View File

@@ -0,0 +1,95 @@
# Tekton Git Clone Task for Bakery-IA CI/CD
# This task clones the source code repository
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: git-clone
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: source
spec:
workspaces:
- name: output
description: Workspace to clone the repository into
params:
- name: url
type: string
description: Repository URL to clone
- name: revision
type: string
description: Git revision to checkout
default: "main"
- name: depth
type: string
description: Git clone depth (0 for full history)
default: "1"
results:
- name: commit-sha
description: The commit SHA that was checked out
- name: commit-message
description: The commit message
steps:
- name: clone
image: alpine/git:2.43.0
script: |
#!/bin/sh
set -e
URL="$(params.url)"
REVISION="$(params.revision)"
DEPTH="$(params.depth)"
OUTPUT_PATH="$(workspaces.output.path)"
echo "============================================"
echo "Git Clone Task"
echo "============================================"
echo "URL: $URL"
echo "Revision: $REVISION"
echo "Depth: $DEPTH"
echo "============================================"
# Clone with depth for faster checkout
if [ "$DEPTH" = "0" ]; then
echo "Cloning full repository..."
git clone "$URL" "$OUTPUT_PATH"
else
echo "Cloning with depth $DEPTH..."
git clone --depth "$DEPTH" "$URL" "$OUTPUT_PATH"
fi
cd "$OUTPUT_PATH"
# Fetch the specific revision if needed
if [ "$REVISION" != "main" ] && [ "$REVISION" != "master" ]; then
echo "Fetching revision: $REVISION"
git fetch --depth 1 origin "$REVISION" 2>/dev/null || true
fi
# Checkout the revision
echo "Checking out: $REVISION"
git checkout "$REVISION" 2>/dev/null || git checkout "origin/$REVISION"
# Get commit info
COMMIT_SHA=$(git rev-parse HEAD)
COMMIT_MSG=$(git log -1 --pretty=format:"%s")
echo ""
echo "============================================"
echo "Clone Complete"
echo "============================================"
echo "Commit: $COMMIT_SHA"
echo "Message: $COMMIT_MSG"
echo "============================================"
# Write results
echo -n "$COMMIT_SHA" > $(results.commit-sha.path)
echo -n "$COMMIT_MSG" > $(results.commit-message.path)
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi

View File

@@ -0,0 +1,200 @@
# Tekton Kaniko Build Task for Bakery-IA CI/CD
# This task builds and pushes container images using Kaniko
# Supports building multiple services from a comma-separated list
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kaniko-build
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: build
spec:
workspaces:
- name: source
description: Source code workspace
- 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
- name: git-revision
type: string
description: Git revision for image tag
default: "latest"
results:
- name: built-images
description: List of successfully built images
- name: build-status
description: Overall build status (success/failure)
steps:
# Step 1: Setup docker credentials
- name: setup-docker-config
image: alpine:3.18
script: |
#!/bin/sh
set -e
echo "Setting up Docker credentials..."
mkdir -p /kaniko/.docker
# Check if credentials secret is mounted
if [ -f "$(workspaces.docker-credentials.path)/config.json" ]; then
cp "$(workspaces.docker-credentials.path)/config.json" /kaniko/.docker/config.json
echo "Docker config copied from secret"
elif [ -f "$(workspaces.docker-credentials.path)/.dockerconfigjson" ]; then
cp "$(workspaces.docker-credentials.path)/.dockerconfigjson" /kaniko/.docker/config.json
echo "Docker config copied from .dockerconfigjson"
else
echo "Warning: No docker credentials found, builds may fail for private registries"
echo '{}' > /kaniko/.docker/config.json
fi
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi
# Step 2: Build each service iteratively
- name: build-services
image: gcr.io/kaniko-project/executor:v1.23.0
script: |
#!/busybox/sh
set -e
SERVICES="$(params.services)"
REGISTRY="$(params.registry)"
REVISION="$(params.git-revision)"
SOURCE_PATH="$(workspaces.source.path)"
BUILT_IMAGES=""
FAILED_SERVICES=""
echo "============================================"
echo "Starting build for services: $SERVICES"
echo "Registry: $REGISTRY"
echo "Tag: $REVISION"
echo "============================================"
# Skip if no services to build
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to build, skipping..."
echo "none" > $(results.built-images.path)
echo "skipped" > $(results.build-status.path)
exit 0
fi
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
for SERVICE in $SERVICES_LIST; do
# Trim whitespace
SERVICE=$(echo "$SERVICE" | tr -d ' ')
# Skip infrastructure changes (not buildable)
if [ "$SERVICE" = "infrastructure" ]; then
echo "Skipping infrastructure (not a buildable service)"
continue
fi
echo ""
echo "--------------------------------------------"
echo "Building service: $SERVICE"
echo "--------------------------------------------"
# Determine Dockerfile path based on service type
if [ "$SERVICE" = "frontend" ]; then
DOCKERFILE_PATH="$SOURCE_PATH/frontend/Dockerfile"
CONTEXT_PATH="$SOURCE_PATH/frontend"
elif [ "$SERVICE" = "gateway" ]; then
DOCKERFILE_PATH="$SOURCE_PATH/gateway/Dockerfile"
CONTEXT_PATH="$SOURCE_PATH/gateway"
else
DOCKERFILE_PATH="$SOURCE_PATH/services/$SERVICE/Dockerfile"
CONTEXT_PATH="$SOURCE_PATH"
fi
# Check if Dockerfile exists
if [ ! -f "$DOCKERFILE_PATH" ]; then
echo "Warning: Dockerfile not found at $DOCKERFILE_PATH, skipping $SERVICE"
FAILED_SERVICES="$FAILED_SERVICES $SERVICE"
continue
fi
IMAGE_NAME="$REGISTRY/bakery/$SERVICE:$REVISION"
IMAGE_NAME_LATEST="$REGISTRY/bakery/$SERVICE:latest"
echo "Dockerfile: $DOCKERFILE_PATH"
echo "Context: $CONTEXT_PATH"
echo "Image: $IMAGE_NAME"
# Run Kaniko build
/kaniko/executor \
--dockerfile="$DOCKERFILE_PATH" \
--context="$CONTEXT_PATH" \
--destination="$IMAGE_NAME" \
--destination="$IMAGE_NAME_LATEST" \
--cache=true \
--cache-ttl=24h \
--verbosity=info \
--snapshot-mode=redo \
--use-new-run
BUILD_EXIT_CODE=$?
if [ $BUILD_EXIT_CODE -eq 0 ]; then
echo "Successfully built and pushed: $IMAGE_NAME"
if [ -z "$BUILT_IMAGES" ]; then
BUILT_IMAGES="$IMAGE_NAME"
else
BUILT_IMAGES="$BUILT_IMAGES,$IMAGE_NAME"
fi
else
echo "Failed to build: $SERVICE (exit code: $BUILD_EXIT_CODE)"
FAILED_SERVICES="$FAILED_SERVICES $SERVICE"
fi
done
echo ""
echo "============================================"
echo "Build Summary"
echo "============================================"
echo "Built images: $BUILT_IMAGES"
echo "Failed services: $FAILED_SERVICES"
# Write results
if [ -z "$BUILT_IMAGES" ]; then
echo "none" > $(results.built-images.path)
else
echo "$BUILT_IMAGES" > $(results.built-images.path)
fi
if [ -n "$FAILED_SERVICES" ]; then
echo "partial" > $(results.build-status.path)
echo "Warning: Some services failed to build: $FAILED_SERVICES"
else
echo "success" > $(results.build-status.path)
fi
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
securityContext:
runAsUser: 0
resources:
limits:
cpu: 2000m
memory: 4Gi
requests:
cpu: 500m
memory: 1Gi
volumes:
- name: docker-config
emptyDir: {}

View File

@@ -0,0 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- git-clone.yaml
- detect-changes.yaml
- run-tests.yaml
- kaniko-build.yaml
- update-gitops.yaml
- pipeline-summary.yaml
# Production deployment tasks
- verify-images.yaml
- pre-deploy-validation.yaml
- prod-deployment-summary.yaml

View File

@@ -0,0 +1,62 @@
# Tekton Pipeline Summary Task for Bakery-IA CI/CD
# This task runs at the end of the pipeline and provides a summary
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: pipeline-summary
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: summary
spec:
params:
- name: changed-services
type: string
description: List of changed services
default: "none"
- name: git-revision
type: string
description: Git revision that was built
default: "unknown"
steps:
- name: summary
image: alpine:3.18
script: |
#!/bin/sh
SERVICES="$(params.changed-services)"
REVISION="$(params.git-revision)"
echo ""
echo "============================================"
echo " Pipeline Execution Summary"
echo "============================================"
echo ""
echo "Git Revision: $REVISION"
echo "Changed Services: $SERVICES"
echo ""
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo ""
echo "============================================"
echo ""
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services were changed in this commit."
echo "Pipeline completed without building any images."
else
echo "The following services were processed:"
echo "$SERVICES" | tr ',' '\n' | while read service; do
echo " - $service"
done
fi
echo ""
echo "============================================"
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi

View File

@@ -0,0 +1,76 @@
# Task for pre-deployment validation
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: pre-deploy-validation
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: validation
spec:
workspaces:
- name: source
description: Source code workspace
params:
- name: services
type: string
description: Comma-separated list of services to validate
- name: environment
type: string
description: Target environment (staging/production)
default: "production"
results:
- name: validation-status
description: Status of validation (passed/failed)
steps:
- name: validate
image: registry.k8s.io/kustomize/kustomize:v5.3.0
script: |
#!/bin/sh
set -e
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
ENVIRONMENT="$(params.environment)"
echo "============================================"
echo "Pre-Deployment Validation"
echo "============================================"
echo "Environment: $ENVIRONMENT"
echo "Services: $SERVICES"
echo "============================================"
cd "$SOURCE_PATH"
# Validate kustomization can be built
KUSTOMIZE_DIR="infrastructure/environments/$ENVIRONMENT"
if [ -d "$KUSTOMIZE_DIR" ]; then
echo ""
echo "Validating kustomization..."
if kustomize build "$KUSTOMIZE_DIR" > /dev/null 2>&1; then
echo " ✓ Kustomization is valid"
else
echo " ✗ Kustomization validation failed"
echo "failed" > $(results.validation-status.path)
exit 1
fi
fi
# Additional validation checks can be added here
# - Schema validation
# - Policy checks (OPA/Gatekeeper)
# - Security scanning
echo ""
echo "============================================"
echo "All validations passed"
echo "============================================"
echo "passed" > $(results.validation-status.path)
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi

View File

@@ -0,0 +1,57 @@
# Task for production deployment summary
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: prod-deployment-summary
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: summary
spec:
params:
- name: services
type: string
description: List of deployed services
- name: git-revision
type: string
description: Git revision that was deployed
- name: approver
type: string
description: Name of the approver
- name: approval-ticket
type: string
description: Approval ticket number
steps:
- name: summary
image: alpine:3.18
script: |
#!/bin/sh
echo ""
echo "============================================"
echo " Production Deployment Summary"
echo "============================================"
echo ""
echo "Git Revision: $(params.git-revision)"
echo "Services: $(params.services)"
echo "Approved By: $(params.approver)"
echo "Approval Ticket: $(params.approval-ticket)"
echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo ""
echo "============================================"
echo ""
echo "Deployment to production initiated."
echo "Flux CD will reconcile the changes."
echo ""
echo "Monitor deployment status with:"
echo " kubectl get kustomization -n flux-system"
echo " kubectl get pods -n bakery-ia"
echo ""
echo "============================================"
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi

View File

@@ -0,0 +1,205 @@
# Tekton Test Task for Bakery-IA CI/CD
# This task runs unit tests and linting for changed services
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: run-tests
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: test
spec:
workspaces:
- name: source
description: Source code workspace
params:
- name: services
type: string
description: Comma-separated list of services to test
- name: skip-lint
type: string
description: Skip linting if "true"
default: "false"
- name: skip-tests
type: string
description: Skip tests if "true"
default: "false"
results:
- name: test-status
description: Overall test status (passed/failed/skipped)
- name: tested-services
description: List of services that were tested
- name: failed-services
description: List of services that failed tests
steps:
- name: run-tests
image: python:3.11-slim
script: |
#!/bin/bash
set -e
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
SKIP_LINT="$(params.skip-lint)"
SKIP_TESTS="$(params.skip-tests)"
TESTED_SERVICES=""
FAILED_SERVICES=""
OVERALL_STATUS="passed"
cd "$SOURCE_PATH"
echo "============================================"
echo "Running Tests"
echo "============================================"
echo "Services: $SERVICES"
echo "Skip Lint: $SKIP_LINT"
echo "Skip Tests: $SKIP_TESTS"
echo "============================================"
# Skip if no services to test
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to test, skipping..."
echo "skipped" > $(results.test-status.path)
echo "none" > $(results.tested-services.path)
echo "none" > $(results.failed-services.path)
exit 0
fi
# Install common test dependencies
echo ""
echo "Installing test dependencies..."
pip install --quiet pytest pytest-cov pytest-asyncio ruff mypy 2>/dev/null || true
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
for SERVICE in $SERVICES_LIST; do
# Trim whitespace
SERVICE=$(echo "$SERVICE" | tr -d ' ')
# Skip infrastructure changes
if [ "$SERVICE" = "infrastructure" ]; then
echo "Skipping infrastructure (not testable)"
continue
fi
echo ""
echo "--------------------------------------------"
echo "Testing service: $SERVICE"
echo "--------------------------------------------"
# Determine service path
if [ "$SERVICE" = "frontend" ]; then
SERVICE_PATH="$SOURCE_PATH/frontend"
elif [ "$SERVICE" = "gateway" ]; then
SERVICE_PATH="$SOURCE_PATH/gateway"
else
SERVICE_PATH="$SOURCE_PATH/services/$SERVICE"
fi
# Check if service exists
if [ ! -d "$SERVICE_PATH" ]; then
echo "Warning: Service directory not found: $SERVICE_PATH"
continue
fi
cd "$SERVICE_PATH"
SERVICE_FAILED=false
# Install service-specific dependencies if requirements.txt exists
if [ -f "requirements.txt" ]; then
echo "Installing service dependencies..."
pip install --quiet -r requirements.txt 2>/dev/null || true
fi
# Run linting (ruff)
if [ "$SKIP_LINT" != "true" ]; then
echo ""
echo "Running linter (ruff)..."
if [ -d "app" ]; then
ruff check app/ --output-format=text 2>&1 || {
echo "Linting failed for $SERVICE"
SERVICE_FAILED=true
}
fi
fi
# Run tests
if [ "$SKIP_TESTS" != "true" ]; then
echo ""
echo "Running tests (pytest)..."
if [ -d "tests" ]; then
pytest tests/ -v --tb=short 2>&1 || {
echo "Tests failed for $SERVICE"
SERVICE_FAILED=true
}
elif [ -d "app/tests" ]; then
pytest app/tests/ -v --tb=short 2>&1 || {
echo "Tests failed for $SERVICE"
SERVICE_FAILED=true
}
else
echo "No tests directory found, skipping tests"
fi
fi
# Track results
if [ -z "$TESTED_SERVICES" ]; then
TESTED_SERVICES="$SERVICE"
else
TESTED_SERVICES="$TESTED_SERVICES,$SERVICE"
fi
if [ "$SERVICE_FAILED" = true ]; then
OVERALL_STATUS="failed"
if [ -z "$FAILED_SERVICES" ]; then
FAILED_SERVICES="$SERVICE"
else
FAILED_SERVICES="$FAILED_SERVICES,$SERVICE"
fi
fi
cd "$SOURCE_PATH"
done
echo ""
echo "============================================"
echo "Test Summary"
echo "============================================"
echo "Tested services: $TESTED_SERVICES"
echo "Failed services: $FAILED_SERVICES"
echo "Overall status: $OVERALL_STATUS"
# Write results
echo "$OVERALL_STATUS" > $(results.test-status.path)
if [ -z "$TESTED_SERVICES" ]; then
echo "none" > $(results.tested-services.path)
else
echo "$TESTED_SERVICES" > $(results.tested-services.path)
fi
if [ -z "$FAILED_SERVICES" ]; then
echo "none" > $(results.failed-services.path)
else
echo "$FAILED_SERVICES" > $(results.failed-services.path)
fi
# Exit with error if tests failed
if [ "$OVERALL_STATUS" = "failed" ]; then
echo ""
echo "ERROR: Some tests failed!"
exit 1
fi
echo ""
echo "All tests passed!"
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi

View File

@@ -0,0 +1,302 @@
# Tekton Update GitOps Manifests Task for Bakery-IA CI/CD
# This task updates Kubernetes manifests with new image tags using Kustomize
# It uses a safer approach than sed for updating image references
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: update-gitops
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: gitops
spec:
workspaces:
- name: source
description: Source code workspace with Git repository
- name: git-credentials
description: Git credentials for pushing changes
optional: true
params:
- name: services
type: string
description: Comma-separated list of services to update
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision for image tag
- name: git-branch
type: string
description: Target branch for GitOps updates
default: "main"
- name: dry-run
type: string
description: If "true", only show what would be changed without committing
default: "false"
results:
- name: updated-services
description: List of services that were updated
- name: commit-sha
description: Git commit SHA of the update (empty if dry-run)
steps:
- name: update-manifests
# Use alpine with curl to install kustomize
image: alpine:3.19
script: |
#!/bin/sh
set -e
# Install kustomize
echo "Installing kustomize..."
wget -q "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -O - | sh
mv kustomize /usr/local/bin/
echo "Kustomize version: $(kustomize version)"
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
REGISTRY="$(params.registry)"
REVISION="$(params.git-revision)"
DRY_RUN="$(params.dry-run)"
UPDATED_SERVICES=""
cd "$SOURCE_PATH"
echo "============================================"
echo "GitOps Manifest Update"
echo "============================================"
echo "Services: $SERVICES"
echo "Registry: $REGISTRY"
echo "Revision: $REVISION"
echo "Dry Run: $DRY_RUN"
echo "============================================"
# Skip if no services to update
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to update, skipping..."
echo "none" > $(results.updated-services.path)
echo "" > $(results.commit-sha.path)
exit 0
fi
# Define the kustomization directory
KUSTOMIZE_DIR="infrastructure/environments/prod"
# Check if kustomization.yaml exists, create if not
if [ ! -f "$KUSTOMIZE_DIR/kustomization.yaml" ]; then
echo "Creating kustomization.yaml in $KUSTOMIZE_DIR"
mkdir -p "$KUSTOMIZE_DIR"
printf '%s\n' \
"apiVersion: kustomize.config.k8s.io/v1beta1" \
"kind: Kustomization" \
"" \
"resources:" \
" - ../base" \
"" \
"images: []" \
> "$KUSTOMIZE_DIR/kustomization.yaml"
fi
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
# Build the images section for kustomization
echo ""
echo "Updating image references..."
for SERVICE in $SERVICES_LIST; do
# Trim whitespace
SERVICE=$(echo "$SERVICE" | tr -d ' ')
# Skip infrastructure changes
if [ "$SERVICE" = "infrastructure" ]; then
echo "Skipping infrastructure (not a deployable service)"
continue
fi
echo "Processing: $SERVICE"
# Determine the image name based on service
NEW_IMAGE="$REGISTRY/bakery/$SERVICE:$REVISION"
# Use kustomize to set the image
# This is safer than sed as it understands the YAML structure
cd "$SOURCE_PATH/$KUSTOMIZE_DIR"
# Check if this service has a deployment
SERVICE_DEPLOYMENT=""
if [ "$SERVICE" = "frontend" ]; then
SERVICE_DEPLOYMENT="frontend"
elif [ "$SERVICE" = "gateway" ]; then
SERVICE_DEPLOYMENT="gateway"
else
SERVICE_DEPLOYMENT="$SERVICE-service"
fi
# Update the kustomization with the new image
# Using kustomize edit to safely modify the file
kustomize edit set image "bakery/$SERVICE=$NEW_IMAGE" 2>/dev/null || \
kustomize edit set image "$SERVICE=$NEW_IMAGE" 2>/dev/null || \
echo "Note: Could not set image via kustomize edit, will use alternative method"
# Track updated services
if [ -z "$UPDATED_SERVICES" ]; then
UPDATED_SERVICES="$SERVICE"
else
UPDATED_SERVICES="$UPDATED_SERVICES,$SERVICE"
fi
cd "$SOURCE_PATH"
done
# Alternative: Update images in kustomization.yaml directly if kustomize edit didn't work
# This creates/updates an images section in the kustomization
echo ""
echo "Ensuring image overrides in kustomization.yaml..."
# Create a patch file for image updates
IMAGES_FILE="$KUSTOMIZE_DIR/images.yaml"
printf '%s\n' \
"# Auto-generated by CI/CD pipeline" \
"# Commit: $REVISION" \
"# Updated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
"images:" \
> "$IMAGES_FILE"
for SERVICE in $SERVICES_LIST; do
SERVICE=$(echo "$SERVICE" | tr -d ' ')
if [ "$SERVICE" != "infrastructure" ]; then
printf '%s\n' \
" - name: bakery/$SERVICE" \
" newName: $REGISTRY/bakery/$SERVICE" \
" newTag: \"$REVISION\"" \
>> "$IMAGES_FILE"
fi
done
echo ""
echo "Generated images.yaml:"
cat "$IMAGES_FILE"
# Validate the kustomization
echo ""
echo "Validating kustomization..."
cd "$SOURCE_PATH/$KUSTOMIZE_DIR"
if kustomize build . > /dev/null 2>&1; then
echo "Kustomization is valid"
else
echo "Warning: Kustomization validation failed, but continuing..."
fi
cd "$SOURCE_PATH"
# Write results
echo "$UPDATED_SERVICES" > $(results.updated-services.path)
if [ "$DRY_RUN" = "true" ]; then
echo ""
echo "============================================"
echo "DRY RUN - Changes not committed"
echo "============================================"
echo "Would update services: $UPDATED_SERVICES"
git diff --stat || true
echo "" > $(results.commit-sha.path)
exit 0
fi
echo ""
echo "Committing changes..."
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
- name: commit-and-push
image: alpine/git:2.43.0
script: |
#!/bin/sh
set -e
SOURCE_PATH="$(workspaces.source.path)"
SERVICES="$(params.services)"
REVISION="$(params.git-revision)"
BRANCH="$(params.git-branch)"
DRY_RUN="$(params.dry-run)"
cd "$SOURCE_PATH"
if [ "$DRY_RUN" = "true" ]; then
echo "Dry run mode - skipping commit"
echo "" > $(results.commit-sha.path)
exit 0
fi
if [ "$SERVICES" = "none" ] || [ -z "$SERVICES" ]; then
echo "No services to commit"
echo "" > $(results.commit-sha.path)
exit 0
fi
# Check if there are changes to commit
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to commit"
echo "" > $(results.commit-sha.path)
exit 0
fi
# Configure git
git config --global user.name "bakery-ia-ci"
git config --global user.email "ci@bakery-ia.local"
git config --global --add safe.directory "$SOURCE_PATH"
# Setup git credentials if provided
if [ -d "$(workspaces.git-credentials.path)" ]; then
if [ -f "$(workspaces.git-credentials.path)/username" ] && [ -f "$(workspaces.git-credentials.path)/password" ]; then
GIT_USER=$(cat "$(workspaces.git-credentials.path)/username")
GIT_PASS=$(cat "$(workspaces.git-credentials.path)/password")
# Get the remote URL and inject credentials
REMOTE_URL=$(git remote get-url origin)
# Handle both http and https
if echo "$REMOTE_URL" | grep -q "^http"; then
REMOTE_URL=$(echo "$REMOTE_URL" | sed "s|://|://$GIT_USER:$GIT_PASS@|")
git remote set-url origin "$REMOTE_URL"
fi
fi
fi
# Stage changes
git add -A
# Create commit with detailed message
COMMIT_MSG=$(printf 'ci: Update image tags to %s\n\nServices updated: %s\n\nThis commit was automatically generated by the CI/CD pipeline.\nPipeline run triggered by commit: %s' "$REVISION" "$SERVICES" "$REVISION")
git commit -m "$COMMIT_MSG"
# Get the commit SHA
COMMIT_SHA=$(git rev-parse HEAD)
echo "$COMMIT_SHA" > $(results.commit-sha.path)
echo "Created commit: $COMMIT_SHA"
# Push changes
echo "Pushing to origin/$BRANCH..."
git push origin HEAD:"$BRANCH"
echo ""
echo "============================================"
echo "GitOps Update Complete"
echo "============================================"
echo "Commit: $COMMIT_SHA"
echo "Branch: $BRANCH"
echo "Services: $SERVICES"
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

View File

@@ -0,0 +1,91 @@
# Task to verify images exist in the registry before deploying
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: verify-images
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: validation
spec:
params:
- name: services
type: string
description: Comma-separated list of services to verify
- name: registry
type: string
description: Container registry URL
- name: git-revision
type: string
description: Git revision/tag to verify
results:
- name: verification-status
description: Status of image verification (success/failed)
- name: missing-images
description: List of images that were not found
steps:
- name: verify
image: gcr.io/go-containerregistry/crane:latest
script: |
#!/bin/sh
set -e
SERVICES="$(params.services)"
REGISTRY="$(params.registry)"
REVISION="$(params.git-revision)"
MISSING=""
echo "============================================"
echo "Verifying Images in Registry"
echo "============================================"
echo "Registry: $REGISTRY"
echo "Revision: $REVISION"
echo "Services: $SERVICES"
echo "============================================"
# Convert comma-separated list to space-separated
SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ')
for SERVICE in $SERVICES_LIST; do
SERVICE=$(echo "$SERVICE" | tr -d ' ')
if [ "$SERVICE" = "infrastructure" ]; then
continue
fi
IMAGE="$REGISTRY/bakery/$SERVICE:$REVISION"
echo ""
echo "Checking: $IMAGE"
if crane manifest "$IMAGE" > /dev/null 2>&1; then
echo " ✓ Found"
else
echo " ✗ NOT FOUND"
if [ -z "$MISSING" ]; then
MISSING="$SERVICE"
else
MISSING="$MISSING,$SERVICE"
fi
fi
done
echo ""
echo "============================================"
if [ -n "$MISSING" ]; then
echo "ERROR: Missing images: $MISSING"
echo "failed" > $(results.verification-status.path)
echo "$MISSING" > $(results.missing-images.path)
exit 1
fi
echo "All images verified successfully"
echo "success" > $(results.verification-status.path)
echo "none" > $(results.missing-images.path)
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi

View File

@@ -0,0 +1,35 @@
# Tekton EventListener for Bakery-IA CI/CD
# This listener receives webhook events and triggers pipelines
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: bakery-ia-listener
namespace: tekton-pipelines
spec:
serviceAccountName: tekton-triggers-sa
triggers:
- name: bakery-ia-gitea-trigger
bindings:
- ref: bakery-ia-trigger-binding
template:
ref: bakery-ia-trigger-template
# Using CEL interceptor for local development (no TLS/CA bundle required)
# The CEL interceptor is built-in and doesn't need external services
interceptors:
- name: "filter-push-events"
ref:
name: "cel"
params:
# Filter for push events from Gitea or GitHub
- name: "filter"
value: "header.match('X-Gitea-Event', 'push') || header.match('X-GitHub-Event', 'push')"
# Add overlays to standardize the payload
- name: "overlays"
value:
- key: "git_url"
expression: "body.repository.clone_url"
- key: "git_revision"
expression: "body.after"
- key: "git_branch"
expression: "body.ref.split('/')[2]"

View File

@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
# NOTE: gitlab-interceptor.yaml removed - uses built-in Tekton Triggers interceptor
# The gitlab ClusterInterceptor is provided by Tekton Triggers installation
- event-listener.yaml
- trigger-template.yaml
- trigger-binding.yaml

View File

@@ -0,0 +1,31 @@
# Tekton TriggerBinding for Bakery-IA CI/CD
# This binding extracts parameters from Gitea webhook events
#
# Note: We use CEL overlay extensions for consistent field access
# The EventListener's CEL interceptor creates these extensions:
# - extensions.git_url: Repository clone URL
# - extensions.git_revision: Commit SHA (from body.after)
# - extensions.git_branch: Branch name (extracted from ref)
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: bakery-ia-trigger-binding
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
spec:
params:
# Use CEL overlay extensions for consistent access across Git providers
- name: git-repo-url
value: $(extensions.git_url)
- name: git-revision
value: $(extensions.git_revision)
- name: git-branch
value: $(extensions.git_branch)
# Direct body access for fields not in overlays
- name: git-repo-name
value: $(body.repository.name)
- name: git-repo-full-name
value: $(body.repository.full_name)

View File

@@ -0,0 +1,86 @@
# Tekton TriggerTemplate for Bakery-IA CI/CD
# This template defines how PipelineRuns are created when triggers fire
#
# Registry URL Configuration:
# The registry URL is configured via the 'registry' parameter.
# Default value should match pipeline-config ConfigMap's REGISTRY_URL.
# To change the registry, update BOTH:
# 1. This template's default value
# 2. The pipeline-config ConfigMap
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: bakery-ia-trigger-template
namespace: tekton-pipelines
labels:
app.kubernetes.io/name: bakery-ia-cicd
app.kubernetes.io/component: triggers
spec:
params:
- name: git-repo-url
description: The git repository URL
- name: git-revision
description: The git revision/commit hash
- name: git-branch
description: The git branch name
default: "main"
- name: git-repo-name
description: The git repository name
default: "bakery-ia"
- name: git-repo-full-name
description: The full repository name (org/repo)
default: "bakery/bakery-ia"
# Registry URL - keep in sync with pipeline-config ConfigMap
- name: registry-url
description: Container registry URL
default: "gitea.bakery-ia.local:5000"
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: bakery-ia-ci-run-
labels:
app.kubernetes.io/name: bakery-ia-cicd
tekton.dev/pipeline: bakery-ia-ci
triggers.tekton.dev/trigger: bakery-ia-gitea-trigger
annotations:
# Track the source commit
bakery-ia.io/git-revision: $(tt.params.git-revision)
bakery-ia.io/git-branch: $(tt.params.git-branch)
spec:
pipelineRef:
name: bakery-ia-ci
serviceAccountName: tekton-pipeline-sa
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
- name: docker-credentials
secret:
secretName: gitea-registry-credentials
- name: git-credentials
secret:
secretName: gitea-git-credentials
params:
- name: git-url
value: $(tt.params.git-repo-url)
- name: git-revision
value: $(tt.params.git-revision)
- name: git-branch
value: $(tt.params.git-branch)
# Use template parameter for registry URL
- name: registry
value: $(tt.params.registry-url)
- name: skip-tests
value: "false"
- name: dry-run
value: "false"
# Timeout for the entire pipeline run
timeouts:
pipeline: "1h0m0s"
tasks: "45m0s"