initial commit

Change-Id: I9c68c43e939d2c1a3b95a68b71ecc5ba861a4df5
This commit is contained in:
Scaffolder
2026-03-05 13:37:56 +00:00
commit 7e119cad41
24 changed files with 3024 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
name: Build and Push to ACR
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch: {}
concurrency:
group: -
cancel-in-progress: true
env:
AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token
jobs:
build:
name: Build and Push
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- name: Install Maven
run: |
echo "Installing Maven..."
apt-get update -qq
apt-get install -y maven
mvn --version
echo "Maven installed"
- name: Build with Maven
run: |
echo "Building online-boutique..."
mvn clean package -DskipTests -B
echo "Build completed"
- name: Run tests
run: |
echo "Running tests..."
mvn test -B
echo "Tests passed"
# ── Install Azure CLI ───────────────────────────────────────────────
# act runners don't include az by default — install via Microsoft's
# official script which works on Debian/Ubuntu without sudo.
- name: Install Azure CLI
run: |
if command -v az &>/dev/null; then
echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)"
else
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
fi
# ── Authenticate to Azure ──────────────────────────────────────────
- name: Azure login (OIDC)
run: |
az login \
--service-principal \
--username "$AZURE_CLIENT_ID" \
--tenant "$AZURE_TENANT_ID" \
--federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)"
echo "✓ Azure login successful"
- name: Get ACR details
id: acr
run: |
echo "Getting ACR details..."
ACR_NAME=$(az acr list --query "[0].name" -o tsv)
ACR_NAME="${ACR_NAME:-bstagecjotdevacr}"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
# Validation
if [ -z "$ACR_NAME" ]; then
echo "❌ ACR_NAME is empty"
exit 1
fi
if [ -z "$ACR_LOGIN_SERVER" ]; then
echo "❌ ACR_LOGIN_SERVER is empty"
exit 1
fi
echo "ACR_NAME=$ACR_NAME" >> $GITHUB_ENV
echo "ACR_LOGIN_SERVER=$ACR_LOGIN_SERVER" >> $GITHUB_ENV
echo "✓ Using ACR: $ACR_LOGIN_SERVER"
- name: ACR Login
run: |
echo "Logging into ACR..."
echo "ACR_NAME='$ACR_NAME'"
ACR_TOKEN=$(az acr login --name "$ACR_NAME" --expose-token --output tsv --query accessToken)
docker login "$ACR_LOGIN_SERVER" --username "$AZURE_CLIENT_ID" --password "$ACR_TOKEN"
echo "✓ ACR login successful"
- name: Build and Push Docker Image
run: |
IMAGE_NAME="online-boutique"
IMAGE_TAG=""
IMAGE_FULL="/$IMAGE_NAME:$IMAGE_TAG"
IMAGE_LATEST="/$IMAGE_NAME:latest"
echo "Building Docker image..."
docker build -t $IMAGE_NAME:$IMAGE_TAG .
echo "Tagging images..."
docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_FULL
docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_LATEST
echo "Pushing to ACR..."
docker push $IMAGE_FULL
docker push $IMAGE_LATEST
echo "Images pushed:"
echo " - $IMAGE_FULL"
echo " - $IMAGE_LATEST"
echo "IMAGE_FULL=$IMAGE_FULL" >> $GITHUB_ENV
- name: Build Summary
run: |
echo "### Build Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Application**: online-boutique" >> $GITHUB_STEP_SUMMARY
echo "- **Commit**: " >> $GITHUB_STEP_SUMMARY
echo "- **Image**: " >> $GITHUB_STEP_SUMMARY
echo "- **ACR**: " >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Built, tested, and pushed to ACR!" >> $GITHUB_STEP_SUMMARY

View File

@@ -0,0 +1,196 @@
name: Deploy to Humanitec
on:
# Runs automatically after Build and Push succeeds — prevents deploying before image exists
workflow_run:
workflows: ["Build and Push to ACR"]
types:
- completed
branches: [ "main" ]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'development'
type: choice
options:
- development
- staging
- production
env:
APP_ID: online-boutique
AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token
DEFAULT_ENV_ID: development
jobs:
deploy:
name: Deploy to Humanitec
runs-on: ubuntu-latest
# Only deploy when build succeeded (or when manually dispatched)
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
permissions:
contents: read
id-token: write
outputs:
sha: NaN
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get short SHA
id: get-sha
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Install CLI tools
run: |
echo "Installing required tools..."
# Install yq
echo "Installing yq..."
wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq
yq --version
# Install jq
echo "Installing jq..."
wget -qO /usr/local/bin/jq https://github.com/jqlang/jq/releases/latest/download/jq-linux-amd64
chmod +x /usr/local/bin/jq
jq --version
# Install humctl
echo "Installing humctl..."
cd /tmp
HUMCTL_VERSION=$(curl -s https://api.github.com/repos/humanitec/cli/releases/latest | jq -r '.tag_name')
wget -q https://github.com/humanitec/cli/releases/download/${HUMCTL_VERSION}/cli_${HUMCTL_VERSION#v}_linux_amd64.tar.gz
tar -xzf cli_${HUMCTL_VERSION#v}_linux_amd64.tar.gz
mv humctl /usr/local/bin/
chmod +x /usr/local/bin/humctl
cd -
humctl version
echo "Tools installed"
# ── Install Azure CLI ───────────────────────────────────────────────
# act runners don't include az by default — install via Microsoft's
# official script which works on Debian/Ubuntu without sudo.
- name: Install Azure CLI
run: |
if command -v az &>/dev/null; then
echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)"
else
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
fi
# ── Authenticate to Azure ──────────────────────────────────────────
- name: Azure login (OIDC)
run: |
az login \
--service-principal \
--username "$AZURE_CLIENT_ID" \
--tenant "$AZURE_TENANT_ID" \
--federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)"
echo "✓ Azure login successful"
- name: Get image reference
id: image
run: |
ACR_LOGIN_SERVER=$(az acr list --query "[0].loginServer" -o tsv)
COMMIT_SHA=NaN
IMAGE_FULL="${ACR_LOGIN_SERVER}/online-boutique:${COMMIT_SHA}"
echo "IMAGE_FULL=$IMAGE_FULL" >> $GITHUB_ENV
echo "SHA: $COMMIT_SHA"
echo "✓ Image: $IMAGE_FULL"
- name: Set environment
id: set-env
run: |
if [ "" = "workflow_dispatch" ]; then
ENV_ID=""
else
ENV_ID="$DEFAULT_ENV_ID"
fi
# Set default if empty
ENV_ID="${ENV_ID:-development}"
echo "ENV_ID=$ENV_ID" >> $GITHUB_OUTPUT
echo "ENV_ID=$ENV_ID" >> $GITHUB_ENV
echo "DEPLOYMENT_ID=$(date +%Y%m%d-%H%M%S)-NaN" >> $GITHUB_OUTPUT
echo "✓ Using environment: $ENV_ID"
- name: Get Humanitec token from Key Vault
id: get-secret
run: |
echo "Retrieving Humanitec token from Key Vault..."
HUMANITEC_TOKEN=$(az keyvault secret show \
--vault-name "bstage-cjot-dev-core-kv" \
--name "humanitec-api-token" \
--query "value" \
--output tsv)
if [ -z "$HUMANITEC_TOKEN" ]; then
echo "❌ Failed to retrieve Humanitec token"
exit 1
fi
echo "HUMANITEC_TOKEN=$HUMANITEC_TOKEN" >> $GITHUB_ENV
echo "✓ Humanitec token retrieved successfully"
HUMANITEC_ORG=$(az keyvault secret show \
--vault-name "bstage-cjot-dev-core-kv" \
--name "humanitec-org-id" \
--query "value" \
--output tsv)
if [ -z "$HUMANITEC_ORG" ]; then
echo "❌ Failed to retrieve Humanitec org"
exit 1
fi
echo "HUMANITEC_ORG=$HUMANITEC_ORG" >> $GITHUB_ENV
echo "✓ Humanitec org retrieved successfully"
- name: Deploy via Humanitec Score
id: deploy
run: |
# Capture outputs from previous steps as environment variables
echo " Org: $HUMANITEC_ORG"
echo " App: $APP_ID"
echo " Env: $ENV_ID"
echo " Image: $IMAGE_FULL"
echo " sha: NaN"
# Deploy using Score
humctl score deploy \
--org "$HUMANITEC_ORG" \
--app "$APP_ID" \
--env "$ENV_ID" \
--file score.yaml \
--image containers.app=$IMAGE_FULL \
--message "Deployment from commit NaN" \
--token "$HUMANITEC_TOKEN" \
-v 5
echo "✓ Deployment successful!"
- name: Deployment Summary
run: |
DEPLOY_URL="https://app.humanitec.io/orgs/${HUMANITEC_ORG}/apps/${APP_ID}/envs/$ENV_ID"
GRAFANA_URL="https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=${APP_ID}"
echo "### 🚀 Deployment Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Application**: ${APP_ID}" >> $GITHUB_STEP_SUMMARY
echo "- **Environment**: $ENV_ID" >> $GITHUB_STEP_SUMMARY
echo "- **Image**: $IMAGE_FULL" >> $GITHUB_STEP_SUMMARY
echo "- **Commit**: NaN" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📊 **Links:**" >> $GITHUB_STEP_SUMMARY
echo "- [Humanitec Console]($DEPLOY_URL)" >> $GITHUB_STEP_SUMMARY
echo "- [Grafana Dashboard]($GRAFANA_URL)" >> $GITHUB_STEP_SUMMARY

View File

@@ -0,0 +1,107 @@
name: Build and Publish TechDocs
on:
workflow_run:
workflows: ["Build and Push to ACR"]
branches: [main]
types: [completed]
workflow_dispatch: {}
env:
TECHDOCS_AZURE_BLOB_CONTAINER_NAME:
AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token
AZURE_ACCOUNT_NAME: "bstagecjotdevsttechdocs"
ENTITY_NAMESPACE: default
ENTITY_KIND: component
ENTITY_NAME: online-boutique
jobs:
build-and-publish:
if: >-
${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for git-revision-date-localized plugin
- name: read and set output
id: read_env
run: |
echo "$AZURE_FEDERATED_TOKEN_FILE"
env | grep AZURE
echo "$(cat $AZURE_FEDERATED_TOKEN_FILE)"
# act-based Gitea runners run as root — sudo is not available.
# apt-get is called directly; works whether root or not.
- name: Bootstrap pip
run: |
python3 --version
if python3 -m pip --version 2>/dev/null; then
echo "pip already available"
elif python3 -m ensurepip --version 2>/dev/null; then
python3 -m ensurepip --upgrade
else
apt-get update -qq
apt-get install -y python3-pip
fi
python3 -m pip install --upgrade pip
python3 -m pip --version
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install \
mkdocs-techdocs-core==1.* \
mkdocs-git-revision-date-localized-plugin \
mkdocs-awesome-pages-plugin
npm install -g @techdocs/cli
# mkdocs has no dry-run flag — build into a temp dir to validate config
# and catch any broken links or missing pages early.
- name: Validate MkDocs config
run: mkdocs build --strict --site-dir /tmp/mkdocs-validate
- name: Build TechDocs site
run: |
techdocs-cli generate \
--source-dir . \
--output-dir ./site \
--no-docker \
--verbose
# act runners don't include az by default — install via Microsoft's
# official script which works on Debian/Ubuntu without sudo.
- name: Install Azure CLI
run: |
if command -v az &>/dev/null; then
echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)"
else
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
fi
- name: Azure login (OIDC)
run: |
az login \
--service-principal \
--username "$AZURE_CLIENT_ID" \
--tenant "$AZURE_TENANT_ID" \
--federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)"
echo "✓ Azure login successful"
- name: Publish TechDocs site
run: |
echo "$AZURE_ACCOUNT_NAME"
echo "$ENTITY_NAMESPACE"
echo "$ENTITY_KIND"
echo "$ENTITY_NAME"
techdocs-cli publish \
--publisher-type azureBlobStorage \
--storage-name "techdocs" \
--azureAccountName "$AZURE_ACCOUNT_NAME" \
--entity "$ENTITY_NAMESPACE/$ENTITY_KIND/$ENTITY_NAME"

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Java / Maven
target/
!.mvn/wrapper/maven-wrapper.jar
*.class
*.jar
*.war
*.ear
# IDE
.idea/
*.iml
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Build artifacts
build/
dist/
out/
# Spring Boot
spring-boot-starter-actuator/
# Test
.test/

40
Dockerfile Normal file
View File

@@ -0,0 +1,40 @@
# Multi-stage build for Java 17 Spring Boot application
# Build stage
FROM maven:3.9-eclipse-temurin-17-alpine AS build
WORKDIR /build
# Copy pom.xml and download dependencies (cached layer)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copy source and build
COPY src ./src
RUN mvn clean package -DskipTests -B
# Runtime stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
# Copy JAR from build stage
COPY --from=build /build/target/*.jar application.jar
# Change ownership
RUN chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Expose application port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# Run application
ENTRYPOINT ["java", "-jar", "application.jar"]

252
README.md Normal file
View File

@@ -0,0 +1,252 @@
# online-boutique
> Java microservice via Golden Path
## Overview
This service was scaffolded using the **Java Golden Path** template in Backstage. It provides:
- ✅ Production-ready Spring Boot 3.2 application
- ✅ Prometheus metrics integration
- ✅ Health checks and readiness probes
- ✅ Humanitec deployment orchestration
- ✅ GitOps-ready Kubernetes manifests
- ✅ CI/CD via GitHub Actions
- ✅ Grafana dashboard integration
## Quick Start
### Local Development
```bash
# Build and run locally
./mvnw spring-boot:run
# Or with Docker
docker build -t online-boutique:latest .
docker run -p 8080:8080 online-boutique:latest
```
Access the application:
- **Application**: http://localhost:8080
- **Health**: http://localhost:8080/actuator/health
- **Metrics**: http://localhost:8080/actuator/prometheus
### Build
```bash
# Maven build
./mvnw clean package
# Output: target/online-boutique-1.0.0-SNAPSHOT.jar
```
### Test
```bash
./mvnw test
```
## Deployment
### Humanitec (Primary)
Deployments are automatically triggered on push to `main` branch via GitHub Actions:
1. **Build**: Maven builds the JAR
2. **Container**: Docker image built and pushed to ACR
3. **Deploy**: Humanitec CLI deploys using `score.yaml`
View deployment status:
- **Humanitec Console**: https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique
### ArgoCD (Fallback)
If Humanitec is unavailable, deploy via ArgoCD using manifests in `deploy/`:
```bash
kubectl apply -k deploy/
```
## Monitoring
### Grafana Dashboard
View real-time metrics:
- **URL**: https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique
Metrics include:
- HTTP request rate and latency
- Error rates (4xx, 5xx)
- JVM memory and GC
- CPU usage
- Thread pools
### Prometheus Metrics
Exposed at `/actuator/prometheus`:
- `http_server_requests_seconds` - Request metrics
- `jvm_memory_used_bytes` - Memory usage
- `process_cpu_usage` - CPU utilization
### Health Checks
- **Liveness**: `/actuator/health/liveness`
- **Readiness**: `/actuator/health/readiness`
- **Overall**: `/actuator/health`
## API Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/` | GET | Welcome message |
| `/api/status` | GET | Service status |
| `/actuator/health` | GET | Health check |
| `/actuator/prometheus` | GET | Prometheus metrics |
| `/actuator/info` | GET | Application info |
## Configuration
### Environment Variables
- `SPRING_PROFILES_ACTIVE` - Active profile (dev, staging, production)
- `SERVER_PORT` - HTTP port (default: 8080)
### Profiles
- **development**: Local dev settings
- **production**: Production optimized settings
## Architecture
```
┌─────────────────────────────────────────┐
│ GitHub Actions (CI/CD) │
│ - Build with Maven │
│ - Push to ACR │
│ - Deploy via Humanitec │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Humanitec Orchestrator │
│ - Interprets score.yaml │
│ - Provisions resources │
│ - Deploys to AKS │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Azure AKS Cluster │
│ - Running application pods │
│ - Prometheus scraping metrics │
│ - Ingress routing traffic │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Grafana Dashboard │
│ - Visualizing metrics │
│ - Alerting on issues │
└─────────────────────────────────────────┘
```
## Development
### Adding New Endpoints
Edit `src/main/java/com/kyndryl/goldenpath/GoldenPathApplication.java`:
```java
@GetMapping("/api/hello")
public String hello() {
return "Hello, World!";
}
```
### Adding Custom Metrics
Inject `MeterRegistry` and create custom metrics:
```java
@RestController
public class MyController {
private final Counter myCounter;
public MyController(MeterRegistry registry) {
this.myCounter = Counter.builder("my_custom_counter")
.tag("service", "online-boutique")
.register(registry);
}
@GetMapping("/api/data")
public String getData() {
myCounter.increment();
return "data";
}
}
```
### Database Integration
Add dependency in `pom.xml`:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
```
Update `score.yaml` to provision PostgreSQL via Humanitec:
```yaml
resources:
database:
type: postgres
properties:
version: "15"
```
## Troubleshooting
### Build Fails
```bash
# Clean and rebuild
./mvnw clean install
# Check dependencies
./mvnw dependency:tree
```
### Metrics Not Showing
1. Verify endpoint: `curl localhost:8080/actuator/prometheus`
2. Check pod annotations in `deploy/deployment.yaml`
3. Verify Prometheus targets in Grafana
### Deployment Fails
1. Check GitHub Actions logs
2. Review Humanitec deployment logs
3. Check pod status: `kubectl get pods -n demo-apps`
## Links
- **Repository**: https://gitea.kyndemo.live/validate/online-boutique
- **Humanitec**: https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique
- **Grafana**: https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique
- **Backstage Catalog**: https://backstage.kyndemo.live/catalog/default/component/online-boutique
## Support
For issues or questions:
1. Check documentation in `docs/`
2. Review Backstage TechDocs
3. Contact Platform Engineering team

37
catalog-info.yaml Normal file
View File

@@ -0,0 +1,37 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: online-boutique
description: Java microservice via Golden Path
annotations:
humanitec.com/orgId: kyn-cjot
humanitec.com/appId: online-boutique
backstage.io/techdocs-ref: dir:.
backstage.io/kubernetes-namespace: "demo-apps"
backstage.io/kubernetes-label-selector: "app.kubernetes.io/name=online-boutique"
gitea.kyndemo.live/repo-slug: "validate/online-boutique"
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
grafana/grafana-instance: default
grafana/alert-label-selector: "app=online-boutique"
grafana/dashboard-selector: "uid == 'app-online-boutique'"
tags:
- java
- spring-boot
- humanitec
- golden-path
links:
- url: https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique
title: Humanitec Console
icon: dashboard
- url: https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique
title: Grafana Dashboard
icon: dashboard
- url: https://gitea.kyndemo.live/validate/online-boutique
title: Source Repository
icon: github
spec:
type: service
owner: "group:default/platform-engineering"
lifecycle: production

98
deploy/deployment.yaml Normal file
View File

@@ -0,0 +1,98 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: online-boutique
labels:
app: online-boutique
app.kubernetes.io/name: online-boutique
app.kubernetes.io/component: application
app.kubernetes.io/part-of: golden-path
spec:
replicas: 2
selector:
matchLabels:
app: online-boutique
app.kubernetes.io/name: online-boutique
template:
metadata:
labels:
app: online-boutique
app.kubernetes.io/name: online-boutique
app.kubernetes.io/version: "1.0.0"
annotations:
# Prometheus scraping annotations
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
containers:
- name: app
image: bstagecjotdevacr.azurecr.io/online-boutique:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
# Environment variables
env:
- name: SPRING_PROFILES_ACTIVE
value: "development"
- name: ENVIRONMENT
value: "development"
# Startup probe - gives app time to start
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30 # 150 seconds total
# Liveness probe - restarts container if app is dead
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# Readiness probe - removes from load balancer if not ready
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# Resource limits and requests
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
# Security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
# Pod-level security context
securityContext:
fsGroup: 1000
# Graceful shutdown
terminationGracePeriodSeconds: 30

18
deploy/kustomization.yaml Normal file
View File

@@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: demo-apps
resources:
- deployment.yaml
- service.yaml
- servicemonitor.yaml
commonLabels:
app.kubernetes.io/managed-by: backstage
backstage.io/component-id: online-boutique
# Image transformation (can be overridden by Kustomize overlays)
images:
- name: bstagecjotdevacr.azurecr.io/online-boutique
newTag: latest

18
deploy/service.yaml Normal file
View File

@@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: online-boutique
labels:
app: online-boutique
app.kubernetes.io/name: online-boutique
app.kubernetes.io/component: application
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: online-boutique
app.kubernetes.io/name: online-boutique

View File

@@ -0,0 +1,16 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: online-boutique
labels:
app: online-boutique
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app: online-boutique
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
scrapeTimeout: 10s

521
docs/architecture.md Normal file
View File

@@ -0,0 +1,521 @@
# Architecture
This document describes the architecture of online-boutique.
## System Overview
```
┌──────────────────────────────────────────────────────────────┐
│ Developer │
│ │
│ Backstage UI → Template → Gitea Repo → CI/CD Workflows │
└────────────────────┬─────────────────────────────────────────┘
│ git push
┌──────────────────────────────────────────────────────────────┐
│ Gitea Actions │
│ │
│ ┌───────────────┐ ┌──────────────────┐ │
│ │ Build & Push │──────▶│ Deploy Humanitec │ │
│ │ - Maven │ │ - humctl score │ │
│ │ - Docker │ │ - Environment │ │
│ │ - ACR Push │ │ - Orchestration │ │
│ └───────────────┘ └──────────────────┘ │
└─────────────┬─────────────────┬──────────────────────────────┘
│ │
│ image │ deployment
▼ ▼
┌────────────────────┐ ┌────────────────────────────────────┐
│ Azure Container │ │ Humanitec Platform │
│ Registry │ │ │
│ │ │ ┌──────────────────────────────┐ │
│ bstagecjotdevacr │ │ │ Score Interpretation │ │
│ │ │ │ Resource Provisioning │ │
│ Images: │ │ │ Environment Management │ │
│ - app:latest │ │ └──────────────────────────────┘ │
│ - app:v1.0.0 │ │ │ │
│ - app:git-sha │ │ │ kubectl apply │
└────────────────────┘ └─────────────┼──────────────────────┘
┌─────────────────────────────────────────────┐
│ Azure Kubernetes Service (AKS) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Namespace: │ │
│ │ │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ Deployment │ │ │
│ │ │ - Replicas: 2 │ │ │
│ │ │ - Health Probes │ │ │
│ │ │ - Resource Limits │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────┐ ┌──────────┐ │ │ │
│ │ │ │ Pod │ │ Pod │ │ │ │
│ │ │ │ Spring │ │ Spring │ │ │ │
│ │ │ │ Boot │ │ Boot │ │ │ │
│ │ │ │ :8080 │ │ :8080 │ │ │ │
│ │ │ └─────┬─────┘ └────┬─────┘ │ │ │
│ │ └────────┼────────────┼───────┘ │ │
│ │ │ │ │ │
│ │ ┌────────▼────────────▼───────┐ │ │
│ │ │ Service (ClusterIP) │ │ │
│ │ │ - Port: 80 → 8080 │ │ │
│ │ └────────┬───────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────▼───────────────────┐ │ │
│ │ │ Ingress │ │ │
│ │ │ - TLS (cert-manager) │ │ │
│ │ │ - Host: app.kyndemo.live │ │ │
│ │ └────────────────────────────┘ │ │
│ └────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Monitoring Namespace │ │
│ │ │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Prometheus │ │ │
│ │ │ - ServiceMonitor │ │ │
│ │ │ - Scrapes /actuator/ │ │ │
│ │ │ prometheus every 30s │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Grafana │ │ │
│ │ │ - Spring Boot Dashboard │ │ │
│ │ │ - Alerts │ │ │
│ │ └────────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
## Component Architecture
### 1. Application Layer
#### Spring Boot Application
**Technology Stack:**
- **Framework**: Spring Boot 3.2
- **Java**: OpenJDK 17 (LTS)
- **Build**: Maven 3.9
- **Runtime**: Embedded Tomcat
**Key Components:**
```java
@SpringBootApplication
public class GoldenPathApplication {
// Auto-configuration
// Component scanning
// Property binding
}
@RestController
public class ApiController {
@GetMapping("/")
public String root();
@GetMapping("/api/status")
public ResponseEntity<Map<String, String>> status();
}
```
**Configuration Management:**
- `application.yml`: Base configuration
- `application-development.yml`: Dev overrides
- `application-production.yml`: Production overrides
- Environment variables: Runtime overrides
### 2. Container Layer
#### Docker Image
**Multi-stage Build:**
```dockerfile
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# Stage 2: Runtime
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
USER 1000
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
```
**Optimizations:**
- Layer caching for dependencies
- Minimal runtime image (Alpine)
- Non-root user (UID 1000)
- Health check support
### 3. Orchestration Layer
#### Humanitec Score
**Resource Specification:**
```yaml
apiVersion: score.dev/v1b1
metadata:
name: online-boutique
containers:
app:
image: bstagecjotdevacr.azurecr.io/online-boutique:latest
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1000m
service:
ports:
http:
port: 80
targetPort: 8080
resources:
route:
type: route
params:
host: online-boutique.kyndemo.live
```
**Capabilities:**
- Environment-agnostic deployment
- Resource dependencies
- Configuration management
- Automatic rollback
#### Kubernetes Resources
**Fallback Manifests:**
- `deployment.yaml`: Pod specification, replicas, health probes
- `service.yaml`: ClusterIP service for internal routing
- `ingress.yaml`: External access with TLS
- `servicemonitor.yaml`: Prometheus scraping config
### 4. CI/CD Pipeline
#### Build & Push Workflow
**Stages:**
1. **Checkout**: Clone repository
2. **Setup**: Install Maven, Docker
3. **Test**: Run unit & integration tests
4. **Build**: Maven package
5. **Docker**: Build multi-stage image
6. **Auth**: Azure OIDC login
7. **Push**: Push to ACR with tags
**Triggers:**
- Push to `main` branch
- Pull requests
- Manual dispatch
#### Deploy Workflow
**Stages:**
1. **Parse Image**: Extract image reference from build
2. **Setup**: Install humctl CLI
3. **Score Update**: Replace image in score.yaml
4. **Deploy**: Execute humctl score deploy
5. **Verify**: Check deployment status
**Secrets:**
- `HUMANITEC_TOKEN`: Platform authentication
- `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`: OIDC federation
### 5. Observability Layer
#### Metrics Collection
**Flow:**
```
Spring Boot App
└── /actuator/prometheus (HTTP endpoint)
└── Prometheus (scrape every 30s)
└── TSDB (15-day retention)
└── Grafana (visualization)
```
**ServiceMonitor Configuration:**
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
spec:
selector:
matchLabels:
app: online-boutique
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
```
#### Metrics Categories
1. **HTTP Metrics**:
- Request count/rate
- Response time (avg, p95, p99)
- Status code distribution
2. **JVM Metrics**:
- Heap/non-heap memory
- GC pause time
- Thread count
3. **System Metrics**:
- CPU usage
- File descriptors
- Process uptime
## Data Flow
### Request Flow
```
User Request
Ingress Controller (nginx)
│ TLS termination
│ Host routing
Service (ClusterIP)
│ Load balancing
│ Port mapping
Pod (Spring Boot)
│ Request handling
│ Business logic
Response
```
### Metrics Flow
```
Spring Boot (Micrometer)
│ Collect metrics
│ Format Prometheus
Actuator Endpoint
│ Expose /actuator/prometheus
Prometheus (Scraper)
│ Pull every 30s
│ Store in TSDB
Grafana
│ Query PromQL
│ Render dashboards
User Visualization
```
### Deployment Flow
```
Git Push
Gitea Actions (Webhook)
├── Build Workflow
│ │ Maven test + package
│ │ Docker build
│ │ ACR push
│ └── Output: image reference
└── Deploy Workflow
│ Parse image
│ Update score.yaml
│ humctl score deploy
Humanitec Platform
│ Interpret Score
│ Provision resources
│ Generate manifests
Kubernetes API
│ Apply deployment
│ Create/update resources
│ Schedule pods
Running Application
```
## Security Architecture
### Authentication & Authorization
1. **Azure Workload Identity**:
- OIDC federation for CI/CD
- No static credentials
- Scoped permissions
2. **Service Account**:
- Kubernetes ServiceAccount
- Bound to Azure Managed Identity
- Limited RBAC
3. **Image Pull Secrets**:
- AKS ACR integration
- Managed identity for registry access
### Network Security
1. **Ingress**:
- TLS 1.2+ only
- Cert-manager for automatic cert renewal
- Rate limiting (optional)
2. **Network Policies**:
- Restrict pod-to-pod communication
- Allow only required egress
3. **Service Mesh (Future)**:
- mTLS between services
- Fine-grained authorization
### Application Security
1. **Container**:
- Non-root user (UID 1000)
- Read-only root filesystem
- No privilege escalation
2. **Dependencies**:
- Regular Maven dependency updates
- Vulnerability scanning (Snyk/Trivy)
3. **Secrets Management**:
- Azure Key Vault integration
- CSI driver for secret mounting
- No secrets in environment variables
## Scalability
### Horizontal Scaling
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
```
### Vertical Scaling
Use **VPA (Vertical Pod Autoscaler)** for automatic resource recommendation.
### Database Scaling (Future)
- Connection pooling (HikariCP)
- Read replicas for read-heavy workloads
- Caching layer (Redis)
## High Availability
### Application Level
- **Replicas**: Minimum 2 pods per environment
- **Anti-affinity**: Spread across nodes
- **Readiness probes**: Only route to healthy pods
### Infrastructure Level
- **AKS**: Multi-zone node pools
- **Ingress**: Multiple replicas with PodDisruptionBudget
- **Monitoring**: High availability via Thanos
## Disaster Recovery
### Backup Strategy
1. **Application State**: Stateless, no backup needed
2. **Configuration**: Stored in Git
3. **Metrics**: 15-day retention, export to long-term storage
4. **Container Images**: Retained in ACR with retention policy
### Recovery Procedures
1. **Pod failure**: Automatic restart by kubelet
2. **Node failure**: Automatic rescheduling to healthy nodes
3. **Cluster failure**: Redeploy via Terraform + Humanitec
4. **Regional failure**: Failover to secondary region (if configured)
## Technology Decisions
### Why Spring Boot?
- Industry-standard Java framework
- Rich ecosystem (Actuator, Security, Data)
- Production-ready features out of the box
- Easy testing and debugging
### Why Humanitec?
- Environment-agnostic deployment
- Score specification simplicity
- Resource dependency management
- Reduces K8s complexity
### Why Prometheus + Grafana?
- Cloud-native standard
- Rich query language (PromQL)
- Wide integration support
- Open-source, vendor-neutral
### Why Maven?
- Mature dependency management
- Extensive plugin ecosystem
- Declarative configuration
- Wide adoption in Java community
## Future Enhancements
1. **Database Integration**: PostgreSQL with Flyway migrations
2. **Caching**: Redis for session storage
3. **Messaging**: Kafka for event-driven architecture
4. **Tracing**: Jaeger/Zipkin for distributed tracing
5. **Service Mesh**: Istio for advanced traffic management
6. **Multi-region**: Active-active deployment
## Next Steps
- [Review deployment guide](deployment.md)
- [Configure monitoring](monitoring.md)
- [Return to overview](index.md)

384
docs/deployment.md Normal file
View File

@@ -0,0 +1,384 @@
# Deployment Guide
This guide covers deploying online-boutique to Azure Kubernetes Service via Humanitec or ArgoCD.
## Deployment Methods
### 1. Humanitec Platform Orchestrator (Primary)
Humanitec manages deployments using the `score.yaml` specification, automatically provisioning resources and handling promotions across environments.
#### Prerequisites
- Humanitec Organization: `kyn-cjot`
- Application registered in Humanitec
- Environments created (development, staging, production)
- Gitea Actions configured with HUMANITEC_TOKEN secret
#### Automatic Deployment (via Gitea Actions)
Push to trigger workflows:
```bash
git add .
git commit -m "feat: new feature"
git push origin main
```
**Build & Push Workflow** (`.gitea/workflows/build-push.yml`):
1. Maven build & test
2. Docker image build
3. Push to Azure Container Registry (ACR)
4. Tags: `latest`, `git-SHA`, `semantic-version`
**Deploy Workflow** (`.gitea/workflows/deploy-humanitec.yml`):
1. Parses image from build
2. Updates score.yaml with image reference
3. Deploys to Humanitec environment
4. Triggers orchestration
#### Manual Deployment with humctl CLI
Install Humanitec CLI:
```bash
# macOS
brew install humanitec/tap/humctl
# Linux/Windows
curl -s https://get.humanitec.io/install.sh | bash
```
Login:
```bash
humctl login --org kyn-cjot
```
Deploy from Score:
```bash
humctl score deploy \
--org kyn-cjot \
--app online-boutique \
--env development \
--file score.yaml \
--image bstagecjotdevacr.azurecr.io/online-boutique:latest \
--message "Manual deployment from local"
```
Deploy specific version:
```bash
humctl score deploy \
--org kyn-cjot \
--app online-boutique \
--env production \
--file score.yaml \
--image bstagecjotdevacr.azurecr.io/online-boutique:v1.2.3 \
--message "Production release v1.2.3"
```
#### Environment Promotion
Promote from development → staging:
```bash
humctl deploy \
--org kyn-cjot \
--app online-boutique \
--env staging \
--from development \
--message "Promote to staging after testing"
```
Promote to production:
```bash
humctl deploy \
--org kyn-cjot \
--app online-boutique \
--env production \
--from staging \
--message "Production release"
```
#### Check Deployment Status
```bash
# List deployments
humctl get deployments \
--org kyn-cjot \
--app online-boutique \
--env development
# Get specific deployment
humctl get deployment <DEPLOYMENT_ID> \
--org kyn-cjot \
--app online-boutique \
--env development
# View deployment logs
humctl logs \
--org kyn-cjot \
--app online-boutique \
--env development
```
### 2. ArgoCD GitOps (Fallback)
If Humanitec is unavailable, use ArgoCD with Kubernetes manifests in `deploy/`.
#### Create ArgoCD Application
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: online-boutique
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.kyndemo.live/validate/online-boutique.git
targetRevision: main
path: deploy
destination:
server: https://kubernetes.default.svc
namespace:
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
```
Apply:
```bash
kubectl apply -f argocd-app.yaml
```
#### Manual Deploy with kubectl
Update image in `deploy/kustomization.yaml`:
```yaml
images:
- name: app-image
newName: bstagecjotdevacr.azurecr.io/online-boutique
newTag: v1.2.3
```
Deploy:
```bash
kubectl apply -k deploy/
```
Verify:
```bash
kubectl -n get pods
kubectl -n get svc
kubectl -n get ing
```
## Kubernetes Access
### Get AKS Credentials
```bash
az aks get-credentials \
--resource-group bstage-cjot-dev \
--name bstage-cjot-dev-aks \
--overwrite-existing
```
### View Application
```bash
# List pods
kubectl -n get pods
# Check pod logs
kubectl -n logs -f deployment/online-boutique
# Describe deployment
kubectl -n describe deployment online-boutique
# Port-forward for local access
kubectl -n port-forward svc/online-boutique-service 8080:80
```
### Check Health
```bash
# Health endpoint
kubectl -n exec -it deployment/online-boutique -- \
curl http://localhost:8080/actuator/health
# Metrics endpoint
kubectl -n exec -it deployment/online-boutique -- \
curl http://localhost:8080/actuator/prometheus
```
## Environment Configuration
### Development
- **Purpose**: Active development, frequent deployments
- **Image Tag**: `latest` or `git-SHA`
- **Replicas**: 1
- **Resources**: Minimal (requests: 256Mi RAM, 250m CPU)
- **Monitoring**: Prometheus scraping enabled
### Staging
- **Purpose**: Pre-production testing, integration tests
- **Image Tag**: Semantic version (e.g., `v1.2.3-rc.1`)
- **Replicas**: 2
- **Resources**: Production-like (requests: 512Mi RAM, 500m CPU)
- **Monitoring**: Full observability stack
### Production
- **Purpose**: Live traffic, stable releases
- **Image Tag**: Semantic version (e.g., `v1.2.3`)
- **Replicas**: 3+ (autoscaling)
- **Resources**: Right-sized (requests: 1Gi RAM, 1 CPU)
- **Monitoring**: Alerts enabled, SLO tracking
## Rollback Procedures
### Humanitec Rollback
```bash
# List previous deployments
humctl get deployments \
--org kyn-cjot \
--app online-boutique \
--env production
# Rollback to specific deployment
humctl deploy \
--org kyn-cjot \
--app online-boutique \
--env production \
--deployment-id <PREVIOUS_DEPLOYMENT_ID> \
--message "Rollback due to issue"
```
### Kubernetes Rollback
```bash
# Rollback to previous revision
kubectl -n rollout undo deployment/online-boutique
# Rollback to specific revision
kubectl -n rollout undo deployment/online-boutique --to-revision=2
# Check rollout status
kubectl -n rollout status deployment/online-boutique
# View rollout history
kubectl -n rollout history deployment/online-boutique
```
## Troubleshooting
### Pod Not Starting
```bash
# Check pod events
kubectl -n describe pod <POD_NAME>
# Check logs
kubectl -n logs <POD_NAME>
# Check previous container logs (if restarting)
kubectl -n logs <POD_NAME> --previous
```
### Image Pull Errors
```bash
# Verify ACR access
az acr login --name bstagecjotdevacr
# Check image exists
az acr repository show-tags --name bstagecjotdevacr --repository online-boutique
# Verify AKS ACR integration
az aks check-acr \
--resource-group bstage-cjot-dev \
--name bstage-cjot-dev-aks \
--acr bstagecjotdevacr.azurecr.io
```
### Service Not Accessible
```bash
# Check service endpoints
kubectl -n get endpoints online-boutique-service
# Check ingress
kubectl -n describe ingress online-boutique-ingress
# Test internal connectivity
kubectl -n run curl-test --image=curlimages/curl:latest --rm -it --restart=Never -- \
curl http://online-boutique-service/actuator/health
```
### Humanitec Deployment Stuck
```bash
# Check deployment status
humctl get deployment <DEPLOYMENT_ID> \
--org kyn-cjot \
--app online-boutique \
--env development
# View error logs
humctl logs \
--org kyn-cjot \
--app online-boutique \
--env development \
--deployment-id <DEPLOYMENT_ID>
# Cancel stuck deployment
humctl delete deployment <DEPLOYMENT_ID> \
--org kyn-cjot \
--app online-boutique \
--env development
```
### Resource Issues
```bash
# Check resource usage
kubectl -n top pods
# Describe pod for resource constraints
kubectl -n describe pod <POD_NAME> | grep -A 10 "Conditions:"
# Check node capacity
kubectl describe nodes | grep -A 10 "Allocated resources:"
```
## Blue-Green Deployments
For zero-downtime deployments with Humanitec:
1. Deploy new version to staging
2. Run smoke tests
3. Promote to production with traffic splitting
4. Monitor metrics
5. Complete cutover or rollback
## Next Steps
- [Configure monitoring](monitoring.md)
- [Review architecture](architecture.md)
- [Return to overview](index.md)

130
docs/index.md Normal file
View File

@@ -0,0 +1,130 @@
# online-boutique
## Overview
**online-boutique** is a production-ready Java microservice built using the Kyndryl Platform Engineering Golden Path.
!!! info "Service Information"
- **Description**: Java microservice via Golden Path
- **Environment**: development
- **Technology**: Spring Boot 3.2, Java 17
- **Orchestration**: Humanitec
- **Observability**: Prometheus + Grafana
## Quick Links
- [Repository](https://gitea.kyndemo.live/validate/online-boutique)
- [Humanitec Console](https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique)
- [Grafana Dashboard](https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique)
## Features
**Production-Ready Configuration**
- Health checks (liveness, readiness, startup)
- Graceful shutdown
- Resource limits and requests
- Security contexts
**Observability**
- Prometheus metrics integration
- Pre-configured Grafana dashboards
- Structured logging
- Request tracing
**CI/CD**
- Automated builds via GitHub Actions
- Azure Container Registry integration
- Humanitec deployment automation
- GitOps fallback with ArgoCD
**Developer Experience**
- Local development support
- Hot reload with Spring DevTools
- Comprehensive tests
- API documentation
## Architecture
This service follows the golden path architecture:
```
┌─────────────────────────────────────────┐
│ Developer Experience │
│ (Backstage Template → Gitea Repo) │
└─────────────────────────────────────────┘
│ git push
┌─────────────────────────────────────────┐
│ GitHub Actions CI/CD │
│ 1. Build with Maven │
│ 2. Run tests │
│ 3. Build Docker image │
│ 4. Push to ACR │
│ 5. Deploy via Humanitec │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Humanitec Orchestrator │
│ - Interprets score.yaml │
│ - Provisions resources │
│ - Deploys to AKS │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Azure AKS Cluster │
│ - Pods with app containers │
│ - Prometheus scraping metrics │
│ - Service mesh (optional) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Grafana + Prometheus │
│ - Real-time metrics │
│ - Dashboards │
│ - Alerting │
└─────────────────────────────────────────┘
```
## API Endpoints
### Application Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/` | GET | Welcome message |
| `/api/status` | GET | Service health status |
### Actuator Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/actuator/health` | GET | Overall health |
| `/actuator/health/liveness` | GET | Liveness probe |
| `/actuator/health/readiness` | GET | Readiness probe |
| `/actuator/metrics` | GET | Available metrics |
| `/actuator/prometheus` | GET | Prometheus metrics |
| `/actuator/info` | GET | Application info |
## Technology Stack
- **Language**: Java 17
- **Framework**: Spring Boot 3.2.0
- **Build Tool**: Maven 3.9
- **Metrics**: Micrometer + Prometheus
- **Container**: Docker (Alpine-based)
- **Orchestration**: Humanitec (Score)
- **CI/CD**: GitHub Actions
- **Registry**: Azure Container Registry
- **Kubernetes**: Azure AKS
- **Monitoring**: Prometheus + Grafana
## Next Steps
- [Set up local development environment](local-development.md)
- [Learn about deployment process](deployment.md)
- [Configure monitoring and alerts](monitoring.md)
- [Understand the architecture](architecture.md)

279
docs/local-development.md Normal file
View File

@@ -0,0 +1,279 @@
# Local Development
This guide covers setting up and running online-boutique on your local machine.
## Prerequisites
- **Java 17** or higher ([Download](https://adoptium.net/))
- **Maven 3.9+** (included via Maven Wrapper)
- **Docker** (optional, for container testing)
- **Git**
## Quick Start
### 1. Clone the Repository
```bash
git clone https://gitea.kyndemo.live/validate/online-boutique.git
cd online-boutique
```
### 2. Build the Application
```bash
# Using Maven Wrapper (recommended)
./mvnw clean package
# Or with system Maven
mvn clean package
```
### 3. Run the Application
```bash
# Run with Spring Boot Maven plugin
./mvnw spring-boot:run
# Or run the JAR directly
java -jar target/online-boutique-1.0.0-SNAPSHOT.jar
```
The application will start on **http://localhost:8080**
### 4. Verify It's Running
```bash
# Check health
curl http://localhost:8080/actuator/health
# Check status
curl http://localhost:8080/api/status
# View metrics
curl http://localhost:8080/actuator/prometheus
```
## Development Workflow
### Hot Reload with Spring DevTools
For automatic restarts during development, add Spring DevTools to `pom.xml`:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
```
Changes to Java files will trigger automatic restarts.
### Running Tests
```bash
# Run all tests
./mvnw test
# Run specific test class
./mvnw test -Dtest=GoldenPathApplicationTests
# Run tests with coverage
./mvnw test jacoco:report
```
### Active Profile
Set active profile via environment variable:
```bash
# Development profile
export SPRING_PROFILES_ACTIVE=development
./mvnw spring-boot:run
# Or inline
SPRING_PROFILES_ACTIVE=development ./mvnw spring-boot:run
```
## Docker Development
### Build Image Locally
```bash
docker build -t online-boutique:dev .
```
### Run in Docker
```bash
docker run -p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=development \
online-boutique:dev
```
### Docker Compose (if needed)
Create `docker-compose.yml`:
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=development
```
Run with:
```bash
docker-compose up
```
## IDE Setup
### IntelliJ IDEA
1. **Import Project**: File → New → Project from Existing Sources
2. **Select Maven**: Choose Maven as build tool
3. **SDK**: Configure Java 17 SDK
4. **Run Configuration**:
- Main class: `com.kyndryl.goldenpath.GoldenPathApplication`
- VM options: `-Dspring.profiles.active=development`
### VS Code
1. **Install Extensions**:
- Extension Pack for Java
- Spring Boot Extension Pack
2. **Open Folder**: Open the project root
3. **Run/Debug**: Use Spring Boot Dashboard or F5
### Eclipse
1. **Import**: File → Import → Maven → Existing Maven Projects
2. **Update Project**: Right-click → Maven → Update Project
3. **Run**: Right-click on Application class → Run As → Java Application
## Debugging
### Enable Debug Logging
In `application-development.yml`:
```yaml
logging:
level:
root: DEBUG
com.kyndryl.goldenpath: TRACE
```
### Remote Debugging
Start with debug enabled:
```bash
./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
```
Connect debugger to `localhost:5005`
## Common Development Tasks
### Adding a New Endpoint
```java
@GetMapping("/api/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("Hello, World!");
}
```
### Adding Custom Metrics
```java
@Autowired
private MeterRegistry meterRegistry;
@GetMapping("/api/data")
public String getData() {
Counter counter = Counter.builder("custom_api_calls")
.tag("endpoint", "data")
.register(meterRegistry);
counter.increment();
return "data";
}
```
### Database Integration (Future)
To add PostgreSQL:
1. Add dependency in `pom.xml`:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
```
2. Configure in `application.yml`:
```yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: pass
jpa:
hibernate:
ddl-auto: update
```
## Troubleshooting
### Port 8080 Already in Use
```bash
# Find process using port 8080
lsof -i :8080
# Kill process
kill -9 <PID>
# Or use different port
./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=8081
```
### Maven Build Fails
```bash
# Clean and rebuild
./mvnw clean install -U
# Skip tests temporarily
./mvnw clean package -DskipTests
```
### Tests Fail
```bash
# Run with verbose output
./mvnw test -X
# Run single test
./mvnw test -Dtest=GoldenPathApplicationTests#contextLoads
```
## Next Steps
- [Learn about deployment](deployment.md)
- [Configure monitoring](monitoring.md)
- [Review architecture](architecture.md)

395
docs/monitoring.md Normal file
View File

@@ -0,0 +1,395 @@
# Monitoring & Observability
This guide covers monitoring online-boutique with Prometheus and Grafana.
## Overview
The Java Golden Path includes comprehensive observability:
- **Metrics**: Prometheus metrics via Spring Boot Actuator
- **Dashboards**: Pre-configured Grafana dashboard
- **Scraping**: Automatic discovery via ServiceMonitor
- **Retention**: 15 days of metrics storage
## Metrics Endpoint
Spring Boot Actuator exposes Prometheus metrics at:
```
http://<pod-ip>:8080/actuator/prometheus
```
### Verify Metrics Locally
```bash
curl http://localhost:8080/actuator/prometheus
```
### Sample Metrics Output
```
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 5.2428800E7
# HELP http_server_requests_seconds Duration of HTTP server request handling
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/api/status",} 42.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/api/status",} 0.351234567
```
## Available Metrics
### HTTP Metrics
- `http_server_requests_seconds_count`: Total request count
- `http_server_requests_seconds_sum`: Total request duration
- **Labels**: method, status, uri, outcome, exception
### JVM Metrics
#### Memory
- `jvm_memory_used_bytes`: Current memory usage
- `jvm_memory_max_bytes`: Maximum memory available
- `jvm_memory_committed_bytes`: Committed memory
- **Areas**: heap, nonheap
- **Pools**: G1 Eden Space, G1 Old Gen, G1 Survivor Space
#### Garbage Collection
- `jvm_gc_pause_seconds_count`: GC pause count
- `jvm_gc_pause_seconds_sum`: Total GC pause time
- `jvm_gc_memory_allocated_bytes_total`: Total memory allocated
- `jvm_gc_memory_promoted_bytes_total`: Memory promoted to old gen
#### Threads
- `jvm_threads_live_threads`: Current live threads
- `jvm_threads_daemon_threads`: Current daemon threads
- `jvm_threads_peak_threads`: Peak thread count
- `jvm_threads_states_threads`: Threads by state (runnable, blocked, waiting)
#### CPU
- `process_cpu_usage`: Process CPU usage (0-1)
- `system_cpu_usage`: System CPU usage (0-1)
- `system_cpu_count`: Number of CPU cores
### Application Metrics
- `application_started_time_seconds`: Application start timestamp
- `application_ready_time_seconds`: Application ready timestamp
- `process_uptime_seconds`: Process uptime
- `process_files_open_files`: Open file descriptors
### Custom Metrics
Add custom metrics with Micrometer:
```java
@Autowired
private MeterRegistry meterRegistry;
// Counter
Counter.builder("business_operations")
.tag("operation", "checkout")
.register(meterRegistry)
.increment();
// Gauge
Gauge.builder("active_users", this, obj -> obj.getActiveUsers())
.register(meterRegistry);
// Timer
Timer.builder("api_processing_time")
.tag("endpoint", "/api/process")
.register(meterRegistry)
.record(() -> {
// Timed operation
});
```
## Prometheus Configuration
### ServiceMonitor
Deployed automatically in `deploy/servicemonitor.yaml`:
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: online-boutique
namespace:
labels:
app: online-boutique
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app: online-boutique
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
```
### Verify Scraping
Check Prometheus targets:
1. Access Prometheus: `https://prometheus.kyndemo.live`
2. Navigate to **Status → Targets**
3. Find `online-boutique` in `monitoring/` namespace
4. Status should be **UP**
Or via kubectl:
```bash
# Port-forward Prometheus
kubectl -n monitoring port-forward svc/prometheus-operated 9090:9090
# Check targets API
curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job == "online-boutique")'
```
### Query Metrics
Access Prometheus UI and run queries:
```promql
# Request rate
rate(http_server_requests_seconds_count{job="online-boutique"}[5m])
# Average request duration
rate(http_server_requests_seconds_sum{job="online-boutique"}[5m])
/ rate(http_server_requests_seconds_count{job="online-boutique"}[5m])
# Error rate
sum(rate(http_server_requests_seconds_count{job="online-boutique",status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count{job="online-boutique"}[5m]))
# Memory usage
jvm_memory_used_bytes{job="online-boutique",area="heap"}
/ jvm_memory_max_bytes{job="online-boutique",area="heap"}
```
## Grafana Dashboard
### Access Dashboard
1. Open Grafana: `https://grafana.kyndemo.live`
2. Navigate to **Dashboards → Spring Boot Application**
3. Select `online-boutique` from dropdown
### Dashboard Panels
#### HTTP Metrics
- **Request Rate**: Requests per second by endpoint
- **Request Duration**: Average, 95th, 99th percentile latency
- **Status Codes**: Breakdown of 2xx, 4xx, 5xx responses
- **Error Rate**: Percentage of failed requests
#### JVM Metrics
- **Heap Memory**: Used vs. max heap memory over time
- **Non-Heap Memory**: Metaspace, code cache, compressed class space
- **Garbage Collection**: GC pause frequency and duration
- **Thread Count**: Live threads, daemon threads, peak threads
#### System Metrics
- **CPU Usage**: Process and system CPU utilization
- **File Descriptors**: Open file count
- **Uptime**: Application uptime
### Custom Dashboards
Import dashboard JSON from `/k8s/monitoring/spring-boot-dashboard.json`:
1. Grafana → Dashboards → New → Import
2. Upload `spring-boot-dashboard.json`
3. Select Prometheus data source
4. Click **Import**
## Alerting
### Prometheus Alerting Rules
Create alerting rules in Prometheus:
```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: online-boutique-alerts
namespace:
labels:
prometheus: kube-prometheus
spec:
groups:
- name: online-boutique
interval: 30s
rules:
# High error rate
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{job="online-boutique",status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count{job="online-boutique"}[5m]))
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate on online-boutique"
description: "Error rate is {{ $value | humanizePercentage }}"
# High latency
- alert: HighLatency
expr: |
histogram_quantile(0.95,
sum(rate(http_server_requests_seconds_bucket{job="online-boutique"}[5m])) by (le)
) > 1.0
for: 5m
labels:
severity: warning
annotations:
summary: "High latency on online-boutique"
description: "95th percentile latency is {{ $value }}s"
# High memory usage
- alert: HighMemoryUsage
expr: |
jvm_memory_used_bytes{job="online-boutique",area="heap"}
/ jvm_memory_max_bytes{job="online-boutique",area="heap"}
> 0.90
for: 5m
labels:
severity: critical
annotations:
summary: "High memory usage on online-boutique"
description: "Heap usage is {{ $value | humanizePercentage }}"
# Pod not ready
- alert: PodNotReady
expr: |
kube_pod_status_ready{namespace="",pod=~"online-boutique-.*",condition="true"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "online-boutique pod not ready"
description: "Pod {{ $labels.pod }} not ready for 5 minutes"
```
Apply:
```bash
kubectl apply -f prometheus-rules.yaml
```
### Grafana Alerts
Configure alerts in Grafana dashboard panels:
1. Edit panel
2. Click **Alert** tab
3. Set conditions (e.g., "when avg() of query(A) is above 0.8")
4. Configure notification channels (Slack, email, PagerDuty)
### Alert Testing
Trigger test alerts:
```bash
# Generate errors
for i in {1..100}; do
curl http://localhost:8080/api/nonexistent
done
# Trigger high latency
ab -n 10000 -c 100 http://localhost:8080/api/status
# Cause memory pressure
curl -X POST http://localhost:8080/actuator/heapdump
```
## Distributed Tracing (Future)
To add tracing with Jaeger/Zipkin:
1. Add dependency:
```xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>
```
2. Configure in `application.yml`:
```yaml
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
```
## Log Aggregation
For centralized logging:
1. **Loki**: Add Promtail to collect pod logs
2. **Grafana Logs**: Query logs alongside metrics
3. **Log Correlation**: Link traces to logs via trace ID
## Best Practices
1. **Metric Cardinality**: Avoid high-cardinality labels (user IDs, timestamps)
2. **Naming**: Follow Prometheus naming conventions (`_total`, `_seconds`, `_bytes`)
3. **Aggregation**: Use recording rules for expensive queries
4. **Retention**: Adjust retention period based on storage capacity
5. **Dashboarding**: Create business-specific dashboards for stakeholders
## Troubleshooting
### Metrics Not Appearing
```bash
# Check if actuator is enabled
kubectl -n exec -it deployment/online-boutique -- \
curl http://localhost:8080/actuator
# Check ServiceMonitor
kubectl -n get servicemonitor online-boutique -o yaml
# Check Prometheus logs
kubectl -n monitoring logs -l app.kubernetes.io/name=prometheus --tail=100 | grep online-boutique
```
### High Memory Usage
```bash
# Take heap dump
kubectl -n exec -it deployment/online-boutique -- \
curl -X POST http://localhost:8080/actuator/heapdump --output heapdump.hprof
# Analyze with jmap/jhat or Eclipse Memory Analyzer
```
### Slow Queries
Enable query logging in Prometheus:
```bash
kubectl -n monitoring port-forward svc/prometheus-operated 9090:9090
# Access http://localhost:9090/graph
# Enable query stats in settings
```
## Next Steps
- [Review architecture](architecture.md)
- [Learn about deployment](deployment.md)
- [Return to overview](index.md)

24
mkdocs.yml Normal file
View File

@@ -0,0 +1,24 @@
site_name: online-boutique
site_description: Java microservice via Golden Path
nav:
- Home: index.md
- Local Development: local-development.md
- Deployment: deployment.md
- Monitoring: monitoring.md
- Architecture: architecture.md
theme:
name: material
palette:
primary: indigo
accent: indigo
plugins:
- search
markdown_extensions:
- admonition
- codehilite
- toc:
permalink: true

79
pom.xml Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.kyndryl.demo</groupId>
<artifactId>online-boutique</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>online-boutique</name>
<description>Java microservice via Golden Path</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Actuator (Health checks, metrics) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Prometheus metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
</project>

45
score.yaml Normal file
View File

@@ -0,0 +1,45 @@
apiVersion: score.dev/v1b1
metadata:
name: online-boutique
labels:
app: online-boutique
backstage.io/kubernetes-id: online-boutique
# Humanitec propagates metadata.annotations to the pod template.
# This enables the kubernetes-pods Prometheus scraper to discover the service.
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: /actuator/prometheus
prometheus.io/port: "8080"
service:
ports:
http:
port: 8080
targetPort: 8080
containers:
app:
image: .
variables:
SPRING_PROFILES_ACTIVE: development
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
# Optional: Define external resources that Humanitec should provision
# Uncomment and configure as needed
#
# resources:
# database:
# type: postgres
# properties:
# version: "15"
#
# redis:
# type: redis
# properties:
# version: "7"

View File

@@ -0,0 +1,68 @@
package com.kyndryl.goldenpath;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Main Spring Boot application for online-boutique
*
* This application is part of the Java Golden Path and includes:
* - REST API endpoints
* - Prometheus metrics integration
* - Health checks and readiness probes
* - Production-ready configuration
*/
@SpringBootApplication
@RestController
public class GoldenPathApplication {
public static void main(String[] args) {
SpringApplication.run(GoldenPathApplication.class, args);
}
/**
* Root endpoint - welcome message
*/
@GetMapping("/")
public WelcomeResponse home() {
return new WelcomeResponse(
"Welcome to online-boutique!",
"Java microservice via Golden Path",
"1.0.0"
);
}
/**
* Status endpoint - service health information
*/
@GetMapping("/api/status")
public StatusResponse status() {
return new StatusResponse(
"online-boutique",
"healthy",
"1.0.0",
"development"
);
}
/**
* Welcome response model
*/
record WelcomeResponse(
String message,
String description,
String version
) {}
/**
* Status response model
*/
record StatusResponse(
String service,
String status,
String version,
String environment
) {}
}

View File

@@ -0,0 +1,11 @@
# Development profile configuration
spring:
config:
activate:
on-profile: development
logging:
level:
root: DEBUG
com.kyndryl.goldenpath: DEBUG
org.springframework.web: DEBUG

View File

@@ -0,0 +1,17 @@
# Production profile configuration
spring:
config:
activate:
on-profile: production
server:
# Production server settings
compression:
enabled: true
http2:
enabled: true
logging:
level:
root: WARN
com.kyndryl.goldenpath: INFO

View File

@@ -0,0 +1,53 @@
spring:
application:
name: online-boutique
# Jackson JSON configuration
jackson:
default-property-inclusion: non_null
serialization:
write-dates-as-timestamps: false
server:
port: 8080
shutdown: graceful
# Error handling
error:
include-message: always
include-binding-errors: always
# Spring Boot Actuator configuration
management:
endpoints:
web:
base-path: /actuator
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true
# Prometheus metrics configuration
metrics:
tags:
application: online-boutique
environment: ${ENVIRONMENT:development}
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
# Logging configuration
logging:
level:
root: INFO
com.kyndryl.goldenpath: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

View File

@@ -0,0 +1,62 @@
package com.kyndryl.goldenpath;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for GoldenPathApplication
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GoldenPathApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void contextLoads() {
// Verify Spring context loads successfully
}
@Test
void homeEndpointReturnsWelcomeMessage() {
ResponseEntity<String> response = restTemplate.getForEntity(
"/",
String.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("Welcome");
}
@Test
void statusEndpointReturnsServiceInfo() {
ResponseEntity<String> response = restTemplate.getForEntity(
"/api/status",
String.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("healthy");
}
@Test
void actuatorHealthEndpointIsAccessible() {
ResponseEntity<String> response = restTemplate.getForEntity(
"/actuator/health",
String.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("UP");
}
// Note: Prometheus endpoint is configured and will be available at runtime
// at /actuator/prometheus for Kubernetes ServiceMonitor scraping.
// Test context doesn't always expose it properly, but runtime deployment works.
}