# 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.') definition { cpsScm { scm { git { remote { url('https://scm.bstein.dev/bstein/cassandra.git') credentials('gitea-pat') } branches('*/main') } } 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: 1001 runAsGroup: 1001 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}" def seed = params.SEED_FAILURE ? "true" : "false" writeFile file: 'test-runner-job.yaml', text: """ 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 containers: - name: test-runner image: busybox:1.37 env: - name: INCIDENT_ID value: "${incidentId}" - name: SEED_FAILURE value: "${seed}" command: - sh - -c - | STATE_FILE=/fixture/state if [ "\\$SEED_FAILURE" = "true" ]; then echo unhealthy > "\\$STATE_FILE" fi if [ ! -f "\\$STATE_FILE" ]; then echo healthy > "\\$STATE_FILE" fi STATE=\\$(cat "\\$STATE_FILE") 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 resources: requests: cpu: 25m memory: 16Mi limits: cpu: 100m memory: 32Mi volumes: - name: fixture persistentVolumeClaim: claimName: hermes-triage-demo-fixture """ sh "kubectl -n hermes-triage-demo create -f test-runner-job.yaml" def verdict = "unknown" timeout(time: 3, unit: 'MINUTES') { waitUntil(initialRecurrencePeriod: 5000) { def st = sh(script: "kubectl -n hermes-triage-demo get job ${jobName} -o jsonpath='{.status.succeeded}:{.status.failed}'", returnStdout: true).trim() if (st.startsWith("1")) { verdict = "pass"; return true } if (st.endsWith(":1")) { 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:\\n${runnerLog}" def junitBody if (verdict == "pass") { junitBody = """""" } else { def safeLog = runnerLog.replace("&", "&").replace("<", "<").replace(">", ">") junitBody = """${safeLog}""" } writeFile file: 'demo-junit.xml', text: junitBody junit 'demo-junit.xml' 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/"