diff --git a/README.md b/README.md
index bfed9c0..381e08b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,3 @@
-This repository contains OpenShift v3 projects
-for Project Atomic related CI infrastructure, which
-is currently just homu.
-
-The plan is to move this into
-https://github.com/openshift/release/tree/master/cluster/ci
-
+This repository contains OpenShift v3 projects for Project
+Atomic related CI infrastructure, which is currently homu
+and PAPR. Together, we refer to these projects as PACI.
diff --git a/papr/README.md b/papr/README.md
new file mode 100644
index 0000000..46ac746
--- /dev/null
+++ b/papr/README.md
@@ -0,0 +1,54 @@
+# PAPR Jenkins
+
+This directory defines the infrastructure for a PAPR pipeline.
+
+The infrastructure is instantiated by the `papr-jenkins.yaml` OpenShift template
+which will create a Jenkins instance customized for PAPR. This is heavily based
+on the `jenkins-persistent` builtin OpenShift template, but with the following
+enhancements:
+
+- the GHPRB plugin is installed by default
+- anonymous users have read access
+- a GitHub token is securely installed at runtime using OpenShift secrets
+
+The `jenkins/` directory is used by the S2I
+[OpenShift Jenkins builder](https://github.com/openshift/jenkins/tree/8e58d88#installing-using-s2i-build)
+to create the customized Jenkins image.
+
+# Usage
+
+Assuming you already have a cluster set up and running (e.g. `oc cluster up`):
+
+```
+$ oc new-project papr
+$ echo "$GITHUB_TOKEN" > mytoken
+$ oc secrets new github-token token=mytoken
+$ oc new-app --file papr-jenkins.yaml
+```
+
+If your project already exists (e.g. you are not a cluster admin) and it is not
+named `papr`, make sure to pass the `-p NAMESPACE=$project` argument to the
+`new-app` command above.
+
+You can now start a build of the PAPR Jenkins image using:
+
+```
+$ oc start-build papr-jenkins
+```
+
+Once the image is built, it will be automatically deployed and available at:
+
+https://jenkins-papr.127.0.0.1.nip.io/
+
+If you're working on your own fork, you can point OpenShift at it:
+
+```
+$ oc new-app --file papr-jenkins.yaml \
+ -p REPO_URL=https://github.com/jlebon/projectatomic-ci-infra \
+ -p REPO_REF=my-branch
+```
+
+Note that modifications to Jenkins configurations fed to the S2I builder will
+probably require that you delete and recreate the PVC so that old configurations
+don't override new ones (I find it easier to just `oc delete project papr` and
+recreate it; the builder image is cached in the `openshift` namespace).
diff --git a/papr/jenkins/configuration/config.xml.tpl b/papr/jenkins/configuration/config.xml.tpl
new file mode 100644
index 0000000..0653fbd
--- /dev/null
+++ b/papr/jenkins/configuration/config.xml.tpl
@@ -0,0 +1,66 @@
+
+
+
+ 2.89.2
+ 5
+ NORMAL
+ true
+
+ hudson.model.Computer.Configure:admin
+ hudson.model.Computer.Delete:admin
+ hudson.model.Hudson.Administer:admin
+ hudson.model.Hudson.Read:admin
+ hudson.model.Item.Build:admin
+ hudson.model.Item.Configure:admin
+ hudson.model.Item.Create:admin
+ hudson.model.Item.Delete:admin
+ hudson.model.Item.Read:admin
+ hudson.model.Item.Workspace:admin
+ hudson.model.Run.Delete:admin
+ hudson.model.Run.Update:admin
+ hudson.model.View.Configure:admin
+ hudson.model.View.Create:admin
+ hudson.model.View.Delete:admin
+
+ hudson.model.Hudson.Read:anonymous
+ hudson.model.Item.Discover:anonymous
+ hudson.model.Item.Read:anonymous
+ hudson.model.View.Read:anonymous
+ hudson.scm.SCM.Tag:admin
+
+
+ true
+ false
+
+ false
+ ${ITEM_ROOTDIR}/workspace
+ ${ITEM_ROOTDIR}/builds
+
+
+
+
+
+ ${KUBERNETES_CONFIG}
+
+ 1
+ 0
+
+
+
+ All
+ false
+ false
+
+
+
+ All
+ ${JNLP_PORT}
+
+ JNLP-connect
+ JNLP2-connect
+
+
+
+
+ true
+
diff --git a/papr/jenkins/configuration/credentials.xml.tpl b/papr/jenkins/configuration/credentials.xml.tpl
new file mode 100644
index 0000000..d0b9497
--- /dev/null
+++ b/papr/jenkins/configuration/credentials.xml.tpl
@@ -0,0 +1,19 @@
+
+
+
+ ${KUBERNETES_CREDENTIALS}
+
+
+
+
+
+
+ GLOBAL
+ GitHub token
+
+ SOOPERSEKRIT
+
+
+
+
+
diff --git a/papr/jenkins/configuration/init.groovy b/papr/jenkins/configuration/init.groovy
new file mode 100644
index 0000000..dca92ad
--- /dev/null
+++ b/papr/jenkins/configuration/init.groovy
@@ -0,0 +1,41 @@
+/* This init script injects the real GitHub token mounted into the container
+ * into the stored credentials.
+ */
+
+import jenkins.model.*
+import com.cloudbees.plugins.credentials.*
+import com.cloudbees.plugins.credentials.domains.*
+import org.jenkinsci.plugins.plaincredentials.*
+import org.jenkinsci.plugins.plaincredentials.impl.*
+
+StringCredentials findGitHubTokenCreds() {
+ def creds = CredentialsProvider.lookupCredentials(
+ StringCredentials.class,
+ Jenkins.instance,
+ null,
+ null)
+ for (c in creds) {
+ if (c.description.equals("GitHub token"))
+ return c
+ }
+}
+
+github_creds = findGitHubTokenCreds()
+if (!github_creds) {
+ println("Didn't find GitHub token credentials, exiting...")
+ return
+}
+
+def real_token_file = "/etc/github-token/token"
+println("Reading token value from $real_token_file")
+String real_token = new File(real_token_file).text.trim()
+
+Credentials new_github_creds = (Credentials) new StringCredentialsImpl(
+ CredentialsScope.GLOBAL,
+ github_creds.id, /* crucially; we copy the ID here to make it a clean swap */
+ github_creds.description,
+ hudson.util.Secret.fromString(real_token))
+
+store = SystemCredentialsProvider.instance.store
+store.updateCredentials(Domain.global(), github_creds, new_github_creds)
+println("Successfully updated credential token!")
diff --git a/papr/jenkins/configuration/jobs/README.md b/papr/jenkins/configuration/jobs/README.md
new file mode 100644
index 0000000..011692b
--- /dev/null
+++ b/papr/jenkins/configuration/jobs/README.md
@@ -0,0 +1,4 @@
+This directory exists just so we override the default job
+that gets created for us. See:
+
+https://github.com/openshift/jenkins/blob/8e58d888cc10e21db40ff5dbd40efe8ee8c77f93/2/contrib/s2i/assemble#L29
diff --git a/papr/jenkins/configuration/org.jenkinsci.plugins.ghprb.GhprbTrigger.xml.tpl b/papr/jenkins/configuration/org.jenkinsci.plugins.ghprb.GhprbTrigger.xml.tpl
new file mode 100644
index 0000000..7014865
--- /dev/null
+++ b/papr/jenkins/configuration/org.jenkinsci.plugins.ghprb.GhprbTrigger.xml.tpl
@@ -0,0 +1,38 @@
+
+
+ 1
+
+ (?m)^\s*bot,\s+add\s+author\s+to\s+whitelist\s*\.?$
+ (?m)^\s*bot,\s+test\s+pull\s+request\s*\.?$
+ (?m)^\s*bot,\s+test\s+pull\s+request\s+once\s*\.?$
+ .*\[skip\W+ci\].*
+
+
+ H/10 * * * *
+ false
+ false
+ false
+ FAILURE
+ false
+ false
+
+
+
+
+ https://api.github.com
+
+ Authenticated connection
+
+
+
+ Can one of the admins verify this patch?
+
+
+
+ false
+ false
+
+
+
+
diff --git a/papr/jenkins/plugins.txt b/papr/jenkins/plugins.txt
new file mode 100644
index 0000000..230d8f7
--- /dev/null
+++ b/papr/jenkins/plugins.txt
@@ -0,0 +1,4 @@
+# This file is read by
+# https://github.com/openshift/jenkins/blob/8e58d88/2/contrib/jenkins/install-plugins.sh
+# TODO: freeze all plugins
+ghprb:1.39.0
diff --git a/papr/papr-jenkins.yaml b/papr/papr-jenkins.yaml
new file mode 100644
index 0000000..820420a
--- /dev/null
+++ b/papr/papr-jenkins.yaml
@@ -0,0 +1,261 @@
+apiVersion: v1
+kind: Template
+labels:
+ app: papr-jenkins
+ template: papr-jenkins-template
+metadata:
+ annotations:
+ description: |-
+ Jenkins service for PAPR. This template is heavily based on the built-in
+ jenkins-persistent OpenShift template. It adds an S2I buildconfig to
+ create Jenkins images tailored for PAPR usage.
+ iconClass: icon-jenkins
+ openshift.io/display-name: PAPR Jenkins
+ openshift.io/documentation-url: https://github.com/projectatomic/projectatomic-ci-infra
+ openshift.io/support-url: https://github.com/projectatomic/projectatomic-ci-infra
+ openshift.io/provider-display-name: Project Atomic CI
+ tags: papr,jenkins
+ name: papr-jenkins
+objects:
+- apiVersion: v1
+ kind: ImageStream
+ metadata:
+ name: papr-jenkins
+ namespace: ${NAMESPACE}
+- apiVersion: v1
+ kind: BuildConfig
+ metadata:
+ name: papr-jenkins
+ triggers:
+ - type: ImageChange
+ imageChange: {}
+ - type: ConfigChange
+ # Notably missing here is a GitHub webhook; we don't want to restart Jenkins
+ # everytime we push to the PACI repo.
+ spec:
+ source:
+ type: Git
+ git:
+ uri: ${REPO_URL}
+ ref: ${REPO_REF}
+ contextDir: papr/jenkins/
+ strategy:
+ type: Source
+ sourceStrategy:
+ from:
+ kind: ImageStreamTag
+ name: jenkins:latest
+ namespace: openshift
+ forcePull: true
+ output:
+ to:
+ kind: ImageStreamTag
+ name: papr-jenkins:latest
+ namespace: ${NAMESPACE}
+- apiVersion: v1
+ kind: Route
+ metadata:
+ annotations:
+ template.openshift.io/expose-uri: http://{.spec.host}{.spec.path}
+ name: ${JENKINS_SERVICE_NAME}
+ spec:
+ tls:
+ insecureEdgeTerminationPolicy: Redirect
+ termination: edge
+ to:
+ kind: Service
+ name: ${JENKINS_SERVICE_NAME}
+- apiVersion: v1
+ kind: PersistentVolumeClaim
+ metadata:
+ name: ${JENKINS_SERVICE_NAME}
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: ${VOLUME_CAPACITY}
+- apiVersion: v1
+ kind: DeploymentConfig
+ metadata:
+ annotations:
+ template.alpha.openshift.io/wait-for-ready: "true"
+ name: ${JENKINS_SERVICE_NAME}
+ spec:
+ replicas: 1
+ selector:
+ name: ${JENKINS_SERVICE_NAME}
+ strategy:
+ type: Recreate
+ template:
+ metadata:
+ labels:
+ name: ${JENKINS_SERVICE_NAME}
+ spec:
+ containers:
+ - capabilities: {}
+ env:
+ - name: OPENSHIFT_ENABLE_OAUTH
+ value: ${ENABLE_OAUTH}
+ - name: OPENSHIFT_ENABLE_REDIRECT_PROMPT
+ value: "true"
+ - name: KUBERNETES_MASTER
+ value: https://kubernetes.default:443
+ - name: KUBERNETES_TRUST_CERTIFICATES
+ value: "true"
+ - name: JENKINS_SERVICE_NAME
+ value: ${JENKINS_SERVICE_NAME}
+ - name: JNLP_SERVICE_NAME
+ value: ${JNLP_SERVICE_NAME}
+ image: ' '
+ imagePullPolicy: IfNotPresent
+ livenessProbe:
+ failureThreshold: 30
+ httpGet:
+ path: /login
+ port: 8080
+ initialDelaySeconds: 420
+ timeoutSeconds: 3
+ name: jenkins
+ readinessProbe:
+ httpGet:
+ path: /login
+ port: 8080
+ initialDelaySeconds: 3
+ timeoutSeconds: 3
+ resources:
+ limits:
+ memory: ${MEMORY_LIMIT}
+ securityContext:
+ capabilities: {}
+ privileged: false
+ terminationMessagePath: /dev/termination-log
+ volumeMounts:
+ - name: ${JENKINS_SERVICE_NAME}-data
+ mountPath: /var/lib/jenkins
+ - name: github-token
+ mountPath: /etc/github-token
+ readOnly: true
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+ serviceAccountName: ${JENKINS_SERVICE_NAME}
+ volumes:
+ - name: ${JENKINS_SERVICE_NAME}-data
+ persistentVolumeClaim:
+ claimName: ${JENKINS_SERVICE_NAME}
+ - name: github-token
+ secret:
+ secretName: ${GITHUB_TOKEN_SECRET}
+ triggers:
+ - imageChangeParams:
+ automatic: true
+ containerNames:
+ - jenkins
+ from:
+ kind: ImageStreamTag
+ name: ${JENKINS_IMAGE_STREAM_TAG}
+ namespace: ${NAMESPACE}
+ lastTriggeredImage: ""
+ type: ImageChange
+ - type: ConfigChange
+- apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ annotations:
+ serviceaccounts.openshift.io/oauth-redirectreference.jenkins: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"${JENKINS_SERVICE_NAME}"}}'
+ name: ${JENKINS_SERVICE_NAME}
+- apiVersion: v1
+ groupNames: null
+ kind: RoleBinding
+ metadata:
+ name: ${JENKINS_SERVICE_NAME}_edit
+ roleRef:
+ name: edit
+ subjects:
+ - kind: ServiceAccount
+ name: ${JENKINS_SERVICE_NAME}
+- apiVersion: v1
+ kind: Service
+ metadata:
+ name: ${JNLP_SERVICE_NAME}
+ spec:
+ ports:
+ - name: agent
+ nodePort: 0
+ port: 50000
+ protocol: TCP
+ targetPort: 50000
+ selector:
+ name: ${JENKINS_SERVICE_NAME}
+ sessionAffinity: None
+ type: ClusterIP
+- apiVersion: v1
+ kind: Service
+ metadata:
+ annotations:
+ service.alpha.openshift.io/dependencies: '[{"name": "${JNLP_SERVICE_NAME}", "namespace": "", "kind": "Service"}]'
+ service.openshift.io/infrastructure: "true"
+ name: ${JENKINS_SERVICE_NAME}
+ spec:
+ ports:
+ - name: web
+ nodePort: 0
+ port: 80
+ protocol: TCP
+ targetPort: 8080
+ selector:
+ name: ${JENKINS_SERVICE_NAME}
+ sessionAffinity: None
+ type: ClusterIP
+parameters:
+- description: The name of the OpenShift Service exposed for the Jenkins container.
+ displayName: Jenkins Service Name
+ name: JENKINS_SERVICE_NAME
+ value: jenkins
+- description: The name of the service used for master/slave communication.
+ displayName: Jenkins JNLP Service Name
+ name: JNLP_SERVICE_NAME
+ value: jenkins-jnlp
+- description: Whether to enable OAuth OpenShift integration. If false, the static
+ account 'admin' will be initialized with the password 'password'.
+ displayName: Enable OAuth in Jenkins
+ name: ENABLE_OAUTH
+ value: "true"
+- description: Maximum amount of memory the container can use.
+ displayName: Memory Limit
+ name: MEMORY_LIMIT
+ value: 512Mi
+- description: Volume space available for data, e.g. 512Mi, 2Gi.
+ displayName: Volume Capacity
+ name: VOLUME_CAPACITY
+ required: true
+ value: 1Gi
+- description: Name of the ImageStreamTag to be used for the Jenkins image.
+ displayName: Jenkins ImageStreamTag
+ name: JENKINS_IMAGE_STREAM_TAG
+ value: papr-jenkins:latest
+# Templates are not able to pick up the namespace we're currently in, so let's
+# allow users to specify a different one if they didn't name the project "papr"
+# https://github.com/openshift/origin/issues/13934
+- description: >
+ The OpenShift Namespace where the Jenkins ImageStream resides. This is
+ usually the same namespace as the project.
+ displayName: Jenkins ImageStream Namespace
+ name: NAMESPACE
+ value: papr
+ required: true
+# This is similar to the ci-pipeline template. It allows developers to specify
+# their own repos when iterating.
+- description: Git source URI for Jenkins S2I
+ name: REPO_URL
+ value: https://github.com/projectatomic/projectatomic-ci-infra
+- description: Git branch/tag reference
+ name: REPO_REF
+ value: master
+- description: >
+ GitHub token secret. This is *not* the token itself. It is the name of the
+ OpenShift secret containing the token, which must be created beforehand. The
+ secret is expected to define a key "token" containing the token.
+ name: GITHUB_TOKEN_SECRET
+ value: github-token
+ required: true