String GITHUB_REPOSITORY = 'security-scripts'
String ECR_ACCOUNT_ID = '086679231553'
List<String> AWS_REGIONS = ['us-east-1']
String SLACK_NOTIFICATIONS_CHANNEL = '#security-team'
String PROD_ACCOUNT_ID = '437795906767'
String PROD_DEPLOYMENT_ROLE = 'prod-jenkins-aws-pipeline-agent'

List<String> VULNERABILITIES_TO_IGNORE = [
    'CVE-2023-7104',  // SQLite3, installed: 3.40.1-2 no patch available
    'CVE-2025-22871', // go/stdlib, installed: 1.22.7, fixed: 1.24.2
    'CVE-2025-47907',
]

List<String> scriptsToEcr = ['github_public_repo_audit', 'github_vuln_scanner']
List<String> scriptsToDeployOnProd = ['github_public_repo_audit']

@groovy.transform.Field
List<String> scriptsToBuild = null

pipeline {
    agent none

    options {
        ansiColor('xterm')
        disableConcurrentBuilds()
        timestamps()
    }

    parameters {
        booleanParam(name: 'DEPLOY_TO_PROD', defaultValue: true, description: 'Whether or not to deploy to prod.')
        string(name: 'SCRIPT_NAME', defaultValue: '', description: 'Name of the script to build and deploy')
        string(name: 'SHARED_LIBRARIES_VERSION', defaultValue: 'master', description: 'The version of the Jenkins shared libraries to use. Can be a branch, tag, Git revision or PR ref (e.g. pull/PR_NUMBER/merge).')
    }

    triggers {
        issueCommentTrigger('.*retest this please.*')
    }
    
    stages {
        stage('Load Shared Libraries') {
            steps {
                library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
            }
        }

        stage('Compliance Checks') {
            steps {
                withModifiedScripts(checkout: true) { scriptName ->
                    echo "Running Compliance Checks for ${scriptName}"
                    dir("${scriptName}") {
                        complianceChecks()
                    }
                }
            }
        }

        stage('Validate Software Catalog Definition') {
            steps{
                withModifiedScripts(checkout: true) { scriptName ->
                    script {
                        if (scriptsToDeployOnProd.contains(scriptName)) {
                            datadogSoftwareCatalogValidate(servicePath: "${scriptName}")
                        } else {
                            echo "Skipping Software Catalog Validation for ${scriptName} as it is not required."
                        }
                    }
                }
            }
        }

        stage('Unit Tests and Style Checks') {
            steps {
                withModifiedScripts(checkout: true) { scriptName ->
                    script {
                        echo "Running Unit Tests and Style Checks for ${scriptName}"
                        withEcr {
                            dir("${scriptName}") {
                                try {
                                    sh "docker compose run --rm --build lint-and-test"
                                }
                                finally {
                                    sh "docker compose down -v"
                                }
                            }
                        }
                    }
                }
            }
        }

        stage('Static Application Security Tests') {
            steps {
                withModifiedScripts(checkout: true) { scriptName ->
                    echo "Running Static App Security Tests for ${scriptName}"
                    sastTests(v2:true, projectDir: "${scriptName}", ux: "markdown")
                }
            }
        }

        stage('Sonar Scan and Analysis') {
            agent any
            when {
                branch 'master'
            }
            steps {
                withModifiedScripts(checkout: true) { scriptName ->
                    echo "Running Sonar scan for ${GITHUB_REPOSITORY}-${scriptName.replaceAll('_', '-')}"
                    sonarScan project: "${GITHUB_REPOSITORY}-${scriptName.replaceAll('_', '-')}",
                        projectBaseDir: "${scriptName}",
                        language: 'py'
                }
            }
        }

        stage('Create a Release') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.GITHUB_COMMENT =~ 'build docker' }
                    expression { pullRequest.labels.contains('build docker') }
                }
            }
            steps {
                withModifiedScripts(checkout: true) { scriptName ->
                    script {
                        if (scriptsToEcr.contains(scriptName)) {
                            echo "Building for ${scriptName}"
                            dockerToEcr awsRegions: AWS_REGIONS,
                                ecrAccountId: ECR_ACCOUNT_ID,
                                imageName: "security-scripts/${scriptName.replaceAll('_', '-')}",
                                imageTag: env.GIT_COMMIT,
                                dockerBuildContext: "${scriptName}",
                                dockerBuildFile: "${scriptName}/Dockerfile",
                                dockerBuildTarget: 'deploy'
                        } else {
                            echo "Skipping Create a Release for ${scriptName} as it is not required."
                        }
                    }
                }
            }
        }

        stage('Scan Docker Images') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.GITHUB_COMMENT =~ 'build docker' }
                    expression { pullRequest.labels.contains('build docker') }
                }
            }
            steps {
                withModifiedScripts { scriptName ->
                    script {
                        if (scriptsToEcr.contains(scriptName)) {
                            echo "Scanning Docker Images for ${scriptName}"
                            dir("${scriptName}") {
                                dockerScan awsRegion: AWS_REGIONS[0],
                                    ecrAccountId: ECR_ACCOUNT_ID,
                                    imageName: "security-scripts/${scriptName.replaceAll('_', '-')}",
                                    imageTag: env.GIT_COMMIT,
                                    vulnerabilitiesToIgnore: VULNERABILITIES_TO_IGNORE,
                                    slackNotificationChannel: (env.BRANCH_NAME == 'master' ? SLACK_NOTIFICATIONS_CHANNEL : null)
                            }
                        } else {
                            echo "Skipping Scan Docker Images for ${scriptName} as it is not required."
                        }
                    }
                }
            }
        }

        stage('Deploy to PROD') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_TO_PROD }
                }
            }
            steps {
                withModifiedScripts(checkout: true) { scriptName ->
                    script {
                        if (scriptsToDeployOnProd.contains(scriptName)) {
                            echo "Deploying to PROD for ${scriptName}"
                            dir("${scriptName}") {
                                lambdaDeploy environment: 'prod',
                                    awsRegions: AWS_REGIONS,
                                    imageTag: env.GIT_COMMIT,
                                    imageName: "security-scripts/${scriptName.replaceAll('_', '-')}",
                                    functionName: "prod-lambda-security-scripts-${scriptName.replaceAll('_', '-')}",
                                    ecrRegistryAccountId: ECR_ACCOUNT_ID,
                                    awsDeploymentTargetAccountId: PROD_ACCOUNT_ID,
                                    awsDeploymentRoleName: PROD_DEPLOYMENT_ROLE
                            }
                            datadogSoftwareCatalogPublish(servicePath: "${scriptName}")
                        } else {
                            echo "Skipping Deploy to PROD for ${scriptName} as it is not required."
                        }
                    }
                    
                }
            }
        }
    }

    post {
        regression {
            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFICATIONS_CHANNEL
                }
            }
        }
        fixed {
            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFICATIONS_CHANNEL
                }
            }
        }
    }
}

def withModifiedScripts(Map args = [:], Closure steps) {
    parallel(getscriptsToBuild().collectEntries { scriptName ->
        return [
            (scriptName): {
                node {
                    if (args.checkout?.toBoolean()) {
                        cleanWs()
                        def scmVars = checkout scm
                        // Set SCM vars as environment variables to replicate default checkout functionality
                        scmVars.each { k, v ->
                            env."${k}" = v
                        }
                    }
                    steps(scriptName)
                }
            }
        ]
    })
}

def getscriptsToBuild() {
    if (this.@scriptsToBuild != null) {
        return this.@scriptsToBuild
    }

    def scriptName = params.SCRIPT_NAME ?: null

    if (scriptName) {
        this.@scriptsToBuild = [scriptName]
    } else {
        node {
            def scmVars = checkout scm
            def scriptDirectories = findFiles(glob: '**/Dockerfile')
            this.@scriptsToBuild = getModifiedFunctions branchName: scmVars.BRANCH_NAME,
                previousSuccessfulCommit: scmVars.GIT_PREVIOUS_SUCCESSFUL_COMMIT,
                lambdaDirectories: scriptDirectories
        }
    }

    if (this.@scriptsToBuild) {
        currentBuild.description = this.@scriptsToBuild.join('<br>')
    }

    return this.@scriptsToBuild
}
