Initialize config repository
diff --git a/playbooks/openshift/build-project.yaml b/playbooks/openshift/build-project.yaml
new file mode 100644
index 0000000..957c957
--- /dev/null
+++ b/playbooks/openshift/build-project.yaml
@@ -0,0 +1,84 @@
+# This file is managed by ansible, do not edit directly
+---
+- name: prepare dumb bare clone of future state
+  git:
+    repo: "{{ zuul.executor.work_root }}/{{ zuul.project.src_dir }}"
+    dest: "{{ zuul.executor.work_root }}/{{ zuul.project.src_dir }}.git"
+    bare: yes
+
+- name: update server info for dumb http transport
+  command: git update-server-info
+  args:
+    chdir: "{{ zuul.executor.work_root }}/{{ zuul.project.src_dir }}.git"
+
+- name: create project dir on http server
+  command: >
+    {{ oc_command }} exec {{ zm_name }} -- mkdir -p {{ zuul.project.src_dir }}.git
+
+- name: copy project to http server
+  command: >
+    {{ oc_command }} rsync -q --progress=false
+      {{ zuul.executor.work_root }}/{{ zuul.project.src_dir }}.git/
+      {{ zm_name }}:/opt/app-root/src/{{ zuul.project.src_dir }}.git/
+  no_log: true
+
+- name: create project ImageStream spec
+  openshift_raw:
+    state: present
+    namespace: "{{ zuul.resources['project'].namespace }}"
+    context: "{{ zuul.resources['project'].context }}"
+    definition:
+      apiVersion: v1
+      kind: ImageStream
+      metadata:
+        generation: 1
+        labels:
+          app: "{{ zuul.project.short_name }}"
+        name: "{{ zuul.project.short_name }}"
+      spec:
+        lookupPolicy:
+          local: false
+  register: _image_stream
+
+- name: create project BuildConfig spec
+  openshift_raw:
+    state: present
+    namespace: "{{ zuul.resources['project'].namespace }}"
+    context: "{{ zuul.resources['project'].context }}"
+    definition:
+      apiVersion: v1
+      kind: BuildConfig
+      metadata:
+        labels:
+          app: "{{ zuul.project.short_name }}"
+        name: "{{ zuul.project.short_name }}"
+      spec:
+        output:
+          to:
+            kind: ImageStreamTag
+            name: '{{ zuul.project.short_name }}:latest'
+        runPolicy: Serial
+        source:
+          git:
+            ref: master
+            uri: 'http://staging-http-server:8080/{{ zuul.project.src_dir }}.git'
+          type: Git
+        strategy:
+          sourceStrategy:
+            from:
+              kind: ImageStreamTag
+              name: '{{ base_image }}'
+              namespace: openshift
+          type: Source
+        triggers:
+          - type: ImageChange
+          - type: ConfigChange
+
+- name: wait for project image built
+  command: >
+    {{ oc_command }} get builds
+      -o "jsonpath={.items[?(@.metadata.labels.buildconfig!='staging-http-server')].status.phase}"
+  register: _project_build
+  retries: 600
+  delay: 1
+  until: "'Complete' in _project_build.stdout"
diff --git a/playbooks/openshift/deploy-project.yaml b/playbooks/openshift/deploy-project.yaml
new file mode 100644
index 0000000..bc1b63a
--- /dev/null
+++ b/playbooks/openshift/deploy-project.yaml
@@ -0,0 +1,66 @@
+# This file is managed by ansible, do not edit directly
+---
+- name: start the project
+  openshift_raw:
+    state: present
+    namespace: "{{ zuul.resources['project'].namespace }}"
+    context: "{{ zuul.resources['project'].context }}"
+    definition:
+      apiVersion: v1
+      kind: DeploymentConfig
+      metadata:
+        generation: 2
+        labels:
+          app: "{{ zuul.project.short_name }}"
+        name: "{{ zuul.project.short_name }}"
+      spec:
+        replicas: 1
+        selector:
+          deploymentconfig: "{{ zuul.project.short_name }}"
+        strategy:
+          resources: {}
+          type: Rolling
+        template:
+          metadata:
+            labels:
+              app: "{{ zuul.project.short_name }}"
+              deploymentconfig: "{{ zuul.project.short_name }}"
+          spec:
+            containers:
+              - image: "{{ _image_stream.result.status.dockerImageRepository }}"
+                name: "{{ zuul.project.short_name }}"
+                command: [ "/bin/bash", "-c", "--" ]
+                args: [ "while true; do sleep 30; done;" ]
+                ports:
+                  - containerPort: 8080
+                    protocol: TCP
+                  - containerPort: 8443
+                    protocol: TCP
+                resources: {}
+            dnsPolicy: ClusterFirst
+            restartPolicy: Always
+            schedulerName: default-scheduler
+            securityContext: {}
+            terminationGracePeriodSeconds: 30
+        test: false
+
+- name: get project pod name
+  command: >
+    {{ oc_command }} get pods --field-selector=status.phase=Running
+    -o "jsonpath={.items[?(@.metadata.labels.app=='{{ zuul.project.short_name }}')].metadata.name}"
+  register: _pod_name
+  retries: 600
+  delay: 1
+  until: "zuul.project.short_name in _pod_name.stdout"
+
+- name: create pods list
+  set_fact:
+    pods_data:
+      pods:
+        - name: "{{ zuul.project.short_name }}"
+          pod: "{{ _pod_name.stdout }}"
+
+- name: store pods list in work_root
+  copy:
+    content: "{{ pods_data | to_yaml }}"
+    dest: "{{ zuul.executor.work_root }}/pods.yaml"
diff --git a/playbooks/openshift/pre.yaml b/playbooks/openshift/pre.yaml
new file mode 100644
index 0000000..44fff25
--- /dev/null
+++ b/playbooks/openshift/pre.yaml
@@ -0,0 +1,34 @@
+---
+- hosts: localhost
+  tasks:
+    - block:
+        - import_role: name=emit-job-header
+        # We need those tasks to use log-inventory, see: https://review.openstack.org/577674
+        - name: Define zuul_info_dir fact
+          set_fact:
+            zuul_info_dir: "{{ zuul.executor.log_root }}/zuul-info"
+
+        - name: Ensure Zuul Ansible directory exists
+          delegate_to: localhost
+          run_once: true
+          file:
+            path: "{{ zuul_info_dir }}"
+            state: directory
+
+        - name: Define inventory_file fact
+          set_fact:
+            inventory_file: "/tmp/{{ zuul.build }}/ansible/inventory.yaml"
+
+        - import_role: name=log-inventory
+      vars:
+        zuul_log_url: "https://spfactory.storpool.com/logs"
+
+    - name: Set oc_command fact
+      set_fact:
+        oc_command: >
+          oc --context "{{ zuul.resources['project'].context }}"
+             --namespace "{{ zuul.resources['project'].namespace }}"
+
+    - include_tasks: prepare-namespace.yaml
+    - include_tasks: build-project.yaml
+    - include_tasks: deploy-project.yaml
diff --git a/playbooks/openshift/prepare-namespace.yaml b/playbooks/openshift/prepare-namespace.yaml
new file mode 100644
index 0000000..d583469
--- /dev/null
+++ b/playbooks/openshift/prepare-namespace.yaml
@@ -0,0 +1,80 @@
+# This file is managed by ansible, do not edit directly
+---
+- name: create staging-http DeploymentConfig
+  openshift_raw:
+    state: present
+    namespace: "{{ zuul.resources['project'].namespace }}"
+    context: "{{ zuul.resources['project'].context }}"
+    definition:
+      apiVersion: v1
+      kind: DeploymentConfig
+      metadata:
+        generation: 2
+        labels:
+          app: staging-http-server
+        name: staging-http-server
+      spec:
+        replicas: 1
+        selector:
+          deploymentconfig: staging-http-server
+        strategy:
+          resources: {}
+          type: Rolling
+        template:
+          metadata:
+            labels:
+              app: staging-http-server
+              deploymentconfig: staging-http-server
+          spec:
+            containers:
+              - image: "docker.io/softwarefactoryproject/staging-http-server"
+                # imagePullPolicy: Always
+                name: staging-http-server
+                ports:
+                  - containerPort: 8080
+                    protocol: TCP
+                  - containerPort: 8443
+                    protocol: TCP
+                resources: {}
+            dnsPolicy: ClusterFirst
+            restartPolicy: Always
+            schedulerName: default-scheduler
+            terminationGracePeriodSeconds: 30
+
+- name: create staging-http Service spec
+  openshift_raw:
+    state: present
+    namespace: "{{ zuul.resources['project'].namespace }}"
+    context: "{{ zuul.resources['project'].context }}"
+    definition:
+      apiVersion: v1
+      kind: Service
+      metadata:
+        labels:
+          app: staging-http-server
+        name: staging-http-server
+      spec:
+        ports:
+          - name: 8080-tcp
+            port: 8080
+            protocol: TCP
+            targetPort: 8080
+        selector:
+          deploymentconfig: staging-http-server
+        sessionAffinity: None
+        type: ClusterIP
+      status:
+        loadBalancer: {}
+
+- name: get staging-http-server pod name
+  command: >
+    {{ oc_command }} get pods --field-selector=status.phase=Running
+    -o "jsonpath={.items[?(@.metadata.labels.app=='staging-http-server')].metadata.name}"
+  register: _zm_name
+  retries: 600
+  delay: 1
+  until: "'staging-http' in _zm_name.stdout"
+
+- name: register staging-http-server pod name
+  set_fact:
+    zm_name: "{{ _zm_name.stdout }}"
diff --git a/playbooks/openshift/unprivileged-machine.yaml b/playbooks/openshift/unprivileged-machine.yaml
new file mode 100644
index 0000000..431b844
--- /dev/null
+++ b/playbooks/openshift/unprivileged-machine.yaml
@@ -0,0 +1,39 @@
+---
+- hosts: localhost
+  tasks:
+    - block:
+        - import_role: name=emit-job-header
+        # We need those tasks to use log-inventory, see: https://review.openstack.org/577674
+        - name: Define zuul_info_dir fact
+          set_fact:
+            zuul_info_dir: "{{ zuul.executor.log_root }}/zuul-info"
+
+        - name: Ensure Zuul Ansible directory exists
+          delegate_to: localhost
+          run_once: true
+          file:
+            path: "{{ zuul_info_dir }}"
+            state: directory
+
+        - name: Define inventory_file fact
+          set_fact:
+            inventory_file: "/tmp/{{ zuul.build }}/ansible/inventory.yaml"
+
+        - import_role: name=log-inventory
+      vars:
+        zuul_log_url: "https://spfactory.storpool.com/logs"
+
+    - name: Create src directory
+      command: >
+        oc --context "{{ zuul.resources['pod'].context }}"
+           --namespace "{{ zuul.resources['pod'].namespace }}"
+           exec {{ zuul.resources['pod'].pod }} mkdir src
+
+    - name: Copy src repos to the pod
+      command: >
+        oc --context "{{ zuul.resources['pod'].context }}"
+           --namespace "{{ zuul.resources['pod'].namespace }}"
+        rsync -q --progress=false
+          {{ zuul.executor.src_root }}/
+          {{ zuul.resources['pod'].pod }}:src/
+      no_log: true