From e9450a0552fce2b8cc845f0ce2e6a6c3dcdddba8 Mon Sep 17 00:00:00 2001 From: demo-bot Date: Fri, 13 Mar 2026 11:38:49 +0000 Subject: [PATCH] feat: add CI/CD workflow automation --- .gitea/workflows/build-push.yml | 79 ++++++++++++++++++ .gitea/workflows/deploy-humanitec.yml | 115 ++++++++++++++++++++++++++ .gitea/workflows/techdocs.yml | 51 ++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 .gitea/workflows/build-push.yml create mode 100644 .gitea/workflows/deploy-humanitec.yml create mode 100644 .gitea/workflows/techdocs.yml diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml new file mode 100644 index 0000000..0765e62 --- /dev/null +++ b/.gitea/workflows/build-push.yml @@ -0,0 +1,79 @@ +name: Build and Push to ACR + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + 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: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + # cache: 'npm' omitted — requires package-lock.json which may not exist yet + - name: Install dependencies + run: npm install + - name: Build project + run: npm run build || echo "No build script configured" + - name: Run tests + run: npm test || echo "No tests configured" + + - name: Security Scan - Trivy + continue-on-error: true + run: | + # Download release tarball directly — avoids install.sh which calls + # api.github.com/releases/tags/... and fails in network-restricted runners. + TRIVY_VERSION="0.57.1" + TRIVY_BIN="/tmp/trivy-bin/trivy" + if ! command -v trivy &>/dev/null; then + mkdir -p /tmp/trivy-bin + curl -sfLo /tmp/trivy.tar.gz "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" + tar -xzf /tmp/trivy.tar.gz -C /tmp/trivy-bin trivy + chmod +x "${TRIVY_BIN}" + else + TRIVY_BIN="$(command -v trivy)" + fi + # Filesystem scan — exit-code 0 so findings are reported but never block the build + "${TRIVY_BIN}" fs --severity HIGH,CRITICAL --exit-code 0 --format table --skip-db-update --offline-scan . || "${TRIVY_BIN}" fs --severity HIGH,CRITICAL --exit-code 0 --format table . + + - name: Install Azure CLI + run: | + if ! command -v az &>/dev/null; then + 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)" + - name: Build and push via ACR Tasks + run: | + SHORT_SHA=$(echo "${{ gitea.sha }}" | cut -c1-7) + az acr build \ + --registry bstagecjotdevacr \ + --image mohamed-node-mod-1:$SHORT_SHA \ + --image mohamed-node-mod-1:latest \ + --file Dockerfile \ + . + echo "✓ Pushed: bstagecjotdevacr.azurecr.io/mohamed-node-mod-1:$SHORT_SHA" diff --git a/.gitea/workflows/deploy-humanitec.yml b/.gitea/workflows/deploy-humanitec.yml new file mode 100644 index 0000000..896039c --- /dev/null +++ b/.gitea/workflows/deploy-humanitec.yml @@ -0,0 +1,115 @@ +name: Deploy to Humanitec v2 + +on: + workflow_run: + workflows: ["Build and Push to ACR"] + types: [completed] + branches: [ "main" ] + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + required: true + default: 'dev' + type: choice + options: [dev, staging, production] + +env: + HUMANITEC_ORG: skillful-wild-chicken-2617 + HUMANITEC_AUTH_TOKEN: ${{ secrets.HUMANITEC_TOKEN }} + PROJECT_ID: mohamed-node-mod-1 + DEFAULT_ENV_ID: dev + ACR_REGISTRY: bstagecjotdevacr.azurecr.io + +jobs: + deploy: + name: Deploy to Humanitec v2 + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: apt-get update -qq && apt-get install -y jq + - name: Install hctl CLI + run: | + HCTL_VERSION=$(curl -s https://api.github.com/repos/humanitec/hctl/releases/latest | jq -r '.tag_name') + mkdir -p /tmp/hctl-install + curl -sLo /tmp/hctl-install/hctl.tar.gz "https://github.com/humanitec/hctl/releases/download/${HCTL_VERSION}/hctl_${HCTL_VERSION#v}_linux_amd64.tar.gz" + tar -xzf /tmp/hctl-install/hctl.tar.gz -C /tmp/hctl-install + install -m 755 /tmp/hctl-install/hctl /usr/local/bin/hctl + - name: Ensure Humanitec project and environment exist + env: + HUMANITEC_AUTH_TOKEN: ${{ secrets.HUMANITEC_TOKEN }} + run: | + DISPATCH_ENV="${{ github.event.inputs.environment }}" + ENV_ID="${DISPATCH_ENV:-$DEFAULT_ENV_ID}" + # Create project if it doesn't exist (hctl exits 0 if already exists) + hctl create project "$PROJECT_ID" --set display_name="mohamed-node-mod-1" 2>&1 | grep -v "already exists" || true + # Create environment if it doesn't exist + hctl create environment "$PROJECT_ID" "$ENV_ID" --set env_type_id=development --set display_name="Development" 2>&1 | grep -v "already exists" || true + echo "✓ Project $PROJECT_ID / env $ENV_ID ready" + - name: Deploy with Score + env: + HUMANITEC_AUTH_TOKEN: ${{ secrets.HUMANITEC_TOKEN }} + run: | + DISPATCH_ENV="${{ github.event.inputs.environment }}" + ENV_ID="${DISPATCH_ENV:-$DEFAULT_ENV_ID}" + DEFAULT_IMAGE="$ACR_REGISTRY/mohamed-node-mod-1:latest" + # Pre-flight: wait for any deployment from a prior run to finish before calling hctl. + # hctl refuses to start a new deployment while one is still executing. + echo "Pre-flight: checking for in-progress deployments..." + MAX_PREFLIGHT=420 + PREFLIGHT_WAITED=0 + while [ $PREFLIGHT_WAITED -lt $MAX_PREFLIGHT ]; do + PREFLIGHT_STATUS=$(curl -sf -H "Authorization: Bearer $HUMANITEC_AUTH_TOKEN" "https://api.humanitec.dev/orgs/$HUMANITEC_ORG/last-deployments?env_id=$ENV_ID&project_id=$PROJECT_ID&state_change_only=true" | jq -r '.items[0].status // "none"' 2>/dev/null || echo "none") + if [ "$PREFLIGHT_STATUS" != "in progress" ] && [ "$PREFLIGHT_STATUS" != "pending" ] && [ "$PREFLIGHT_STATUS" != "executing" ]; then + echo "Pre-flight passed (status=$PREFLIGHT_STATUS). Proceeding." + break + fi + echo " Prior deployment still running ($PREFLIGHT_WAITED s elapsed, status=$PREFLIGHT_STATUS)..." + sleep 15 + PREFLIGHT_WAITED=$((PREFLIGHT_WAITED + 15)) + done + # First deploy — provisions all resources. On a brand-new Humanitec project the + # dns-k8s-ingress Terraform module runs before the K8s Service exists, so the + # ingress backend port falls back to 3000. A second deploy (below) corrects it + # once the Service is up, which is essential for Java/Python apps on port 8080. + HCTL_EXIT=0 + timeout 300 hctl score deploy "$PROJECT_ID" "$ENV_ID" score.yaml --no-prompt --default-image "$DEFAULT_IMAGE" || HCTL_EXIT=$? + if [ "$HCTL_EXIT" -eq 0 ]; then + echo "✓ First deployment complete for mohamed-node-mod-1 to $ENV_ID" + elif [ "$HCTL_EXIT" -eq 124 ]; then + echo "✓ First deployment submitted (polling timed out — waiting for K8s to settle)" + else + echo "✗ hctl failed with exit code $HCTL_EXIT" + exit $HCTL_EXIT + fi + # Poll Humanitec API until the first deployment is no longer in-progress before + # re-deploying. A flat sleep is unreliable — Terraform DNS modules can take 4-6 min. + echo "Waiting for first deployment to finish (polling Humanitec API)..." + MAX_WAIT=360 + WAITED=0 + while [ $WAITED -lt $MAX_WAIT ]; do + DEPLOY_STATUS=$(curl -sf -H "Authorization: Bearer $HUMANITEC_AUTH_TOKEN" "https://api.humanitec.dev/orgs/$HUMANITEC_ORG/last-deployments?env_id=$ENV_ID&project_id=$PROJECT_ID&state_change_only=true" | jq -r '.items[0].status // "unknown"' 2>/dev/null || echo "unknown") + if [ "$DEPLOY_STATUS" != "in progress" ] && [ "$DEPLOY_STATUS" != "pending" ] && [ "$DEPLOY_STATUS" != "executing" ]; then + echo "First deployment finished with status: $DEPLOY_STATUS" + break + fi + echo " Still running ($WAITED s elapsed, status=$DEPLOY_STATUS)..." + sleep 15 + WAITED=$((WAITED + 15)) + done + if [ $WAITED -ge $MAX_WAIT ]; then + echo "Warning: first deployment still running after $MAX_WAIT s — proceeding anyway" + fi + # Second deploy — dns module now reads the real K8s Service port, fixing the ingress + HCTL_EXIT2=0 + timeout 120 hctl score deploy "$PROJECT_ID" "$ENV_ID" score.yaml --no-prompt --default-image "$DEFAULT_IMAGE" || HCTL_EXIT2=$? + if [ "$HCTL_EXIT2" -eq 0 ]; then + echo "✓ Deployment finalised for mohamed-node-mod-1 to $ENV_ID" + elif [ "$HCTL_EXIT2" -eq 124 ]; then + echo "✓ Second deployment submitted for mohamed-node-mod-1 to $ENV_ID (polling timed out)" + else + echo "✗ Second hctl deploy failed with exit code $HCTL_EXIT2" + exit $HCTL_EXIT2 + fi diff --git a/.gitea/workflows/techdocs.yml b/.gitea/workflows/techdocs.yml new file mode 100644 index 0000000..6c8bfb1 --- /dev/null +++ b/.gitea/workflows/techdocs.yml @@ -0,0 +1,51 @@ +name: Build and Publish TechDocs + +on: + workflow_run: + workflows: ["Build and Push to ACR"] + branches: [main] + types: [completed] + workflow_dispatch: {} + +env: + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_ACCOUNT_NAME: "bstagecjotdevsttechdocs" + ENTITY_NAMESPACE: default + ENTITY_KIND: component + ENTITY_NAME: mohamed-node-mod-1 + +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: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install dependencies + run: | + apt-get update -qq && apt-get install -y python3-pip + pip3 install mkdocs-techdocs-core==1.* + npm install -g @techdocs/cli + - name: Build TechDocs site + run: techdocs-cli generate --source-dir . --output-dir ./site --no-docker --verbose + - name: Install Azure CLI + run: | + if ! command -v az &>/dev/null; then 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)" + - name: Publish TechDocs site + run: | + techdocs-cli publish \ + --publisher-type azureBlobStorage \ + --storage-name "techdocs" \ + --azureAccountName "$AZURE_ACCOUNT_NAME" \ + --entity "$ENTITY_NAMESPACE/$ENTITY_KIND/$ENTITY_NAME"