diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 045e9c1..38a6514 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -124,8 +124,8 @@ python_tests: - .pip-cache/ policy: pull-push before_script: - - pip install -r backend/requirements.txt - - pip install pytest pytest-asyncio pytest-cov httpx + - pip install --index-url "$PIP_INDEX_URL" -r backend/requirements.txt + - pip install --index-url "$PIP_INDEX_URL" pytest pytest-asyncio pytest-cov httpx script: - cd backend # Only run unit tests - integration tests require Docker Compose services @@ -175,7 +175,7 @@ frontend_tests: # Shared deploy configuration .deploy_template: &deploy_template stage: deploy - needs: [build_image, kics, hadolint, python_tests, frontend_tests, secrets] + needs: [build_image, test_image, kics, hadolint, python_tests, frontend_tests, secrets, app_deps_scan, cve_scan, cve_sbom_analysis, app_sbom_analysis] image: deps.global.bsf.tools/registry-1.docker.io/alpine/k8s:1.29.12 .helm_setup: &helm_setup @@ -246,8 +246,8 @@ deploy_stage: --set image.tag=git.linux-amd64-$CI_COMMIT_SHA \ --wait \ --atomic \ - --timeout 5m - - kubectl rollout status deployment/orchard-stage-server -n $NAMESPACE --timeout=5m + --timeout 10m + - kubectl rollout status deployment/orchard-stage-server -n $NAMESPACE --timeout=10m - *verify_deployment environment: name: stage @@ -256,7 +256,7 @@ deploy_stage: agent: esv/bsf/bsf-integration/orchard/orchard-mvp:orchard-stage rules: - if: '$CI_COMMIT_BRANCH == "main"' - when: always + when: on_success # Deploy feature branch to dev namespace deploy_feature: @@ -282,8 +282,8 @@ deploy_feature: --set minioIngress.tls.secretName=minio-$CI_COMMIT_REF_SLUG-tls \ --wait \ --atomic \ - --timeout 5m - - kubectl rollout status deployment/orchard-$CI_COMMIT_REF_SLUG-server -n $NAMESPACE --timeout=5m + --timeout 10m + - kubectl rollout status deployment/orchard-$CI_COMMIT_REF_SLUG-server -n $NAMESPACE --timeout=10m - export BASE_URL="https://orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools" - *verify_deployment environment: @@ -295,7 +295,7 @@ deploy_feature: agent: esv/bsf/bsf-integration/orchard/orchard-mvp:orchard rules: - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "main"' - when: always + when: on_success # Cleanup feature branch deployment cleanup_feature: @@ -318,3 +318,51 @@ cleanup_feature: - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "main"' when: manual allow_failure: true + +# Deploy to production (version tags only, manual approval required) +deploy_prod: + stage: deploy + # For tag pipelines, most jobs don't run (trusting main was tested) + # We only need build_image to have the image available + needs: [build_image] + image: deps.global.bsf.tools/registry-1.docker.io/alpine/k8s:1.29.12 + variables: + NAMESPACE: orch-prod-namespace + VALUES_FILE: helm/orchard/values-prod.yaml + BASE_URL: https://orchard.common.global.bsf.tools + before_script: + - kubectl config use-context esv/bsf/bsf-integration/orchard/orchard-mvp:orchard-prod + - *helm_setup + script: + - echo "Deploying to PRODUCTION - version $CI_COMMIT_TAG" + - cd $CI_PROJECT_DIR + - | + helm upgrade --install orchard-prod ./helm/orchard \ + --namespace $NAMESPACE \ + -f $VALUES_FILE \ + --set image.tag=git.linux-amd64-$CI_COMMIT_SHA \ + --wait \ + --atomic \ + --timeout 10m + - kubectl rollout status deployment/orchard-prod-server -n $NAMESPACE --timeout=10m + - *verify_deployment + environment: + name: production + url: https://orchard.common.global.bsf.tools + kubernetes: + agent: esv/bsf/bsf-integration/orchard/orchard-mvp:orchard-prod + rules: + # Only run on semantic version tags (v1.0.0, v1.2.3, etc.) + - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/' + when: manual # Require manual approval for prod + allow_failure: false + +# Integration tests for production deployment +integration_test_prod: + <<: *integration_test_template + needs: [deploy_prod] + variables: + BASE_URL: https://orchard.common.global.bsf.tools + rules: + - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/' + when: on_success diff --git a/CHANGELOG.md b/CHANGELOG.md index 17172b8..8aca96b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added production deployment job triggered by semantic version tags (v1.0.0) with manual approval gate (#63) +- Added production Helm values file with persistence enabled (20Gi PostgreSQL, 100Gi MinIO) (#63) +- Added integration tests for production deployment (#63) - Added GitLab CI pipeline for feature branch deployments to dev namespace (#51) - Added `deploy_feature` job with dynamic hostnames and unique release names (#51) - Added `cleanup_feature` job with `on_stop` for automatic cleanup on merge (#51) @@ -16,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added internal proxy configuration for npm, pip, helm, and apt (#51) ### Changed +- Deploy jobs now require all security scans to pass before deployment (added test_image, app_deps_scan, cve_scan, cve_sbom_analysis, app_sbom_analysis to dependencies) (#63) +- Increased deploy job timeout from 5m to 10m (#63) - Added `--atomic` flag to Helm deployments for automatic rollback on failure - Adjusted dark mode color palette to use lighter background tones for better readability and reduced eye strain (#52) - Replaced project card grid with sortable data table on Home page for better handling of large project lists @@ -26,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved pod naming: Orchard pods now named `orchard-{env}-server-*` for clarity (#51) ### Fixed +- Fixed deploy jobs running even when tests or security scans fail (changed rules from `when: always` to `when: on_success`) (#63) +- Fixed python_tests job not using internal PyPI proxy (#63) - Fixed `cleanup_feature` job failing when branch is deleted (`GIT_STRATEGY: none`) (#51) - Fixed gitleaks false positives with fingerprints for historical commits (#51) - Fixed integration tests running when deploy fails (`when: on_success`) (#51) diff --git a/helm/orchard/values-prod.yaml b/helm/orchard/values-prod.yaml new file mode 100644 index 0000000..6d8caf6 --- /dev/null +++ b/helm/orchard/values-prod.yaml @@ -0,0 +1,217 @@ +# Production values for orchard +# TODO: Replace subcharts with managed services (RDS, S3) when ready +replicaCount: 1 + +image: + repository: registry.global.bsf.tools/esv/bsf/bsf-integration/orchard/orchard-mvp + pullPolicy: IfNotPresent # Don't always pull in prod + tag: "latest" # Overridden by CI + +imagePullSecrets: + - name: orchard-pull-secret + +initContainer: + image: + repository: containers.global.bsf.tools/busybox + tag: "1.36" + pullPolicy: IfNotPresent + +serviceAccount: + create: true + automount: true + annotations: {} + name: "orchard" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: {} + +securityContext: + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 1000 + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt" + hosts: + - host: orchard.common.global.bsf.tools + paths: + - path: / + pathType: Prefix + tls: + - secretName: orchard-prod-tls + hosts: + - orchard.common.global.bsf.tools + +# Production resources - same as stage for MVP, increase as needed +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 500m + memory: 512Mi + +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} + +orchard: + server: + host: "0.0.0.0" + port: 8080 + + # Database configuration (used when postgresql.enabled is false) + # TODO: Configure for managed PostgreSQL when ready + database: + host: "" + port: 5432 + user: orchard + password: "" + dbname: orchard + sslmode: disable + existingSecret: "" + existingSecretPasswordKey: "password" + + # S3 configuration (used when minio.enabled is false) + # TODO: Configure for real S3 when ready + s3: + endpoint: "" + region: us-east-1 + bucket: orchard-artifacts + accessKeyId: "" + secretAccessKey: "" + usePathStyle: true + existingSecret: "" + existingSecretAccessKeyKey: "access-key-id" + existingSecretSecretKeyKey: "secret-access-key" + + download: + mode: "presigned" + presignedUrlExpiry: 3600 + +# PostgreSQL subchart - MVP uses subchart, switch to managed later +postgresql: + enabled: true + image: + registry: containers.global.bsf.tools + repository: bitnami/postgresql + tag: "15" + pullPolicy: IfNotPresent + auth: + username: orchard + password: orchard-prod-password # TODO: Use existingSecret + database: orchard + primary: + persistence: + enabled: true # Enable persistence for prod + size: 20Gi + resourcesPreset: "none" + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 512Mi + volumePermissions: + resourcesPreset: "none" + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 128Mi + +# MinIO subchart - MVP uses subchart, switch to real S3 later +minio: + enabled: true + image: + registry: containers.global.bsf.tools + repository: bitnami/minio + tag: "latest" + pullPolicy: IfNotPresent + auth: + rootUser: minioadmin + rootPassword: minioadmin-prod # TODO: Use existingSecret + defaultBuckets: "orchard-artifacts" + persistence: + enabled: true # Enable persistence for prod + size: 100Gi + resourcesPreset: "none" + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 512Mi + defaultInitContainers: + volumePermissions: + resourcesPreset: "none" + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 128Mi + provisioning: + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 100m + memory: 256Mi + +# MinIO external ingress for presigned URL access +minioIngress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + host: "minio-orchard.common.global.bsf.tools" + tls: + enabled: true + secretName: minio-prod-tls + +redis: + enabled: false + +waitForDatabase: true + +global: + security: + allowInsecureImages: true