pipeline {
  agent {
    kubernetes {
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: build
      image: mcr.microsoft.com/dotnet/sdk:8.0
      command: ["sleep", "999999"]
      resources: { limits: { memory: 2Gi } }
    - name: semgrep
      image: semgrep/semgrep:latest
      command: ["sleep", "999999"]
      resources: { limits: { memory: 1Gi } }
    - name: gitleaks
      image: ghcr.io/gitleaks/gitleaks:latest
      command: ["sleep", "999999"]
      resources: { limits: { memory: 256Mi } }
    - name: trivy
      image: ghcr.io/aquasecurity/trivy:latest
      command: ["sleep", "999999"]
      resources: { limits: { memory: 1Gi } }
    - name: sonar
      image: sonarsource/sonar-scanner-cli:latest
      command: ["sleep", "999999"]
      resources: { limits: { memory: 2Gi } }
      env:
        - name: SONAR_TOKEN
          valueFrom: { secretKeyRef: { name: sonar-token, key: SONAR_TOKEN } }
    - name: kaniko
      image: gcr.io/kaniko-project/executor:debug
      command: ["/busybox/sh", "-c", "sleep 999999"]
      resources: { limits: { memory: 2Gi } }
    - name: checkov
      image: bridgecrew/checkov:latest
      command: ["sleep", "999999"]
      resources: { limits: { memory: 512Mi } }
    - name: cosign
      image: public.ecr.aws/docker/library/alpine:3.20
      command: ["sleep", "999999"]
      resources: { limits: { memory: 256Mi } }
      env:
        - name: COSIGN_PASSWORD
          valueFrom: { secretKeyRef: { name: cosign-keys, key: password } }
      volumeMounts:
        - { name: cosign-keys, mountPath: /keys, readOnly: true }
    - name: zap
      image: ghcr.io/zaproxy/zaproxy:stable
      command: ["sleep", "999999"]
      resources: { limits: { memory: 1Gi } }
    - name: git
      image: alpine/git:latest
      command: ["sleep", "999999"]
      resources: { limits: { memory: 128Mi } }
    - name: uploader
      image: curlimages/curl:latest
      command: ["sleep", "999999"]
      securityContext: { runAsUser: 1000 }
      resources: { limits: { memory: 128Mi } }
      env:
        - name: DD_TOKEN
          valueFrom: { secretKeyRef: { name: defectdojo-token, key: DD_TOKEN } }
  volumes:
    - name: cosign-keys
      secret: { secretName: cosign-keys }
'''
    }
  }
  environment {
    REGISTRY   = "10.0.1.215:30500"
    IMAGE      = "inventory-api"
    SONAR_HOST = "http://sonarqube-sonarqube.platform.svc:9000"
    DD_URL     = "http://defectdojo-django.security.svc"
    APP_URL    = "http://inventory-api.demo-app.svc:8080"
  }
  stages {
    stage('Install & Test') {
      steps { container('build') { sh 'dotnet restore && dotnet build -c Release --no-restore' } }
    }
    stage('SAST — Semgrep audit') {
      steps {
        container('semgrep') {
          sh 'semgrep scan --config auto --json --output semgrep.json --metrics=off || true'
        }
      }
    }
    stage('SAST Gate') {
      steps {
        container('semgrep') {
          sh 'semgrep scan --config .semgrep-gate.yml --error --exclude=LegacyReports.cs --metrics=off .'
        }
      }
    }
    stage('Secrets — Gitleaks') {
      steps {
        container('gitleaks') {
          sh 'gitleaks detect --source . --no-git --report-format json --report-path gitleaks.json || true'
        }
      }
    }
    stage('SCA — Trivy deps') {
      steps {
        container('trivy') {
          sh 'trivy fs --scanners vuln --format json --output trivy-fs.json .'
          sh 'trivy fs --scanners vuln --severity HIGH,CRITICAL .'
        }
      }
    }
    stage('SonarQube') {
      steps {
        container('sonar') {
          sh 'sonar-scanner -Dsonar.host.url=$SONAR_HOST -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=false'
        }
      }
    }
    stage('Build — Kaniko') {
      steps {
        container('kaniko') {
          sh '''/kaniko/executor \
            --context "dir://$(pwd)" \
            --dockerfile Dockerfile \
            --destination "$REGISTRY/$IMAGE:$BUILD_NUMBER" \
            --tar-path image.tar \
            --insecure --insecure-pull'''
        }
      }
    }
    stage('Image Scan — Trivy') {
      steps {
        container('trivy') {
          sh 'trivy image --input image.tar --format json --output trivy-image.json'
        }
      }
    }
    stage('SBOM — CycloneDX') {
      steps {
        container('trivy') {
          sh 'trivy image --input image.tar --format cyclonedx --output sbom.cdx.json'
        }
      }
    }
    stage('IaC — Checkov') {
      steps {
        container('checkov') {
          sh 'checkov -d . --quiet -o json > checkov.json || true'
        }
      }
    }
    stage('Sign — Cosign') {
      steps {
        container('cosign') {
          sh '''
            wget -qO /tmp/cosign https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64
            chmod +x /tmp/cosign
            /tmp/cosign sign --key /keys/cosign.key --allow-insecure-registry --tlog-upload=false --yes "$REGISTRY/$IMAGE:$BUILD_NUMBER"
          '''
        }
      }
    }
    stage('Deploy — GitOps bump') {
      steps {
        container('git') {
          sh '''
            git config --global --add safe.directory "$(pwd)"
            git config user.name "Jenkins CI"
            git config user.email "jenkins@demo.local"
            sed -i "s/^  tag: .*/  tag: \\"$BUILD_NUMBER\\"/" helm/values.yaml
            if git diff --quiet helm/values.yaml; then
              echo "values.yaml already points at build $BUILD_NUMBER — nothing to push"
            else
              git add helm/values.yaml
              git commit -m "ci: deploy build $BUILD_NUMBER [ci skip]"
              git push http://demo:devsecops@gitea-http.platform.svc:3000/demo/$IMAGE.git HEAD:main
            fi
          '''
        }
      }
    }
    stage('DAST — OWASP ZAP') {
      steps {
        container('uploader') {
          sh '''
            echo "Waiting for deployment to become healthy..."
            for i in $(seq 1 60); do
              if curl -sf "$APP_URL/health" > /dev/null; then echo "App is up"; break; fi
              sleep 10
            done
          '''
        }
        container('zap') {
          sh '''
            rm -rf /zap/wrk && ln -s "$(pwd)" /zap/wrk
            zap-baseline.py -t "$APP_URL" -J zap.json -I || true
          '''
        }
      }
    }
    stage('Publish — DefectDojo') {
      steps {
        container('uploader') {
          sh '''
            upload() {
              [ -f "$2" ] || { echo "skip $1 ($2 missing)"; return 0; }
              curl -sf -X POST "$DD_URL/api/v2/import-scan/" \
                -H "Authorization: Token $DD_TOKEN" \
                -F scan_type="$1" \
                -F file=@"$2" \
                -F product_name=$IMAGE \
                -F engagement_name="CI Build $BUILD_NUMBER" \
                -F auto_create_context=true \
                -F active=true -F verified=true \
                > /dev/null && echo "uploaded: $1" || echo "FAILED: $1"
            }
            upload "Semgrep JSON Report" semgrep.json
            upload "Gitleaks Scan"       gitleaks.json
            upload "Trivy Scan"          trivy-fs.json
            upload "Trivy Scan"          trivy-image.json
            upload "Checkov Scan"        checkov.json
            upload "ZAP Scan"            zap.json
          '''
        }
      }
    }
  }
  post {
    always {
      archiveArtifacts artifacts: '*.json', allowEmptyArchive: true
    }
    success { echo 'Pipeline PASSED — findings aggregated in DefectDojo.' }
    failure { echo 'Pipeline FAILED — a security gate blocked the release.' }
  }
}
