name: Java - Main Pipeline
on: workflow_call: inputs: # Runner configuration runner: description: 'Runner type' required: false type: string default: 'ubuntu-latest'
# Java configuration java_version: description: 'Java version' required: false type: string default: '21' java_distribution: description: 'Java distribution' required: false type: string default: 'temurin' build_tool: description: 'Build tool: gradle or maven' required: false type: string default: 'gradle'
# Pipeline steps control run_commit_lint: description: 'Run commit message validation' required: false type: boolean default: false run_trufflehog: description: 'Run TruffleHog secret scanning' required: false type: boolean default: false trufflehog_only_verified: description: 'Only report verified secrets' required: false type: boolean default: true trufflehog_fail_on_findings: description: 'Fail the workflow if secrets are found' required: false type: boolean default: true run_dependency_review: description: 'Run dependency review (PR only)' required: false type: boolean default: false dependency_review_severity: description: 'Minimum severity to fail: low, moderate, high, critical' required: false type: string default: 'high' run_build: description: 'Run build job' required: false type: boolean default: true run_test: description: 'Run test job' required: false type: boolean default: false run_coverage: description: 'Run JaCoCo coverage report' required: false type: boolean default: false run_code_analysis: description: 'Run code analysis' required: false type: boolean default: false code_analysis_tool: description: 'Code analysis tool: sonar or qodana' required: false type: string default: 'sonar' skip_quality_gate: description: 'Skip Quality Gate check' required: false type: boolean default: false run_owasp: description: 'Run OWASP Dependency Check' required: false type: boolean default: false owasp_fail_on_cvss: description: 'CVSS score threshold to fail (7 = HIGH+CRITICAL, 9 = CRITICAL only)' required: false type: number default: 7 run_architecture: description: 'Run architecture validation (ArchUnit + diagrams)' required: false type: boolean default: false
# Artifact options run_artifact: description: 'Build and push Docker image' required: false type: boolean default: false artifact_registry: description: 'Artifact registry: ecr, github, packages' required: false type: string default: 'ecr' publish_task: description: 'Gradle publish task for packages registry' required: false type: string default: 'publish'
# Test options coverage_instruction_threshold: description: 'Minimum instruction coverage percentage (0-100). Requires jacocoLogCodeCoverage task.' required: false type: number default: 0 coverage_branch_threshold: description: 'Minimum branch coverage percentage (0-100). Requires jacocoLogCodeCoverage task.' required: false type: number default: 0 coverage_line_threshold: description: 'Minimum line coverage percentage (0-100). Set to 0 to disable.' required: false type: number default: 0
# Artifact & Deploy options image_tag: description: 'Docker image tag (empty = commit SHA)' required: false type: string default: '' docker_platform: description: 'Docker platform (linux/amd64, linux/arm64)' required: false type: string default: 'linux/arm64'
# Deploy options run_deploy: description: 'Deploy after artifact build' required: false type: boolean default: false deploy_target: description: 'Deploy target: ec2, ec2-vpn, or eks' required: false type: string default: 'ec2' environment: description: 'GitHub environment (develop, prod)' required: false type: string default: 'develop' memory_limit: description: 'Container memory limit (e.g., 512m/512Mi)' required: false type: string default: '800m' memory_reservation: description: 'Container memory reservation (e.g., 256m/256Mi)' required: false type: string default: '256m' spring_profiles: description: 'Additional Spring profiles (e.g., awsparamstore)' required: false type: string default: '' extra_volumes: description: 'Additional docker-compose volume mounts (one per line, including the leading "- "). Example: "- ./certs:/etc/ssl/app:ro"' required: false type: string default: ''
# EKS-specific options eks_cluster_name: description: 'EKS cluster name (required when deploy_target: eks)' required: false type: string default: '' eks_namespace: description: 'Kubernetes namespace' required: false type: string default: 'default' eks_use_helm: description: 'Use Helm for EKS deployment' required: false type: boolean default: false eks_helm_chart_path: description: 'Path to Helm chart' required: false type: string default: './helm' eks_replicas: description: 'Number of EKS replicas' required: false type: number default: 1
# Issue tracking run_create_issue_on_failure: description: 'Create GitHub issue on pipeline failure' required: false type: boolean default: false issue_labels: description: 'Labels for the failure issue (comma-separated)' required: false type: string default: 'bug,pipeline-failure'
# Notifications (Slack Incoming Webhook) notify_on_failure: description: 'Send Slack notification on pipeline failure' required: false type: boolean default: false notify_failure_channel: description: 'Slack channel for failure notifications' required: false type: string default: 'pipeline-errors' notify_failure_mention: description: 'Slack mention on failure (e.g., @channel, @here)' required: false type: string default: '@channel' notify_on_deploy: description: 'Send Slack notification on deploy success' required: false type: boolean default: false notify_deploy_channel: description: 'Slack channel for deploy notifications' required: false type: string default: 'pipeline-deployments' notify_on_release: description: 'Send Slack notification when release PR is created' required: false type: boolean default: false notify_release_channel: description: 'Slack channel for release PR notifications' required: false type: string default: 'pr-review'
# Cleanup & Release run_cleanup: description: 'Delete merged branch after deploy' required: false type: boolean default: false run_release: description: 'Create release PR after deploy' required: false type: boolean default: false release_target_branch: description: 'Target branch for release PR' required: false type: string default: 'main' release_strict_flow: description: 'Enforce GitFlow on release PR (base must be develop, target must be main)' required: false type: boolean default: true run_tag: description: 'Create git tag and GitHub Release after deploy' required: false type: boolean default: false
outputs: image_tag: description: 'Docker image tag pushed' value: ${{ jobs.artifact-ecr.outputs.image_tag || jobs.artifact-github.outputs.image_tag }} image_uri: description: 'Full Docker image URI' value: ${{ jobs.artifact-ecr.outputs.image_uri || jobs.artifact-github.outputs.image_uri }} coverage: description: 'Code coverage percentage (line)' value: ${{ jobs.test.outputs.coverage_percentage }} coverage_instruction: description: 'Instruction coverage percentage' value: ${{ jobs.test.outputs.coverage_instruction }} coverage_branch: description: 'Branch coverage percentage' value: ${{ jobs.test.outputs.coverage_branch }} quality_gate: description: 'SonarQube Quality Gate status' value: ${{ jobs.test.outputs.quality_gate_status }} owasp_vulnerabilities: description: 'Number of OWASP vulnerabilities found' value: ${{ jobs.owasp.outputs.vulnerabilities }} deleted_branch: description: 'Name of deleted branch' value: ${{ jobs.cleanup.outputs.deleted_branch }} release_version: description: 'Release version created' value: ${{ jobs.release.outputs.version }} release_pr_url: description: 'Release PR URL' value: ${{ jobs.release.outputs.pr_url }} release_changelog: description: 'Release changelog' value: ${{ jobs.release.outputs.changelog }} arch_test_result: description: 'Architecture test result' value: ${{ jobs.architecture.outputs.arch_test_result }} diagrams_artifact: description: 'Architecture diagrams artifact name' value: ${{ jobs.architecture.outputs.diagrams_artifact }} package_version: description: 'Published package version' value: ${{ jobs.artifact-packages.outputs.version }} package_group: description: 'Published package group' value: ${{ jobs.artifact-packages.outputs.group }} package_artifact_name: description: 'Published package artifact name' value: ${{ jobs.artifact-packages.outputs.artifact_name }}
jobs: # ============================================ # COMMIT LINT (parallel, no deps) # ============================================ commit-lint: name: Commit Lint if: inputs.run_commit_lint uses: ./.github/workflows/shared-commit-lint.yml with: runner: ${{ inputs.runner }}
# ============================================ # SECURITY - TruffleHog (parallel, no deps) # ============================================ security: name: Security Scan if: inputs.run_trufflehog uses: ./.github/workflows/security-trufflehog.yml with: runner: ${{ inputs.runner }} only_verified: ${{ inputs.trufflehog_only_verified }} fail_on_findings: ${{ inputs.trufflehog_fail_on_findings }} secrets: inherit
# ============================================ # SECURITY - Dependency Review (parallel, no deps) # ============================================ dependency-review: name: Dependency Review # dependency-review-action only works on pull_request events; auto-skip on push. if: inputs.run_dependency_review && github.event_name == 'pull_request' uses: ./.github/workflows/security-dependency-review.yml with: runner: ${{ inputs.runner }} fail_on_severity: ${{ inputs.dependency_review_severity }} secrets: inherit
# ============================================ # BUILD # ============================================ build: name: Build if: inputs.run_build uses: ./.github/workflows/java-build.yml with: runner: ${{ inputs.runner }} java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} build_tool: ${{ inputs.build_tool }} secrets: inherit
# ============================================ # TEST (needs: build) # ============================================ test: name: Test if: inputs.run_test needs: build uses: ./.github/workflows/java-test.yml with: runner: ${{ inputs.runner }} java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} run_coverage: ${{ inputs.run_coverage }} coverage_instruction_threshold: ${{ inputs.coverage_instruction_threshold }} coverage_branch_threshold: ${{ inputs.coverage_branch_threshold }} coverage_line_threshold: ${{ inputs.coverage_line_threshold }} run_code_analysis: ${{ inputs.run_code_analysis }} code_analysis_tool: ${{ inputs.code_analysis_tool }} skip_quality_gate: ${{ inputs.skip_quality_gate }} run_owasp: ${{ inputs.run_owasp }} secrets: inherit
# ============================================ # QODANA (parallel with test, needs: build) # ============================================ qodana: name: Qodana if: inputs.run_code_analysis && inputs.code_analysis_tool == 'qodana' needs: build uses: ./.github/workflows/java-qodana.yml with: runner: ${{ inputs.runner }} secrets: inherit
# ============================================ # OWASP (parallel with test, needs: build) # ============================================ owasp: name: OWASP if: inputs.run_owasp needs: build uses: ./.github/workflows/java-owasp.yml with: runner: ${{ inputs.runner }} java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} owasp_fail_on_cvss: ${{ inputs.owasp_fail_on_cvss }} build_tool: ${{ inputs.build_tool }} secrets: inherit
# ============================================ # ARCHITECTURE (parallel with test, needs: build) # ============================================ architecture: name: Architecture if: inputs.run_architecture needs: build uses: ./.github/workflows/java-architecture.yml with: runner: ${{ inputs.runner }} java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} secrets: inherit
# ============================================ # ARTIFACT - ECR # ============================================ artifact-ecr: name: Build & Push ECR if: | inputs.run_artifact && inputs.artifact_registry == 'ecr' && always() && needs.build.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.qodana.result == 'success' || needs.qodana.result == 'skipped') && (needs.owasp.result == 'success' || needs.owasp.result == 'skipped') && (needs.architecture.result == 'success' || needs.architecture.result == 'skipped') needs: [build, test, qodana, owasp, architecture] uses: ./.github/workflows/shared-artifact-docker-ecr.yml with: runner: ${{ inputs.runner }} image_tag: ${{ inputs.image_tag }} docker_platform: ${{ inputs.docker_platform }} environment: ${{ inputs.environment }} build_from_source: true java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} build_tool: ${{ inputs.build_tool }} secrets: inherit
# ============================================ # ARTIFACT - GitHub Registry # ============================================ artifact-github: name: Build & Push GitHub Registry if: | inputs.run_artifact && inputs.artifact_registry == 'github' && always() && needs.build.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.architecture.result == 'success' || needs.architecture.result == 'skipped') needs: [build, test, architecture] uses: ./.github/workflows/java-artifact-docker-github.yml with: runner: ${{ inputs.runner }} java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} image_tag: ${{ inputs.image_tag }} docker_platform: ${{ inputs.docker_platform }} artifact_name: ${{ needs.test.outputs.artifact_name }} environment: ${{ inputs.environment }} secrets: inherit
# ============================================ # ARTIFACT - GitHub Packages # ============================================ artifact-packages: name: Publish to GitHub Packages if: | inputs.run_artifact && inputs.artifact_registry == 'packages' && always() && needs.build.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.architecture.result == 'success' || needs.architecture.result == 'skipped') needs: [build, test, architecture] uses: ./.github/workflows/java-artifact-dependency-github.yml with: runner: ${{ inputs.runner }} java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} publish_task: ${{ inputs.publish_task }} secrets: inherit
# ============================================ # DEPLOY - EC2 # ============================================ deploy-ec2: name: Deploy EC2 if: | inputs.run_deploy && inputs.deploy_target == 'ec2' && always() && (needs.artifact-ecr.result == 'success' || needs.artifact-github.result == 'success' || needs.artifact-packages.result == 'success') needs: [artifact-ecr, artifact-github, artifact-packages] uses: ./.github/workflows/shared-deploy-ec2.yml with: runner: ${{ inputs.runner }} image_tag: ${{ needs.artifact-ecr.outputs.image_tag || needs.artifact-github.outputs.image_tag }} docker_platform: ${{ inputs.docker_platform }} environment: ${{ inputs.environment }} memory_limit: ${{ inputs.memory_limit }} memory_reservation: ${{ inputs.memory_reservation }} container_env_vars: | SPRING_PROFILES_ACTIVE=${{ inputs.spring_profiles != '' && format('{0},{1}', inputs.spring_profiles, inputs.environment) || inputs.environment }} SERVER_CONTEXT_PATH=/${{ github.event.repository.name }} extra_volumes: ${{ inputs.extra_volumes }} secrets: inherit
# ============================================ # DEPLOY - EC2 via WireGuard VPN # ============================================ deploy-ec2-vpn: name: Deploy EC2 (VPN) if: | inputs.run_deploy && inputs.deploy_target == 'ec2-vpn' && always() && (needs.artifact-ecr.result == 'success' || needs.artifact-github.result == 'success' || needs.artifact-packages.result == 'success') needs: [artifact-ecr, artifact-github, artifact-packages] uses: ./.github/workflows/shared-deploy-ec2-vpn.yml with: runner: ${{ inputs.runner }} image_tag: ${{ needs.artifact-ecr.outputs.image_tag || needs.artifact-github.outputs.image_tag }} docker_platform: ${{ inputs.docker_platform }} environment: ${{ inputs.environment }} memory_limit: ${{ inputs.memory_limit }} memory_reservation: ${{ inputs.memory_reservation }} container_env_vars: | SPRING_PROFILES_ACTIVE=${{ inputs.spring_profiles != '' && format('{0},{1}', inputs.spring_profiles, inputs.environment) || inputs.environment }} SERVER_CONTEXT_PATH=/${{ github.event.repository.name }} extra_volumes: ${{ inputs.extra_volumes }} secrets: inherit
# ============================================ # DEPLOY - EKS # ============================================ deploy-eks: name: Deploy EKS if: | inputs.run_deploy && inputs.deploy_target == 'eks' && always() && (needs.artifact-ecr.result == 'success' || needs.artifact-github.result == 'success') needs: [artifact-ecr, artifact-github, artifact-packages] uses: ./.github/workflows/shared-deploy-eks.yml with: runner: ${{ inputs.runner }} image_tag: ${{ needs.artifact-ecr.outputs.image_tag || needs.artifact-github.outputs.image_tag }} environment: ${{ inputs.environment }} cluster_name: ${{ inputs.eks_cluster_name }} namespace: ${{ inputs.eks_namespace }} replicas: ${{ inputs.eks_replicas }} use_helm: ${{ inputs.eks_use_helm }} helm_chart_path: ${{ inputs.eks_helm_chart_path }} memory_limit: ${{ inputs.memory_limit }} memory_request: ${{ inputs.memory_reservation }} container_env_vars: | SPRING_PROFILES_ACTIVE=${{ inputs.spring_profiles != '' && format('{0},{1}', inputs.spring_profiles, inputs.environment) || inputs.environment }} SERVER_CONTEXT_PATH=/${{ github.event.repository.name }} secrets: inherit
# ============================================ # RELEASE - Create release PR # ============================================ release: name: Create Release if: | inputs.run_release && always() && (needs.deploy-ec2.result == 'success' || needs.deploy-ec2-vpn.result == 'success' || needs.deploy-eks.result == 'success' || (needs.deploy-ec2.result == 'skipped' && needs.deploy-ec2-vpn.result == 'skipped' && needs.deploy-eks.result == 'skipped' && (needs.artifact-ecr.result == 'success' || needs.artifact-github.result == 'success' || needs.artifact-packages.result == 'success' || (needs.artifact-ecr.result == 'skipped' && needs.artifact-github.result == 'skipped' && needs.artifact-packages.result == 'skipped' && needs.test.result == 'success')))) needs: [test, artifact-ecr, artifact-github, artifact-packages, deploy-ec2, deploy-ec2-vpn, deploy-eks] uses: ./.github/workflows/shared-release.yml with: base_branch: ${{ github.ref_name }} target_branch: ${{ inputs.release_target_branch }} strict_flow: ${{ inputs.release_strict_flow }} secrets: inherit
# ============================================ # CLEANUP - Delete merged branch (runs after release PR created) # ============================================ cleanup: name: Delete Branch if: | inputs.run_cleanup && always() && (needs.deploy-ec2.result == 'success' || needs.deploy-ec2-vpn.result == 'success' || needs.deploy-eks.result == 'success' || (needs.deploy-ec2.result == 'skipped' && needs.deploy-ec2-vpn.result == 'skipped' && needs.deploy-eks.result == 'skipped' && (needs.artifact-ecr.result == 'success' || needs.artifact-github.result == 'success' || needs.artifact-packages.result == 'success' || (needs.artifact-ecr.result == 'skipped' && needs.artifact-github.result == 'skipped' && needs.artifact-packages.result == 'skipped' && needs.test.result == 'success')))) && (needs.release.result == 'success' || needs.release.result == 'skipped') needs: [test, artifact-ecr, artifact-github, artifact-packages, deploy-ec2, deploy-ec2-vpn, deploy-eks, release] uses: ./.github/workflows/shared-delete-branch.yml secrets: inherit
# ============================================ # TAG - Create git tag and GitHub Release # ============================================ tag: name: Tag Release if: | inputs.run_tag && always() && (needs.deploy-ec2.result == 'success' || needs.deploy-ec2-vpn.result == 'success' || needs.deploy-eks.result == 'success' || (needs.deploy-ec2.result == 'skipped' && needs.deploy-ec2-vpn.result == 'skipped' && needs.deploy-eks.result == 'skipped' && (needs.artifact-ecr.result == 'success' || needs.artifact-github.result == 'success' || needs.artifact-packages.result == 'success' || (needs.test.result == 'success' && needs.artifact-ecr.result == 'skipped' && needs.artifact-github.result == 'skipped' && needs.artifact-packages.result == 'skipped')))) needs: [build, test, artifact-ecr, artifact-github, artifact-packages, deploy-ec2, deploy-ec2-vpn, deploy-eks, cleanup, release] uses: ./.github/workflows/shared-tag-release.yml secrets: inherit
# ============================================ # NOTIFY FAILURE - Slack notification on pipeline failure # ============================================ notify-failure: name: Notify Failure if: | inputs.notify_on_failure && always() && (needs.build.result == 'failure' || needs.test.result == 'failure' || needs.artifact-ecr.result == 'failure' || needs.artifact-github.result == 'failure' || needs.artifact-packages.result == 'failure' || needs.deploy-ec2.result == 'failure' || needs.deploy-ec2-vpn.result == 'failure' || needs.deploy-eks.result == 'failure') needs: [build, test, artifact-ecr, artifact-github, artifact-packages, deploy-ec2, deploy-ec2-vpn, deploy-eks, cleanup, release, tag] uses: ./.github/workflows/shared-slack-notify.yml with: channel: ${{ inputs.notify_failure_channel }} status: 'failure' mention_on_failure: ${{ inputs.notify_failure_mention }} environment: ${{ inputs.environment }} secrets: inherit
# ============================================ # NOTIFY DEPLOY - Slack notification on deploy success # ============================================ notify-deploy: name: Notify Deploy if: | inputs.notify_on_deploy && always() && (needs.deploy-ec2.result == 'success' || needs.deploy-ec2-vpn.result == 'success' || needs.deploy-eks.result == 'success') needs: [deploy-ec2, deploy-ec2-vpn, deploy-eks] uses: ./.github/workflows/shared-slack-notify.yml with: channel: ${{ inputs.notify_deploy_channel }} status: 'success' title: 'Deploy successful — ${{ github.repository }}' environment: ${{ inputs.environment }} secrets: inherit
# ============================================ # NOTIFY RELEASE - Slack notification when release PR created # ============================================ notify-release: name: Notify Release PR if: | inputs.notify_on_release && always() && needs.release.result == 'success' needs: [release] uses: ./.github/workflows/shared-slack-notify.yml with: channel: ${{ inputs.notify_release_channel }} status: 'info' title: 'Release PR created — ${{ github.repository }}' version: ${{ needs.release.outputs.version }} pr_url: ${{ needs.release.outputs.pr_url }} secrets: inherit
# ============================================ # CREATE ISSUE - On failure (runs last, always) # ============================================ create-issue: name: Create Issue if: | inputs.run_create_issue_on_failure && always() needs: [build, test, artifact-ecr, artifact-github, artifact-packages, deploy-ec2, deploy-ec2-vpn, deploy-eks, cleanup, release, tag] uses: ./.github/workflows/shared-create-issue-on-failure.yml with: status: ${{ (needs.build.result == 'failure' || needs.test.result == 'failure' || needs.artifact-ecr.result == 'failure' || needs.artifact-github.result == 'failure' || needs.artifact-packages.result == 'failure' || needs.deploy-ec2.result == 'failure' || needs.deploy-ec2-vpn.result == 'failure' || needs.deploy-eks.result == 'failure') && 'failure' || 'success' }} environment: ${{ inputs.environment }} version: ${{ needs.release.outputs.version }} changelog: ${{ needs.release.outputs.changelog }} labels: ${{ inputs.issue_labels }} Java (Spring Boot)· Reusable workflow ·on: workflow_call
Java Main Pipeline
Java - Main Pipeline
.github/workflows/java-main-pipeline.yml