From 43af4ffb8b59866846203121521f14ffd78f8021 Mon Sep 17 00:00:00 2001 From: iamboyoungkim Date: Fri, 10 Jul 2026 16:50:50 +0900 Subject: [PATCH] feat(devops-agent-operator): add sample-app CodeBuild demo - Sample app with stable/buggy versions for live demo - CodeBuild buildspec for ECR image build pipeline - demo-run.sh script (stable/buggy/status/cleanup) - Alternative deploy methods: local Docker, Kaniko, ConfigMap - README with setup guide and demo timing - All credentials replaced with placeholders --- .../sample-app/Dockerfile | 4 + .../sample-app/README.md | 141 ++++++++++++++ .../devops-agent-operator/sample-app/app.py | 37 ++++ .../sample-app/build-in-cluster.sh | 104 ++++++++++ .../sample-app/buildspec.yml | 25 +++ .../sample-app/demo-run.sh | 180 ++++++++++++++++++ .../sample-app/deploy-app.sh | 71 +++++++ .../sample-app/deploy.sh | 46 +++++ .../sample-app/k8s-deployment.yaml.tpl | 33 ++++ .../sample-app/versions/buggy.py | 53 ++++++ .../sample-app/versions/stable.py | 37 ++++ 11 files changed, 731 insertions(+) create mode 100644 containers/devops-agent-operator/sample-app/Dockerfile create mode 100644 containers/devops-agent-operator/sample-app/README.md create mode 100644 containers/devops-agent-operator/sample-app/app.py create mode 100755 containers/devops-agent-operator/sample-app/build-in-cluster.sh create mode 100644 containers/devops-agent-operator/sample-app/buildspec.yml create mode 100755 containers/devops-agent-operator/sample-app/demo-run.sh create mode 100755 containers/devops-agent-operator/sample-app/deploy-app.sh create mode 100755 containers/devops-agent-operator/sample-app/deploy.sh create mode 100644 containers/devops-agent-operator/sample-app/k8s-deployment.yaml.tpl create mode 100644 containers/devops-agent-operator/sample-app/versions/buggy.py create mode 100644 containers/devops-agent-operator/sample-app/versions/stable.py diff --git a/containers/devops-agent-operator/sample-app/Dockerfile b/containers/devops-agent-operator/sample-app/Dockerfile new file mode 100644 index 0000000..bc79dfa --- /dev/null +++ b/containers/devops-agent-operator/sample-app/Dockerfile @@ -0,0 +1,4 @@ +FROM public.ecr.aws/docker/library/python:3.12-alpine +WORKDIR /app +COPY app.py . +CMD ["python", "app.py"] diff --git a/containers/devops-agent-operator/sample-app/README.md b/containers/devops-agent-operator/sample-app/README.md new file mode 100644 index 0000000..28c773d --- /dev/null +++ b/containers/devops-agent-operator/sample-app/README.md @@ -0,0 +1,141 @@ +# Sample App — CodeBuild 기반 데모 + +DevOps Agent Operator의 장애 감지 → 자동 트러블슈팅 데모를 위한 샘플 애플리케이션입니다. + +CodeBuild로 이미지를 빌드하고 EKS에 배포하여, 버그 버전 배포 시 Operator가 자동으로 장애를 감지하고 데이터를 수집하는 전체 흐름을 시연합니다. + +## 구조 + +``` +sample-app/ +├── app.py # 애플리케이션 코드 (데모 시 stable/buggy로 교체) +├── versions/ +│ ├── stable.py # 정상 버전 (기본 config 사용) +│ └── buggy.py # 버그 버전 (APP_CONFIG 필수, 미설정 시 crash) +├── Dockerfile # 컨테이너 이미지 빌드 +├── buildspec.yml # CodeBuild 빌드 스펙 +├── demo-run.sh # 데모 실행 스크립트 (stable/buggy/status/cleanup) +├── deploy.sh # 로컬 Docker 빌드 & 배포 +├── deploy-app.sh # ConfigMap 기반 배포 (이미지 빌드 없이) +├── build-in-cluster.sh # Kaniko 기반 클러스터 내 빌드 +└── k8s-deployment.yaml.tpl # Kubernetes Deployment 템플릿 +``` + +## 사전 요구사항 + +- EKS 클러스터 (Operator가 배포된 상태) +- AWS CLI 설정 완료 +- CodeBuild 프로젝트 생성 (아래 설정 참조) +- ECR 리포지토리 생성 +- AWS DevOps Agent에서 GitHub repository 연동 완료 + - Agent가 장애 발생 시 관련 commit diff를 자동으로 추적하려면 소스 repository 연결이 필요합니다 + - 설정 방법: [Connecting to GitHub](https://docs.aws.amazon.com/devopsagent/latest/userguide/connecting-to-cicd-pipelines-connecting-github.html) + +## 환경 설정 + +`demo-run.sh` 상단의 변수를 자신의 환경에 맞게 수정합니다: + +```bash +AWS_ACCOUNT_ID="" # 예: 123456789012 +AWS_REGION="" # 예: ap-northeast-2 +ECR_REPO="sample-app" # ECR 리포지토리 이름 +S3_BUCKET="" # CodeBuild 소스용 S3 버킷 +CODEBUILD_PROJECT="" # CodeBuild 프로젝트 이름 +``` + +## CodeBuild 프로젝트 설정 + +### 1. S3 소스 버킷 생성 + +```bash +aws s3 mb s3:// --region +``` + +### 2. ECR 리포지토리 생성 + +```bash +aws ecr create-repository --repository-name sample-app --region +``` + +### 3. CodeBuild 프로젝트 생성 + +콘솔 또는 CLI로 다음 설정의 CodeBuild 프로젝트를 생성합니다: + +| 항목 | 값 | +|------|-----| +| 소스 | S3 (`s3:///source.zip`) | +| 환경 이미지 | `aws/codebuild/amazonlinux2-x86_64-standard:5.0` | +| 권한 모드 | Privileged (Docker 빌드용) | +| 빌드 스펙 | `sample-app/buildspec.yml` | +| 환경 변수 | `AWS_DEFAULT_REGION` = `` | + +CodeBuild IAM Role에 ECR push 권한과 S3 read 권한이 필요합니다. + +## 사용 방법 + +### 데모 스크립트 (CodeBuild 사용) + +```bash +cd sample-app/ + +# 정상 버전 빌드 & 배포 +./demo-run.sh stable + +# 버그 버전 배포 → CrashLoopBackOff 발생 → Operator 감지 +./demo-run.sh buggy + +# 상태 확인 +./demo-run.sh status + +# 정리 +./demo-run.sh cleanup +``` + +### 로컬 Docker 빌드 (CodeBuild 없이) + +```bash +# 환경 변수 설정 +export AWS_ACCOUNT_ID="" +export AWS_REGION="" + +./deploy.sh +``` + +### ConfigMap 기반 배포 (이미지 빌드 없이) + +```bash +./deploy-app.sh +``` + +## 데모 시나리오 + +### 정상 → 장애 발생 흐름 + +1. **stable 배포**: `app.py`는 기본 config(`{"app_name": "sample-app", "port": 8080}`)를 사용하므로 정상 동작 +2. **buggy 배포**: `app.py`가 `APP_CONFIG` 환경변수를 필수로 요구하도록 변경. Deployment에 해당 환경변수가 없으므로 즉시 crash +3. **Operator 감지**: CrashLoopBackOff 상태를 감지하고, pod manifest/logs/events/node logs를 수집 +4. **결과 확인**: S3에 인시던트 데이터가 저장되고, Webhook을 통해 DevOps Agent에 알림 전달 + +### 버그의 핵심 + +```python +# stable: 기본값 사용 +config_str = os.environ.get("APP_CONFIG", '{"app_name": "sample-app", "port": 8080}') + +# buggy: 환경변수 필수 → Deployment에 미설정 → crash +config_str = os.environ.get("APP_CONFIG") +if config_str is None: + sys.exit(1) # FATAL +``` + +실제 운영에서 발생할 수 있는 "리팩토링 시 기본값 제거" 패턴을 재현합니다. + + +## 트러블슈팅 + +| 증상 | 대응 | +|------|------| +| 빌드 실패 | `aws codebuild batch-get-builds --ids ` 로 에러 확인 | +| Pod가 계속 Running | stable 버전 배포됨. `./demo-run.sh buggy` 재실행 | +| Operator가 감지 안 함 | `kubectl rollout restart deploy/devops-agent-operator -n devops-agent-operator-system` | +| S3에 인시던트 없음 | Operator 환경변수에 `S3_BUCKET` 설정 확인 | diff --git a/containers/devops-agent-operator/sample-app/app.py b/containers/devops-agent-operator/sample-app/app.py new file mode 100644 index 0000000..111ba0d --- /dev/null +++ b/containers/devops-agent-operator/sample-app/app.py @@ -0,0 +1,37 @@ +"""Sample application - stable version""" +import http.server +import json +import os +import sys + + +def load_config(): + """Load configuration from environment variable.""" + config_str = os.environ.get("APP_CONFIG", '{"app_name": "sample-app", "port": 8080}') + config = json.loads(config_str) + return config + + +def main(): + config = load_config() + port = config["port"] + app_name = config["app_name"] + + class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/health": + self.send_response(200) + self.end_headers() + self.wfile.write(b"ok") + else: + self.send_response(200) + self.end_headers() + self.wfile.write(f"Hello from {app_name}!\n".encode()) + + print(f"Starting {app_name} on port {port}") + server = http.server.HTTPServer(("", port), Handler) + server.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/containers/devops-agent-operator/sample-app/build-in-cluster.sh b/containers/devops-agent-operator/sample-app/build-in-cluster.sh new file mode 100755 index 0000000..89cceef --- /dev/null +++ b/containers/devops-agent-operator/sample-app/build-in-cluster.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Configuration +AWS_ACCOUNT_ID="${AWS_ACCOUNT_ID:-}" +AWS_REGION="${AWS_REGION:-}" +ECR_REPO="sample-app" +COMMIT_SHA=$(git -C "$REPO_ROOT" rev-parse HEAD) +IMAGE_TAG=$(git -C "$REPO_ROOT" rev-parse --short HEAD) +FULL_IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:${IMAGE_TAG}" + +echo "=== Building sample-app in cluster (Kaniko) ===" +echo "Image: $FULL_IMAGE" +echo "Commit: $COMMIT_SHA" +echo "" + +# 1. Create ECR repo if not exists +echo "[1/4] Ensuring ECR repository..." +aws ecr describe-repositories --repository-names "$ECR_REPO" --region "$AWS_REGION" 2>/dev/null || \ + aws ecr create-repository --repository-name "$ECR_REPO" --region "$AWS_REGION" --output text >/dev/null + +# 2. Create ConfigMap with build context (Dockerfile + source) +echo "[2/4] Creating build context..." +kubectl delete configmap sample-app-build-context --ignore-not-found -n default >/dev/null 2>&1 +kubectl create configmap sample-app-build-context \ + --from-file=Dockerfile="$SCRIPT_DIR/Dockerfile" \ + --from-file=app.py="$SCRIPT_DIR/app.py" \ + -n default + +# 3. Run Kaniko build pod +echo "[3/4] Running Kaniko build..." +cat </dev/null || true +kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/kaniko-build --timeout=300s 2>/dev/null || { + echo " Build logs:" + kubectl logs kaniko-build -c kaniko 2>/dev/null | tail -10 + STATUS=$(kubectl get pod kaniko-build -o jsonpath='{.status.phase}') + if [ "$STATUS" != "Succeeded" ]; then + echo " ERROR: Build failed (status: $STATUS)" + kubectl delete pod kaniko-build --ignore-not-found >/dev/null 2>&1 + exit 1 + fi +} +echo " ✓ Image built: $FULL_IMAGE" +kubectl delete pod kaniko-build --ignore-not-found >/dev/null 2>&1 + +# 4. Deploy +echo "[4/4] Deploying..." +sed -e "s|__COMMIT_SHA__|${COMMIT_SHA}|g" \ + -e "s|__IMAGE__|${FULL_IMAGE}|g" \ + "$SCRIPT_DIR/k8s-deployment.yaml.tpl" > "$SCRIPT_DIR/k8s-deployment.yaml" +kubectl apply -f "$SCRIPT_DIR/k8s-deployment.yaml" +kubectl rollout status deployment/sample-app --timeout=120s + +echo "" +echo "=== Done ===" +echo "Commit: $COMMIT_SHA" +echo "Image: $FULL_IMAGE" +kubectl get pods -l app=sample-app diff --git a/containers/devops-agent-operator/sample-app/buildspec.yml b/containers/devops-agent-operator/sample-app/buildspec.yml new file mode 100644 index 0000000..53f6239 --- /dev/null +++ b/containers/devops-agent-operator/sample-app/buildspec.yml @@ -0,0 +1,25 @@ +version: 0.2 + +env: + variables: + ECR_REPO: sample-app + AWS_ACCOUNT_ID: "" + +phases: + pre_build: + commands: + - echo Logging in to ECR... + - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com + - IMAGE_TAG=$(date +%Y%m%d%H%M%S) + - FULL_IMAGE=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPO:$IMAGE_TAG + - echo "Building image $FULL_IMAGE" + - ls -la && ls -la sample-app/ || true + build: + commands: + - echo Building Docker image... + - cd sample-app && docker build -t $FULL_IMAGE . + post_build: + commands: + - echo Pushing to ECR... + - docker push $FULL_IMAGE + - echo "Image pushed successfully - $FULL_IMAGE" diff --git a/containers/devops-agent-operator/sample-app/demo-run.sh b/containers/devops-agent-operator/sample-app/demo-run.sh new file mode 100755 index 0000000..c086bec --- /dev/null +++ b/containers/devops-agent-operator/sample-app/demo-run.sh @@ -0,0 +1,180 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# ============================================================ +# Configuration — 아래 값을 자신의 환경에 맞게 수정하세요 +# ============================================================ +AWS_ACCOUNT_ID="${AWS_ACCOUNT_ID:-}" +AWS_REGION="${AWS_REGION:-}" +ECR_REPO="sample-app" +S3_BUCKET="${CODEBUILD_SOURCE_BUCKET:-}" +CODEBUILD_PROJECT="${CODEBUILD_PROJECT:-}" +S3_INCIDENTS_BUCKET="${S3_INCIDENTS_BUCKET:-}" +GITHUB_REPO="${GITHUB_REPO:-/}" +# ============================================================ + +usage() { + echo "Usage: ./demo-run.sh [stable|buggy|status|cleanup]" + echo "" + echo " stable - Deploy stable version (normal operation)" + echo " buggy - Introduce bug, build, and deploy (triggers incident)" + echo " status - Check current pod and operator status" + echo " cleanup - Remove sample-app deployment" + exit 1 +} + +wait_for_build() { + local build_id=$1 + echo " Waiting for build to complete..." + while true; do + STATUS=$(aws codebuild batch-get-builds --ids "$build_id" --region $AWS_REGION \ + --query "builds[0].buildStatus" --output text 2>/dev/null) + if [ "$STATUS" = "SUCCEEDED" ]; then + echo " ✓ Build succeeded" + return 0 + elif [ "$STATUS" = "FAILED" ] || [ "$STATUS" = "FAULT" ] || [ "$STATUS" = "STOPPED" ]; then + echo " ✗ Build failed: $STATUS" + return 1 + fi + sleep 10 + printf "." + done +} + +get_latest_image() { + aws ecr describe-images --repository-name $ECR_REPO --region $AWS_REGION \ + --query "sort_by(imageDetails, &imagePushedAt)[-1].imageTags[0]" --output text +} + +build_and_push() { + echo "[2/4] Uploading source to S3..." + rm -f /tmp/source.zip + cd "$REPO_ROOT" + zip -r /tmp/source.zip sample-app/ -x "sample-app/versions/*" -x "sample-app/DEMO.md" -x "sample-app/README.md" > /dev/null + aws s3 cp /tmp/source.zip "s3://$S3_BUCKET/source.zip" --region $AWS_REGION > /dev/null + + echo "[3/4] Starting CodeBuild..." + BUILD_ID=$(aws codebuild start-build --project-name $CODEBUILD_PROJECT --region $AWS_REGION \ + --query "build.id" --output text) + echo " Build ID: $BUILD_ID" + wait_for_build "$BUILD_ID" +} + +deploy() { + local image_tag=$1 + local commit_sha=$2 + local full_image="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:${image_tag}" + + echo "[4/4] Deploying image: $full_image" + cat < /dev/null 2>&1 || true + git push origin main > /dev/null 2>&1 || true + COMMIT_SHA=$(git rev-parse HEAD) + echo " Commit: ${COMMIT_SHA:0:7}" + + build_and_push + IMAGE_TAG=$(get_latest_image) + deploy "$IMAGE_TAG" "$COMMIT_SHA" + + echo "" + echo "=== Waiting for pod to be ready... ===" + sleep 10 + kubectl get pods -l app=sample-app + ;; + + buggy) + echo "=== Deploying BUGGY version ===" + echo "" + echo "[1/4] Introducing bug..." + cp "$SCRIPT_DIR/versions/buggy.py" "$SCRIPT_DIR/app.py" + cd "$REPO_ROOT" + git add sample-app/app.py + git commit -m "refactor: require explicit APP_CONFIG, add database_url validation + +- Remove default config fallback (strict mode) +- Add required field validation: app_name, port, database_url +- App now exits with error if config is missing or incomplete" > /dev/null 2>&1 + git push origin main > /dev/null 2>&1 + COMMIT_SHA=$(git rev-parse HEAD) + echo " Commit: ${COMMIT_SHA:0:7}" + echo " Change: removed default config, added database_url requirement" + + build_and_push + IMAGE_TAG=$(get_latest_image) + deploy "$IMAGE_TAG" "$COMMIT_SHA" + + echo "" + echo "=== Pod will crash shortly — watch operator logs: ===" + echo "kubectl logs -f deployment/devops-agent-operator -n devops-agent-operator-system" + ;; + + status) + echo "=== Current Status ===" + echo "" + echo "--- Pods ---" + kubectl get pods -l app=sample-app -n default + echo "" + echo "--- Operator (last 5 lines) ---" + kubectl logs deployment/devops-agent-operator -n devops-agent-operator-system --tail=5 2>/dev/null | grep -v "^$" + echo "" + echo "--- Latest S3 incidents ---" + aws s3 ls "s3://${S3_INCIDENTS_BUCKET}/incidents/" --region $AWS_REGION 2>/dev/null | tail -5 + ;; + + cleanup) + echo "=== Cleanup ===" + kubectl delete deployment sample-app -n default --ignore-not-found + echo "✓ Done" + ;; + + *) + usage + ;; +esac diff --git a/containers/devops-agent-operator/sample-app/deploy-app.sh b/containers/devops-agent-operator/sample-app/deploy-app.sh new file mode 100755 index 0000000..0985b30 --- /dev/null +++ b/containers/devops-agent-operator/sample-app/deploy-app.sh @@ -0,0 +1,71 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +COMMIT_SHA=$(git -C "$REPO_ROOT" rev-parse HEAD) + +# Configuration +GITHUB_REPO="${GITHUB_REPO:-/}" + +echo "=== Sample App Deploy ===" +echo "Commit: $COMMIT_SHA" +echo "" + +# 1. Create/update ConfigMap with app code +echo "[1/2] Updating app code ConfigMap..." +kubectl delete configmap sample-app-code -n default --ignore-not-found >/dev/null 2>&1 +kubectl create configmap sample-app-code \ + --from-file=app.py="$SCRIPT_DIR/app.py" \ + -n default + +# 2. Deploy +echo "[2/2] Deploying..." +cat </dev/null || \ + aws ecr create-repository --repository-name "$ECR_REPO" --region "$AWS_REGION" --output text + +# 2. Build and push +echo "[2/4] Building and pushing Docker image..." +aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" +docker build -t "$FULL_IMAGE" "$SCRIPT_DIR" +docker push "$FULL_IMAGE" + +# 3. Render k8s manifest +echo "[3/4] Rendering Kubernetes manifest..." +sed -e "s|__COMMIT_SHA__|${COMMIT_SHA}|g" \ + -e "s|__IMAGE__|${FULL_IMAGE}|g" \ + "$SCRIPT_DIR/k8s-deployment.yaml.tpl" > "$SCRIPT_DIR/k8s-deployment.yaml" + +# 4. Deploy +echo "[4/4] Deploying to cluster..." +kubectl apply -f "$SCRIPT_DIR/k8s-deployment.yaml" +kubectl rollout status deployment/sample-app --timeout=60s + +echo "" +echo "=== Deploy complete ===" +echo "Commit: $COMMIT_SHA" +echo "Image: $FULL_IMAGE" +kubectl get pods -l app=sample-app diff --git a/containers/devops-agent-operator/sample-app/k8s-deployment.yaml.tpl b/containers/devops-agent-operator/sample-app/k8s-deployment.yaml.tpl new file mode 100644 index 0000000..cf9147e --- /dev/null +++ b/containers/devops-agent-operator/sample-app/k8s-deployment.yaml.tpl @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sample-app + namespace: default + labels: + app: sample-app +spec: + replicas: 1 + selector: + matchLabels: + app: sample-app + template: + metadata: + labels: + app: sample-app + app.kubernetes.io/name: sample-app + annotations: + app.kubernetes.io/source-repository: "https://github.com//" + app.kubernetes.io/source-commit: "__COMMIT_SHA__" + github.com/repository: "/" + github.com/commit: "__COMMIT_SHA__" + spec: + containers: + - name: app + image: "__IMAGE__" + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi diff --git a/containers/devops-agent-operator/sample-app/versions/buggy.py b/containers/devops-agent-operator/sample-app/versions/buggy.py new file mode 100644 index 0000000..15e8d1a --- /dev/null +++ b/containers/devops-agent-operator/sample-app/versions/buggy.py @@ -0,0 +1,53 @@ +"""Sample application - refactored config loading""" +import http.server +import json +import os +import sys + + +def load_config(): + """Load configuration from environment variable. + + Refactored: now requires APP_CONFIG to be set explicitly. + Removed default fallback for stricter configuration management. + """ + config_str = os.environ.get("APP_CONFIG") + if config_str is None: + print("FATAL: APP_CONFIG environment variable is required", file=sys.stderr) + sys.exit(1) + + config = json.loads(config_str) + + # Validate required fields + required_fields = ["app_name", "port", "database_url"] + for field in required_fields: + if field not in config: + print(f"FATAL: missing required config field: {field}", file=sys.stderr) + sys.exit(1) + + return config + + +def main(): + config = load_config() + port = config["port"] + app_name = config["app_name"] + + class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/health": + self.send_response(200) + self.end_headers() + self.wfile.write(b"ok") + else: + self.send_response(200) + self.end_headers() + self.wfile.write(f"Hello from {app_name}!\n".encode()) + + print(f"Starting {app_name} on port {port}") + server = http.server.HTTPServer(("", port), Handler) + server.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/containers/devops-agent-operator/sample-app/versions/stable.py b/containers/devops-agent-operator/sample-app/versions/stable.py new file mode 100644 index 0000000..111ba0d --- /dev/null +++ b/containers/devops-agent-operator/sample-app/versions/stable.py @@ -0,0 +1,37 @@ +"""Sample application - stable version""" +import http.server +import json +import os +import sys + + +def load_config(): + """Load configuration from environment variable.""" + config_str = os.environ.get("APP_CONFIG", '{"app_name": "sample-app", "port": 8080}') + config = json.loads(config_str) + return config + + +def main(): + config = load_config() + port = config["port"] + app_name = config["app_name"] + + class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/health": + self.send_response(200) + self.end_headers() + self.wfile.write(b"ok") + else: + self.send_response(200) + self.end_headers() + self.wfile.write(f"Hello from {app_name}!\n".encode()) + + print(f"Starting {app_name} on port {port}") + server = http.server.HTTPServer(("", port), Handler) + server.serve_forever() + + +if __name__ == "__main__": + main()