# services/jenkins/configmap-jcasc.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: jenkins-jcasc
  namespace: jenkins
data:
  securityrealm.yaml: |
    jenkins:
      securityRealm:
        oic:
          clientId: "${OIDC_CLIENT_ID}"
          clientSecret: "${OIDC_CLIENT_SECRET}"
          serverConfiguration:
            wellKnown:
              wellKnownOpenIDConfigurationUrl: "${OIDC_ISSUER}/.well-known/openid-configuration"
              scopesOverride: "openid profile email"
          logoutFromOpenIdProvider: true
          postLogoutRedirectUrl: "https://ci.bstein.dev"
          sendScopesInTokenRequest: true
          rootURLFromRequest: false
          userNameField: "preferred_username"
          fullNameFieldName: "name"
          emailFieldName: "email"
          groupsFieldName: "groups"
  authorization.yaml: |
    jenkins:
      authorizationStrategy:
        loggedInUsersCanDoAnything:
          allowAnonymousRead: false
  creds.yaml: |
    credentials:
      system:
        domainCredentials:
        - credentials:
          - usernamePassword:
              scope: GLOBAL
              id: gitea-pat
              username: "${GITEA_PAT_USERNAME}"
              password: "${GITEA_PAT_TOKEN}"
              description: "Gitea PAT for pipelines"
          - usernamePassword:
              scope: GLOBAL
              id: harbor-robot
              username: "${HARBOR_ROBOT_USERNAME}"
              password: "${HARBOR_ROBOT_PASSWORD}"
              description: "Harbor robot for pipelines"
          - usernamePassword:
              scope: GLOBAL
              id: harbor-robot-streaming
              username: "${HARBOR_STREAMING_ROBOT_USERNAME}"
              password: "${HARBOR_STREAMING_ROBOT_PASSWORD}"
              description: "Harbor robot for streaming pushes"
          - string:
              scope: GLOBAL
              id: sonarqube-token
              secret: "${SONARQUBE_TOKEN}"
              description: "SonarQube token for quality-gate evidence collection"
  jobs.yaml: |
    jobs:
      - script: |
          pipelineJob('harbor-arm-build') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/harbor-arm-build.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
              }
            }
          }
          pipelineJob('bstein-dev-home') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/2 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/bstein-dev-home.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('ariadne') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/2 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/ariadne.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('metis') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/metis.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('ananke') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/ananke.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('lesavka') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/lesavka.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('arcanagon') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/arcanagon.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('pegasus') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/pegasus.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('atlasbot') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/atlasbot.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('soteria') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/soteria.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('data-prepper') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/titan-iac.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('services/logging/Jenkinsfile.data-prepper')
              }
            }
          }
          pipelineJob('titan-iac') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H/12 * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/titan-iac.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('typhon') {
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                  cron {
                    spec('H H * * *')
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/typhon.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('cassandra') {
            disabled(false)
            description('Staged Cassandra alpha image pipeline. Backend/frontend should build linux/amd64 and linux/arm64; sim-worker may begin amd64-only if Forge dependencies require it.')
            properties {
              pipelineTriggers {
                triggers {
                  scmTrigger {
                    scmpoll_spec('H/5 * * * *')
                    ignorePostCommitHooks(false)
                  }
                }
              }
            }
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/cassandra.git')
                      credentials('gitea-pat')
                    }
                    branches('*/main')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          multibranchPipelineJob('hermes-code-demo-branches') {
            description('Branch validation for the Hermes code-repair demo. Builds hermes-repair/* proposal branches so a pull request carries a green test gate before a human merges it.')
            branchSources {
              branchSource {
                source {
                  git {
                    id('hermes-code-demo-branches')
                    remote('https://scm.bstein.dev/bstein/hermes-code-demo.git')
                    credentialsId('gitea-pat')
                    traits {
                      gitBranchDiscovery()
                    }
                  }
                }
              }
            }
            factory {
              workflowBranchProjectFactory {
                scriptPath('Jenkinsfile')
              }
            }
            orphanedItemStrategy {
              discardOldItems {
                numToKeep(20)
              }
            }
            triggers {
              periodicFolderTrigger {
                interval('1m')
              }
            }
          }
          pipelineJob('hermes-code-demo') {
            disabled(false)
            description('Hermes code-repair demo: pytest gate over a small pricing helper. A seeded one-line defect fails the gate; Hermes proposes a minimal patch and Ariadne opens a pull request for human review.')
            definition {
              cpsScm {
                scm {
                  git {
                    remote {
                      url('https://scm.bstein.dev/bstein/hermes-code-demo.git')
                      credentials('gitea-pat')
                    }
                    branches('*/master')
                  }
                }
                scriptPath('Jenkinsfile')
              }
            }
          }
          pipelineJob('hermes-triage-demo') {
            disabled(false)
            description('Hermes automated-triage demo: runs a deterministic fixture check as a Kubernetes Job in the hermes-triage-demo namespace. Build with SEED_FAILURE=true to arm the demo failure; the automated repair loop rebuilds with SEED_FAILURE=false.')
            parameters {
              booleanParam('SEED_FAILURE', false, 'Write "unhealthy" into the demo fixture before the check (arms the demo failure).')
            }
            definition {
              cps {
                sandbox(true)
                script('''
                  podTemplate(
                    cloud: 'kubernetes',
                    yaml: """
                  apiVersion: v1
                  kind: Pod
                  spec:
                    serviceAccountName: jenkins
                    securityContext:
                      runAsUser: 1000
                      runAsGroup: 1000
                      fsGroup: 1000
                      fsGroupChangePolicy: "OnRootMismatch"
                    nodeSelector:
                      kubernetes.io/arch: arm64
                      node-role.kubernetes.io/worker: "true"
                    containers:
                      - name: kubectl
                        image: bitnami/kubectl@sha256:554ab88b1858e8424c55de37ad417b16f2a0e65d1607aa0f3fe3ce9b9f10b131
                        command: ["sleep"]
                        args: ["3600"]
                        resources:
                          requests:
                            cpu: 50m
                            memory: 64Mi
                          limits:
                            cpu: 250m
                            memory: 128Mi
                  """
                  ) {
                    node(POD_LABEL) {
                      container('kubectl') {
                        stage('Fixture check') {
                          def buildNum = '' + env.BUILD_NUMBER
                          def incidentId = 'hermes-triage-demo/' + buildNum
                          def jobName = 'hermes-demo-test-' + buildNum
                          sh 'kubectl -n hermes-triage-demo get configmap hermes-triage-demo-fixture || kubectl -n hermes-triage-demo create configmap hermes-triage-demo-fixture --from-literal=state=healthy'
                          if (params.SEED_FAILURE) {
                            writeFile file: 'seed.json', text: '{"data":{"state":"unhealthy"}}'
                            sh 'kubectl -n hermes-triage-demo patch configmap hermes-triage-demo-fixture --type merge --patch-file seed.json'
                            echo 'Demo failure armed: fixture state set to unhealthy'
                          }
                          def manifest = [
                            'apiVersion: batch/v1',
                            'kind: Job',
                            'metadata:',
                            '  name: ' + jobName,
                            '  namespace: hermes-triage-demo',
                            '  labels:',
                            '    app.kubernetes.io/part-of: hermes-triage-demo',
                            '    atlas.bstein.dev/role: demo-test-runner',
                            'spec:',
                            '  backoffLimit: 0',
                            '  ttlSecondsAfterFinished: 3600',
                            '  template:',
                            '    metadata:',
                            '      labels:',
                            '        app.kubernetes.io/part-of: hermes-triage-demo',
                            '        atlas.bstein.dev/role: demo-test-runner',
                            '    spec:',
                            '      restartPolicy: Never',
                            '      nodeSelector:',
                            '        node-role.kubernetes.io/worker: "true"',
                            '      containers:',
                            '        - name: test-runner',
                            '          image: busybox:1.37',
                            '          env:',
                            '            - name: INCIDENT_ID',
                            '              value: "' + incidentId + '"',
                            '          command:',
                            '            - sh',
                            '            - -c',
                            '            - |',
                            '              STATE=$(cat /fixture/state)',
                            '              if [ "$STATE" = "healthy" ]; then',
                            '                echo "{\\\\"event\\\\":\\\\"hermes_demo_test_pass\\\\",\\\\"incident_id\\\\":\\\\"$INCIDENT_ID\\\\",\\\\"message\\\\":\\\\"fixture state healthy\\\\"}"',
                            '                exit 0',
                            '              fi',
                            '              echo "{\\\\"event\\\\":\\\\"hermes_demo_test_failure\\\\",\\\\"incident_id\\\\":\\\\"$INCIDENT_ID\\\\",\\\\"classification_hint\\\\":\\\\"demo_fixture_unhealthy\\\\",\\\\"message\\\\":\\\\"expected fixture state healthy; found $STATE\\\\"}"',
                            '              exit 1',
                            '          volumeMounts:',
                            '            - name: fixture',
                            '              mountPath: /fixture',
                            '              readOnly: true',
                            '          resources:',
                            '            requests:',
                            '              cpu: 25m',
                            '              memory: 16Mi',
                            '            limits:',
                            '              cpu: 100m',
                            '              memory: 32Mi',
                            '      volumes:',
                            '        - name: fixture',
                            '          configMap:',
                            '            name: hermes-triage-demo-fixture'
                          ]
                          writeFile file: 'test-runner-job.yaml', text: manifest.join('\\n') + '\\n'
                          sh 'kubectl -n hermes-triage-demo create -f test-runner-job.yaml'
                          def verdict = 'unknown'
                          // Read the container's exit code, not the Job status.
                          // The kubelet records the exit code the moment the
                          // container stops; the Job controller can take minutes
                          // to reconcile .status.failed, and has been observed
                          // taking over half an hour on this cluster. Waiting on
                          // the Job made the build look hung long after the test
                          // had actually finished.
                          timeout(time: 3, unit: 'MINUTES') {
                            waitUntil {
                              def code = sh(script: 'kubectl -n hermes-triage-demo get pods -l job-name=' + jobName + ' -o jsonpath="{.items[0].status.containerStatuses[0].state.terminated.exitCode}" 2>/dev/null || true', returnStdout: true).trim()
                              if (code == '0') { verdict = 'pass'; return true }
                              if (code != '') { verdict = 'fail'; return true }
                              return false
                            }
                          }
                          def runnerLog = sh(script: 'kubectl -n hermes-triage-demo logs job/' + jobName + ' --tail=20 || true', returnStdout: true).trim()
                          echo 'test-runner output: ' + runnerLog
                          def junitBody
                          if (verdict == 'pass') {
                            junitBody = '<testsuite name="hermes-triage-demo" tests="1" failures="0"><testcase classname="hermes-triage-demo" name="fixture-state-check"/></testsuite>'
                          } else {
                            def safeLog = runnerLog.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
                            junitBody = '<testsuite name="hermes-triage-demo" tests="1" failures="1"><testcase classname="hermes-triage-demo" name="fixture-state-check"><failure message="demo fixture unhealthy">' + safeLog + '</failure></testcase></testsuite>'
                          }
                          writeFile file: 'demo-junit.xml', text: junitBody
                          archiveArtifacts artifacts: 'demo-junit.xml', fingerprint: false
                          if (verdict != 'pass') {
                            error('Demo fixture check failed for incident ' + incidentId)
                          }
                        }
                      }
                    }
                  }
                ''')
              }
            }
          }
          multibranchPipelineJob('titan-iac-quality-gate') {
            branchSources {
              branchSource {
                source {
                  git {
                    id('titan-iac-quality-gate')
                    remote('https://scm.bstein.dev/bstein/titan-iac.git')
                    credentialsId('gitea-pat')
                  }
                }
              }
            }
            factory {
              workflowBranchProjectFactory {
                scriptPath('ci/Jenkinsfile.titan-iac')
              }
            }
            orphanedItemStrategy {
              discardOldItems {
                numToKeep(30)
              }
            }
            triggers {
              periodicFolderTrigger {
                interval('12h')
              }
            }
            configure { node ->
              def webhookToken = System.getenv('TITAN_IAC_WEBHOOK_TOKEN') ?: ''
              def triggers = node / 'triggers'
              def webhook = triggers.appendNode('com.igalg.jenkins.plugins.mswt.trigger.ComputedFolderWebHookTrigger')
              webhook.appendNode('token', webhookToken)
            }
          }
  base.yaml: |
    jenkins:
      disableRememberMe: false
      mode: NORMAL
      numExecutors: 0
      labelString: ""
      projectNamingStrategy: "standard"
      markupFormatter:
        plainText
      globalNodeProperties:
        - envVars:
            env:
              - key: "GIT_CONFIG_COUNT"
                value: "1"
              - key: "GIT_CONFIG_KEY_0"
                value: "safe.directory"
              - key: "GIT_CONFIG_VALUE_0"
                value: "*"
      clouds:
      - kubernetes:
          containerCapStr: "5"
          connectTimeout: "20"
          readTimeout: "90"
          jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080"
          jenkinsTunnel: "jenkins.jenkins.svc.cluster.local:50000"
          skipTlsVerify: false
          maxRequestsPerHostStr: "32"
          retentionTimeout: "5"
          waitForPodSec: "600"
          name: "kubernetes"
          namespace: "jenkins"
          restrictedPssSecurityContext: false
          serverUrl: "https://kubernetes.default"
          credentialsId: ""
          podLabels:
          - key: "jenkins/jenkins-jenkins-agent"
            value: "true"
          templates:
            - name: "default"
              namespace: "jenkins"
              workspaceVolume:
                dynamicPVC:
                  accessModes: "ReadWriteOnce"
                  requestsSize: "20Gi"
                  storageClassName: "astreae"
              containers:
              - name: "jnlp"
                args: "^${computer.jnlpmac} ^${computer.name}"
                envVars:
                  - envVar:
                      key: "JENKINS_URL"
                      value: "http://jenkins.jenkins.svc.cluster.local:8080/"
                image: "jenkins/inbound-agent:3355.v388858a_47b_33-3"
                privileged: "false"
                resourceLimitCpu: 512m
                resourceLimitMemory: 512Mi
                resourceRequestCpu: 512m
                resourceRequestMemory: 512Mi
                ttyEnabled: false
                workingDir: /home/jenkins/agent
              idleMinutes: 0
              instanceCap: 2147483647
              label: "jenkins-jenkins-agent "
              nodeUsageMode: "NORMAL"
              podRetention: Never
              serviceAccount: "jenkins"
              slaveConnectTimeoutStr: "100"
              yaml: |
                spec:
                  securityContext:
                    runAsUser: 1000
                    runAsGroup: 1000
                    fsGroup: 1000
                    fsGroupChangePolicy: "OnRootMismatch"
                  nodeSelector:
                    kubernetes.io/arch: arm64
                    node-role.kubernetes.io/worker: "true"
                  affinity:
                    nodeAffinity:
                      requiredDuringSchedulingIgnoredDuringExecution:
                        nodeSelectorTerms:
                          - matchExpressions:
                              - key: kubernetes.io/hostname
                                operator: NotIn
                                values:
                                  - titan-06
                      preferredDuringSchedulingIgnoredDuringExecution:
                        - weight: 100
                          preference:
                            matchExpressions:
                              - key: atlas.bstein.dev/spillover
                                operator: DoesNotExist
                        - weight: 95
                          preference:
                            matchExpressions:
                              - key: kubernetes.io/hostname
                                operator: NotIn
                                values:
                                  - titan-13
                                  - titan-15
                                  - titan-17
                                  - titan-19
                        - weight: 85
                          preference:
                            matchExpressions:
                              - key: hardware
                                operator: In
                                values:
                                  - rpi5
                        - weight: 45
                          preference:
                            matchExpressions:
                              - key: hardware
                                operator: In
                                values:
                                  - rpi4
                  topologySpreadConstraints:
                    - maxSkew: 1
                      topologyKey: kubernetes.io/hostname
                      whenUnsatisfiable: ScheduleAnyway
                      labelSelector:
                        matchLabels:
                          jenkins/jenkins-jenkins-agent: "true"
              yamlMergeStrategy: override
              inheritYamlMergeStrategy: false
      slaveAgentPort: 50000
      crumbIssuer:
        standard:
          excludeClientIPFromCrumb: true
    unclassified:
      location:
        url: "https://ci.bstein.dev/"
