├── Makefile ├── patroni ├── postgresql.sh ├── nss-wrap.sh ├── entrypoint.sh ├── callback.py └── Dockerfile ├── config ├── detectors.ini └── roundup.ini ├── template_bpo.yaml ├── template_patroni.yaml ├── README.md └── LICENSE /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Default docker registry and username for the image 3 | PREFIX ?= docker.io/python 4 | 5 | all: builder patroni 6 | .PHONY: all 7 | 8 | builder: 9 | docker build -t $(PREFIX)/bpo-builder builder 10 | .PHONY: builder 11 | 12 | patroni: 13 | ./patroni/postgresql.sh 14 | docker build -t $(PREFIX)/bpo-patroni patroni 15 | .PHONY: patroni 16 | -------------------------------------------------------------------------------- /patroni/postgresql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # clone postrges repository 4 | tmppg=$(mktemp -d) 5 | git clone https://github.com/docker-library/postgres/ "${tmppg}" 6 | pushd "${tmppg}" > /dev/null 7 | pushd 10 > /dev/null 8 | # remove the VOLUME line 9 | sed -i '/VOLUME \/var\/lib\/postgresql\/data/d' Dockerfile 10 | # and build a new image 11 | docker build -t postgres:10 . 12 | popd > /dev/null 13 | popd > /dev/null 14 | rm -rf "${tmppg}" 15 | -------------------------------------------------------------------------------- /patroni/nss-wrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # write mock passwd and group files 4 | ( 5 | exec 2>/dev/null 6 | __username=${NSS_USERNAME:-$(id -un)} 7 | __uid=${NSS_UID:-$(id -u)} 8 | 9 | __groupname=${NSS_GROUPNAME:-$(id -gn)} 10 | __gid=${NSS_GID:-$(id -g)} 11 | 12 | echo "$__username:x:$__uid:$__uid:gecos:$HOME:/bin/bash" > $NSS_WRAPPER_PASSWD 13 | echo "$__groupname:x:$__gid:" > $NSS_WRAPPER_GROUP 14 | ) 15 | 16 | # wrap command 17 | export LD_PRELOAD=/usr/local/lib64/libnss_wrapper.so 18 | exec $@ 19 | -------------------------------------------------------------------------------- /config/detectors.ini: -------------------------------------------------------------------------------- 1 | # This file was copied from https://hg.python.org/tracker/python-dev/file/tip/config.ini.template 2 | 3 | #This configuration file controls the behavior of busybody.py and tellteam.py 4 | #The two definitions can be comma-delimited lists of email addresses. 5 | #Be sure these addresses will accept mail from the tracker's email address. 6 | [main] 7 | triage_email = triage@example.com 8 | busybody_email= busybody@example.com 9 | 10 | # URI to XMLRPC server doing the actual spam check. 11 | spambayes_uri = http://localhost/not_here 12 | # These must match the {ham,spam}_cutoff setting in the SpamBayes server 13 | # config. 14 | spambayes_ham_cutoff = 0.2 15 | spambayes_spam_cutoff = 0.85 16 | 17 | spambayes_may_view_spam = User,Coordinator,Developer 18 | spambayes_may_classify = Coordinator 19 | spambayes_may_report_misclassified = User,Coordinator,Developer 20 | 21 | ciavc_server = http://localhost/not_here 22 | 23 | [irker] 24 | channels = irc://chat.freenode.net/python-dev 25 | -------------------------------------------------------------------------------- /patroni/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tmpcfg="$(mktemp)" 4 | cat > "${tmpcfg}" <<__EOF__ 5 | bootstrap: 6 | dcs: 7 | postgresql: 8 | use_pg_rewind: true 9 | initdb: 10 | - auth-host: md5 11 | - auth-local: trust 12 | - encoding: UTF8 13 | - locale: en_US.UTF-8 14 | - data-checksums 15 | pg_hba: 16 | - host all all 0.0.0.0/0 md5 17 | - host replication ${PATRONI_REPLICATION_USERNAME} ${PATRONI_KUBERNETES_POD_IP}/16 md5 18 | restapi: 19 | connect_address: ${PATRONI_KUBERNETES_POD_IP}:8008 20 | postgresql: 21 | connect_address: ${PATRONI_KUBERNETES_POD_IP}:5432 22 | authentication: 23 | replication: 24 | username: ${PATRONI_REPLICATION_USERNAME} 25 | password: ${PATRONI_REPLICATION_PASSWORD} 26 | superuser: 27 | username: ${PATRONI_SUPERUSER_USERNAME} 28 | password: ${PATRONI_SUPERUSER_PASSWORD} 29 | __EOF__ 30 | 31 | unset PATRONI_SUPERUSER_PASSWORD PATRONI_REPLICATION_PASSWORD 32 | export KUBERNETES_NAMESPACE=$PATRONI_KUBERNETES_NAMESPACE 33 | export POD_NAME=$PATRONI_NAME 34 | 35 | exec /usr/bin/python /usr/local/bin/patroni "${tmpcfg}" 36 | -------------------------------------------------------------------------------- /patroni/callback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | import os 5 | import socket 6 | import sys 7 | import time 8 | 9 | from kubernetes import client as k8s_client, config as k8s_config 10 | from urllib3.exceptions import HTTPError 11 | from six.moves.http_client import HTTPException 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class CoreV1Api(k8s_client.CoreV1Api): 17 | 18 | def retry(func): 19 | def wrapped(*args, **kwargs): 20 | count = 0 21 | while True: 22 | try: 23 | return func(*args, **kwargs) 24 | except (HTTPException, HTTPError, socket.error, socket.timeout): 25 | if count >= 10: 26 | raise 27 | logger.info('Throttling API requests...') 28 | time.sleep(2 ** count * 0.5) 29 | count += 1 30 | return wrapped 31 | 32 | @retry 33 | def patch_namespaced_endpoints(self, *args, **kwargs): 34 | return super(CoreV1Api, self).patch_namespaced_endpoints(*args, **kwargs) 35 | 36 | 37 | def patch_master_endpoint(api, namespace, cluster): 38 | addresses = [k8s_client.V1EndpointAddress(ip=os.environ['POD_IP'])] 39 | ports = [k8s_client.V1EndpointPort(port=5432)] 40 | subsets = [k8s_client.V1EndpointSubset(addresses=addresses, ports=ports)] 41 | body = k8s_client.V1Endpoints(subsets=subsets) 42 | return api.patch_namespaced_endpoints(cluster, namespace, body) 43 | 44 | 45 | def main(): 46 | logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO) 47 | if len(sys.argv) != 4 or sys.argv[1] not in ('on_start', 'on_stop', 'on_role_change'): 48 | sys.exit('Usage: %s ', sys.argv[0]) 49 | 50 | action, role, cluster = sys.argv[1:4] 51 | 52 | k8s_config.load_incluster_config() 53 | k8s_api = CoreV1Api() 54 | 55 | namespace = os.environ['KUBERNETES_NAMESPACE'] 56 | 57 | if role == 'master' and action in ('on_start', 'on_role_change'): 58 | patch_master_endpoint(k8s_api, namespace, cluster) 59 | 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /patroni/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:10 2 | 3 | RUN export DEBIAN_FRONTEND=noninteractive \ 4 | && echo 'APT::Install-Recommends "0";\nAPT::Install-Suggests "0";' > /etc/apt/apt.conf.d/01norecommend \ 5 | && apt-get update -y \ 6 | && apt-get upgrade -y \ 7 | && apt-get install -y git curl jq python-psycopg2 python-yaml python-requests \ 8 | python-six python-dateutil python-pip python-setuptools python-prettytable \ 9 | python-wheel python-psutil python locales openssh-client \ 10 | gcc build-essential cmake \ 11 | 12 | # Make sure we have a en_US.UTF-8 locale available 13 | && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 \ 14 | 15 | # Install patched version of patroni 16 | && pip install 'git+https://github.com/soltysh/patroni.git@openshift#egg=patroni[kubernetes]' \ 17 | 18 | # build nss_wrapper 19 | && git clone git://git.samba.org/nss_wrapper.git /tmp/nss_wrapper \ 20 | && mkdir /tmp/nss_wrapper/build \ 21 | && cd /tmp/nss_wrapper/build \ 22 | && cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DLIB_SUFFIX=64 .. \ 23 | && make \ 24 | && make install \ 25 | && rm -rf /tmp/nss_wrapper 26 | 27 | # This image exposes port 5432 which is default postgresql port and 8008 which 28 | # is used by the patroni 29 | EXPOSE 5432 8008 30 | 31 | ENV LC_ALL=en_US.UTF-8 \ 32 | LANG=en_US.UTF-8 \ 33 | NSS_WRAPPER_PASSWD=/tmp/passwd \ 34 | NSS_WRAPPER_GROUP=/tmp/group 35 | 36 | COPY entrypoint.sh callback.py nss-wrap.sh / 37 | 38 | RUN \ 39 | # create postgresql data directory 40 | mkdir -p /opt/pgdata \ 41 | 42 | # Setup nss wrapper files 43 | && for path in "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP"; do \ 44 | touch $path && chmod 666 $path ; done \ 45 | 46 | # Clean up 47 | && apt-get remove -y git python-pip python-setuptools git gcc build-essential cmake \ 48 | && apt-get autoremove -y \ 49 | && apt-get clean -y \ 50 | && rm -rf /var/lib/apt/lists/* /root/.cache \ 51 | 52 | # In order to drop the root user, we have to make some directories world 53 | # writable as OpenShift default security model is to run the container under 54 | # random UID. 55 | && chown -R 1001:0 /opt/pgdata && chmod -R og+rwx /opt/pgdata 56 | 57 | USER 1001 58 | 59 | ENTRYPOINT ["/nss-wrap.sh"] 60 | CMD ["/entrypoint.sh"] 61 | -------------------------------------------------------------------------------- /template_bpo.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # build configuration 3 | # 4 | kind: BuildConfig 5 | apiVersion: v1 6 | metadata: 7 | name: bpo 8 | labels: 9 | name: bpo 10 | spec: 11 | source: 12 | type: Git 13 | git: 14 | uri: https://github.com/python/bpo-builder.git 15 | strategy: 16 | type: Source 17 | sourceStrategy: 18 | from: 19 | kind: ImageStreamTag 20 | name: bpo-builder:latest 21 | output: 22 | to: 23 | kind: ImageStreamTag 24 | name: bpo:latest 25 | 26 | --- 27 | # 28 | # builder image stream configuration 29 | # 30 | kind: ImageStream 31 | apiVersion: v1 32 | metadata: 33 | name: bpo-builder 34 | spec: 35 | tags: 36 | - name: latest 37 | from: 38 | kind: DockerImage 39 | name: python/bpo-builder:latest 40 | 41 | --- 42 | # 43 | # output image stream configuration 44 | # 45 | kind: ImageStream 46 | apiVersion: v1 47 | metadata: 48 | name: bpo 49 | 50 | --- 51 | # 52 | # deployment configuration 53 | # 54 | kind: DeploymentConfig 55 | apiVersion: v1 56 | metadata: 57 | name: bpo 58 | spec: 59 | strategy: 60 | type: Rolling 61 | triggers: 62 | - type: ImageChange 63 | imageChangeParams: 64 | automatic: true 65 | containerNames: 66 | - bpo 67 | from: 68 | kind: ImageStreamTag 69 | name: bpo:latest 70 | - type: ConfigChange 71 | replicas: 1 72 | revisionHistoryLimit: 1 73 | selector: 74 | name: bpo 75 | template: 76 | metadata: 77 | labels: 78 | name: bpo 79 | spec: 80 | containers: 81 | - name: bpo 82 | image: bpo 83 | ports: 84 | - containerPort: 9999 85 | protocol: TCP 86 | readinessProbe: 87 | httpGet: 88 | path: /python-dev/ 89 | port: 9999 90 | scheme: HTTP 91 | livenessProbe: 92 | tcpSocket: 93 | port: 9999 94 | volumeMounts: 95 | - name: config 96 | mountPath: "/opt/tracker/config" 97 | readOnly: true 98 | restartPolicy: Always 99 | volumes: 100 | - name: config 101 | secret: 102 | secretName: config 103 | items: 104 | - key: roundup 105 | path: config.ini 106 | - key: detectors 107 | path: detectors/config.ini 108 | 109 | --- 110 | # 111 | # service configuration 112 | # 113 | kind: Service 114 | apiVersion: v1 115 | metadata: 116 | name: bpo 117 | spec: 118 | ports: 119 | - name: web 120 | protocol: TCP 121 | port: 9999 122 | targetPort: 9999 123 | selector: 124 | name: bpo 125 | 126 | --- 127 | # 128 | # route configuration 129 | # 130 | kind: Route 131 | apiVersion: v1 132 | metadata: 133 | name: bpo 134 | spec: 135 | to: 136 | kind: Service 137 | name: bpo 138 | -------------------------------------------------------------------------------- /template_patroni.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # stateful set 3 | # 4 | kind: StatefulSet 5 | apiVersion: apps/v1 6 | metadata: 7 | name: &cluster_name patroni 8 | labels: 9 | application: patroni 10 | cluster-name: *cluster_name 11 | spec: 12 | replicas: 3 13 | selector: 14 | matchLabels: 15 | application: patroni 16 | cluster-name: *cluster_name 17 | serviceName: *cluster_name 18 | template: 19 | metadata: 20 | labels: 21 | application: patroni 22 | cluster-name: *cluster_name 23 | spec: 24 | serviceAccountName: patroni 25 | containers: 26 | - name: *cluster_name 27 | image: docker.io/python/bpo-patroni 28 | # resources: 29 | # limits: 30 | # cpu: 100m 31 | ports: 32 | - containerPort: 8008 33 | protocol: TCP 34 | - containerPort: 5432 35 | protocol: TCP 36 | volumeMounts: 37 | - mountPath: /opt/pgdata 38 | name: pgdata 39 | env: 40 | - name: PATRONI_KUBERNETES_POD_IP 41 | valueFrom: 42 | fieldRef: 43 | fieldPath: status.podIP 44 | - name: PATRONI_KUBERNETES_NAMESPACE 45 | valueFrom: 46 | fieldRef: 47 | fieldPath: metadata.namespace 48 | - name: PATRONI_KUBERNETES_LABELS 49 | value: '{application: patroni, cluster-name: patroni}' 50 | - name: PATRONI_SUPERUSER_USERNAME 51 | value: postgres 52 | - name: PATRONI_SUPERUSER_PASSWORD 53 | valueFrom: 54 | secretKeyRef: 55 | name: *cluster_name 56 | key: superuser-password 57 | - name: PATRONI_REPLICATION_USERNAME 58 | value: standby 59 | - name: PATRONI_REPLICATION_PASSWORD 60 | valueFrom: 61 | secretKeyRef: 62 | name: *cluster_name 63 | key: replication-password 64 | - name: PATRONI_SCOPE 65 | value: *cluster_name 66 | - name: PATRONI_NAME 67 | valueFrom: 68 | fieldRef: 69 | fieldPath: metadata.name 70 | - name: PATRONI_POSTGRESQL_DATA_DIR 71 | value: /opt/pgdata/pgroot/data 72 | - name: PATRONI_POSTGRESQL_PGPASS 73 | value: /tmp/pgpass 74 | - name: PATRONI_POSTGRESQL_LISTEN 75 | value: '0.0.0.0:5432' 76 | - name: PATRONI_RESTAPI_LISTEN 77 | value: '0.0.0.0:8008' 78 | terminationGracePeriodSeconds: 0 79 | volumeClaimTemplates: 80 | - metadata: 81 | labels: 82 | application: patroni 83 | cluster-name: *cluster_name 84 | name: pgdata 85 | spec: 86 | accessModes: 87 | - ReadWriteOnce 88 | resources: 89 | requests: 90 | storage: 20Gi 91 | 92 | --- 93 | # 94 | # master service 95 | # 96 | kind: Service 97 | apiVersion: v1 98 | metadata: 99 | name: &cluster_name patroni 100 | labels: 101 | application: patroni 102 | cluster-name: *cluster_name 103 | spec: 104 | type: ClusterIP 105 | ports: 106 | - port: 5432 107 | targetPort: 5432 108 | 109 | --- 110 | # 111 | # replica service 112 | # 113 | kind: Service 114 | apiVersion: v1 115 | metadata: 116 | name: patroni-repl 117 | labels: 118 | application: patroni 119 | cluster-name: &cluster_name patroni 120 | spec: 121 | type: ClusterIP 122 | selector: 123 | application: patroni 124 | cluster-name: *cluster_name 125 | role: replica 126 | ports: 127 | - port: 5432 128 | targetPort: 5432 129 | 130 | --- 131 | # 132 | # secrets (superuser password and replication passwor) 133 | # 134 | kind: Secret 135 | apiVersion: v1 136 | metadata: 137 | name: &cluster_name patroni 138 | labels: 139 | application: patroni 140 | cluster-name: *cluster_name 141 | type: Opaque 142 | data: 143 | superuser-password: Y2hhbmdlbWUK 144 | replication-password: Y2hhbmdlbWUK 145 | 146 | --- 147 | # 148 | # dedicated service account 149 | # 150 | kind: ServiceAccount 151 | apiVersion: v1 152 | metadata: 153 | name: patroni 154 | 155 | --- 156 | # 157 | # dedicated role 158 | # 159 | kind: Role 160 | apiVersion: rbac.authorization.k8s.io/v1 161 | metadata: 162 | name: patroni 163 | rules: 164 | - apiGroups: 165 | - "" 166 | resources: 167 | - configmaps 168 | verbs: 169 | - create 170 | - get 171 | - list 172 | - patch 173 | - update 174 | - watch 175 | - apiGroups: 176 | - "" 177 | resources: 178 | - endpoints 179 | verbs: 180 | - get 181 | - patch 182 | - update 183 | # the following three privileges are necessary only when using endpoints 184 | - create 185 | - list 186 | - watch 187 | - apiGroups: 188 | - "" 189 | resources: 190 | - pods 191 | verbs: 192 | - get 193 | - list 194 | - patch 195 | - update 196 | - watch 197 | 198 | --- 199 | # 200 | # bind dedicated role to the service account 201 | # 202 | kind: RoleBinding 203 | apiVersion: rbac.authorization.k8s.io/v1 204 | metadata: 205 | name: patroni 206 | roleRef: 207 | apiGroup: rbac.authorization.k8s.io 208 | kind: Role 209 | name: patroni 210 | subjects: 211 | - kind: ServiceAccount 212 | name: patroni 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ------ 3 | This repository contains the code allowing to deploy http://bugs.python.org 4 | on [OpenShift](https://www.openshift.org/). 5 | 6 | 7 | Usage 8 | ----- 9 | 1. Download latest [OpenShift client](https://github.com/openshift/origin/releases) 10 | and run `oc cluster up` to setup a local cluster. If you encounter any problems 11 | follow the diagnostic messages that appear on the screen, it is probably missing 12 | packages (eg. docker) or necessary configuration changes. 13 | 14 | 2. Instantiate a postgresql instance. Depending on your needs there are two possibilies 15 | here. You can either proceed with a single development instance (A) or a full HA 16 | production one (B). 17 | 18 | A. To deploy single development instance you can either use the web console or 19 | the following command. With the former make sure to use the exact same values 20 | as below command. 21 | 22 | ``` 23 | oc new-app postgresql:9.5 \ 24 | --name=bpo-db \ 25 | --labels=app=bugs.python.org \ 26 | --env=POSTGRESQL_USER=roundup \ 27 | --env=POSTGRESQL_PASSWORD=roundup \ 28 | --env=POSTGRESQL_DATABASE=roundup 29 | ``` 30 | 31 | This will create the following resources: 32 | - [Deployment Configuration](https://docs.openshift.org/latest/dev_guide/deployments/how_deployments_work.html) 33 | - [Service](https://docs.openshift.org/latest/architecture/core_concepts/pods_and_services.html#services) 34 | 35 | This deployment configuration will kick of an actual deployment of our postgresql 36 | instance which leads to creating a [Replication Controller](https://docs.openshift.org/latest/architecture/core_concepts/deployments.html#replication-controllers) 37 | and a [Pod](https://docs.openshift.org/latest/architecture/core_concepts/pods_and_services.html#pods). 38 | 39 | **NOTE:** This setup uses an ephemeral storage, if you want to save your data you 40 | should read about [Persistence Volumes](https://docs.openshift.org/latest/dev_guide/persistent_volumes.html). 41 | 42 | When the postgresql instance is up we need to drop the database and allow roundup 43 | initialize it from scratch. To do so invoke the following commands, which will 44 | get you connected to bpo-db pod and drop the database and add necessary access 45 | rights to create a new one, instead: 46 | 47 | ``` 48 | oc rsh $(oc get pod -l deploymentconfig=bpo-db -o jsonpath='{.items[*].metadata.name}') 49 | # psql 50 | # drop database roundup; 51 | # alter user roundup createdb; 52 | ``` 53 | 54 | B. To deploy full HA PostgreSQL using [patroni project](https://github.com/zalando/patroni/) 55 | invoke the following command: 56 | 57 | ``` 58 | oc create -f \ 59 | https://raw.githubusercontent.com/python/bpo-builder/master/template_patroni.yaml 60 | ``` 61 | 62 | This will create the following resources: 63 | - [Stateful Set](https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/) 64 | - [Services](https://docs.openshift.org/latest/architecture/core_concepts/pods_and_services.html#services) 65 | - [Service Account](https://docs.openshift.org/latest/dev_guide/service_accounts.html) 66 | - [Role and RoleBinding](https://kubernetes.io/docs/admin/authorization/rbac/) 67 | 68 | **NOTE:** You should copy the above template file and change `superuser-password` 69 | and `replication-password`. These are `base64` encoded passwords. 70 | 71 | When the postgresql instance is up we need to create user roundup with appropriate 72 | password and add it rights to create a database. 73 | 74 | ``` 75 | oc rsh patroni-0 76 | # psql -U postgres 77 | # create user roundup with createdb encrypted password 'changeme'; 78 | ``` 79 | 80 | 3. Now it is time to prepare all the bits necessary to deploy bugs.python.org itself: 81 | 82 | ``` 83 | oc create -f \ 84 | https://raw.githubusercontent.com/python/bpo-builder/master/template_bpo.yaml 85 | ``` 86 | 87 | This will create the following resources: 88 | - [Build Configuration](https://docs.openshift.org/latest/dev_guide/builds/index.html) 89 | - [Image Stream](https://docs.openshift.org/latest/dev_guide/managing_images.html) 90 | - [Deployment Configuration](https://docs.openshift.org/latest/dev_guide/deployments/how_deployments_work.html) 91 | - [Service](https://docs.openshift.org/latest/architecture/core_concepts/pods_and_services.html#services) 92 | - [Route](https://docs.openshift.org/latest/dev_guide/routes.html) 93 | 94 | **NOTE:** This needs to be performed only when you're using a temporary database. 95 | 96 | Since we need to initiate the database only once, we need to set an environment 97 | variable (`INIT_DATABASE`), to tell the `run` script to do it: 98 | 99 | ``` 100 | oc set env deploymentconfig/bpo INIT_DATABASE=true 101 | ``` 102 | 103 | After the initial rollout this value should be cleared out: 104 | 105 | ``` 106 | oc set env deploymentconfig/bpo INIT_DATABASE- 107 | ``` 108 | 109 | 4. Edit `config/roundup.ini` and change the line: 110 | 111 | ``` 112 | web = http://localhost:9999/python-dev/ 113 | ``` 114 | 115 | So that it matches the route the app will be exposed under. You can easily check 116 | that with `oc get route/bpo`. Afterwards you can create the necessary configuration: 117 | 118 | ``` 119 | oc create secret generic config \ 120 | --from-file=roundup=config/roundup.ini \ 121 | --from-file=detectors=config/detectors.ini 122 | ``` 123 | 124 | 5. With all the pieces in place we can finally start the application. To do so 125 | we need to build the actual image that will serve bugs.python.org: 126 | 127 | ``` 128 | oc start-build bpo 129 | ``` 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Python Software Foundation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /config/roundup.ini: -------------------------------------------------------------------------------- 1 | # This file was copied from https://hg.python.org/tracker/python-dev/file/tip/detectors/config.ini.template 2 | 3 | # Roundup issue tracker configuration file 4 | # Autogenerated at Fri Nov 17 16:59:49 2006 5 | 6 | # WARNING! Following options need adjustments: 7 | # [mail]: domain, host 8 | # [tracker]: web 9 | 10 | [main] 11 | 12 | # Database directory path. 13 | # The path may be either absolute or relative 14 | # to the directory containig this config file. 15 | # Default: db 16 | database = db 17 | 18 | # Path to the HTML templates directory. 19 | # The path may be either absolute or relative 20 | # to the directory containig this config file. 21 | # Default: html 22 | templates = html 23 | 24 | # Path to directory holding additional static files 25 | # available via Web UI. This directory may contain 26 | # sitewide images, CSS stylesheets etc. and is searched 27 | # for these files prior to the TEMPLATES directory 28 | # specified above. If this option is not set, all static 29 | # files are taken from the TEMPLATES directory 30 | # The path may be either absolute or relative 31 | # to the directory containig this config file. 32 | # Default: 33 | static_files = 34 | 35 | # Email address that roundup will complain to if it runs into trouble. 36 | # Default: roundup-admin 37 | admin_email = roundup-admin 38 | 39 | # The 'dispatcher' is a role that can get notified 40 | # of new items to the database. 41 | # It is used by the ERROR_MESSAGES_TO config setting. 42 | # Default: roundup-admin 43 | dispatcher_email = roundup-admin 44 | 45 | # Additional text to include in the "name" part 46 | # of the From: address used in nosy messages. 47 | # If the sending user is "Foo Bar", the From: line 48 | # is usually: "Foo Bar" 49 | # the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so: 50 | # "Foo Bar EMAIL_FROM_TAG" 51 | # Default: 52 | email_from_tag = 53 | 54 | # Roles that a user gets when they register with Web User Interface. 55 | # This is a comma-separated string of role names (e.g. 'Admin,User'). 56 | # Default: User 57 | new_web_user_roles = User 58 | 59 | # Roles that a user gets when they register with Email Gateway. 60 | # This is a comma-separated string of role names (e.g. 'Admin,User'). 61 | # Default: User 62 | new_email_user_roles = User 63 | 64 | # Send error message emails to the dispatcher, user, or both? 65 | # The dispatcher is configured using the DISPATCHER_EMAIL setting. 66 | # Default: user 67 | error_messages_to = user 68 | 69 | # HTML version to generate. The templates are html4 by default. 70 | # If you wish to make them xhtml, then you'll need to change this 71 | # var to 'xhtml' too so all auto-generated HTML is compliant. 72 | # Allowed values: html4, xhtml 73 | # Default: html4 74 | html_version = xhtml 75 | 76 | # Default timezone offset, applied when user's timezone is not set. 77 | # If pytz module is installed, value may be any valid 78 | # timezone specification (e.g. EET or Europe/Warsaw). 79 | # If pytz is not installed, value must be integer number 80 | # giving local timezone offset from UTC in hours. 81 | # Default: UTC 82 | timezone = UTC 83 | 84 | # Register new users instantly, or require confirmation via 85 | # email? 86 | # Allowed values: yes, no 87 | # Default: no 88 | instant_registration = no 89 | 90 | # Offer registration confirmation by email or only through the web? 91 | # Allowed values: yes, no 92 | # Default: yes 93 | email_registration_confirmation = yes 94 | 95 | # Force Roundup to use a particular text indexer. 96 | # If no indexer is supplied, the first available indexer 97 | # will be used in the following order: 98 | # Possible values: xapian, whoosh, native (internal). 99 | # Default: 100 | indexer = native 101 | 102 | # Additional stop-words for the full-text indexer specific to 103 | # your tracker. See the indexer source for the default list of 104 | # stop-words (eg. A,AND,ARE,AS,AT,BE,BUT,BY, ...) 105 | # Allowed values: comma-separated list of words 106 | # Default: 107 | indexer_stopwords = 108 | 109 | # Defines the file creation mode mask. 110 | # Default: 02 111 | umask = 02 112 | 113 | [tracker] 114 | 115 | # A descriptive name for your roundup instance. 116 | # Default: Roundup issue tracker 117 | name = Tracker 118 | 119 | # The web address that the tracker is viewable at. 120 | # This will be included in information sent to users of the tracker. 121 | # The URL MUST include the cgi-bin part or anything else 122 | # that is required to get to the home page of the tracker. 123 | # You MUST include a trailing '/' in the URL. 124 | # Default: NO DEFAULT 125 | #web = NO DEFAULT 126 | #web = http://psf.upfronthosting.co.za/roundup/tracker/ 127 | web = http://localhost:9999/python-dev/ 128 | 129 | # Email address that mail to roundup should go to. 130 | # Default: issue_tracker 131 | email = issue_tracker 132 | 133 | # Default locale name for this tracker. 134 | # If this option is not set, the language is determined 135 | # by OS environment variable LANGUAGE, LC_ALL, LC_MESSAGES, 136 | # or LANG, in that order of preference. 137 | # Default: 138 | language = 139 | 140 | [web] 141 | 142 | # Whether to use HTTP Basic Authentication, if present. 143 | # Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION 144 | # variables supplied by your web server (in that order). 145 | # Set this option to 'no' if you do not wish to use HTTP Basic 146 | # Authentication in your web interface. 147 | # Allowed values: yes, no 148 | # Default: yes 149 | http_auth = yes 150 | 151 | # Whether to use HTTP Accept-Language, if present. 152 | # Browsers send a language-region preference list. 153 | # It's usually set in the client's browser or in their 154 | # Operating System. 155 | # Set this option to 'no' if you want to ignore it. 156 | # Allowed values: yes, no 157 | # Default: yes 158 | use_browser_language = no 159 | 160 | # Setting this option makes Roundup display error tracebacks 161 | # in the user's browser rather than emailing them to the 162 | # tracker admin. 163 | # Allowed values: yes, no 164 | # Default: no 165 | debug = yes 166 | 167 | # Settings in this section are used by Postgresql and MySQL backends only 168 | [rdbms] 169 | 170 | # Database backend. 171 | # Default: 172 | backend = postgresql 173 | 174 | # Name of the database to use. 175 | # Default: roundup 176 | name = roundup 177 | 178 | # Database server host. 179 | # Default: localhost 180 | host = bpo-db 181 | 182 | # TCP port number of the database server. 183 | # Postgresql usually resides on port 5432 (if any), 184 | # for MySQL default port number is 3306. 185 | # Leave this option empty to use backend default 186 | # Default: 187 | port = 188 | 189 | # Database user name that Roundup should use. 190 | # Default: roundup 191 | user = roundup 192 | 193 | # Database user password. 194 | # Default: roundup 195 | password = roundup 196 | 197 | # Name of the MySQL defaults file. 198 | # Only used in MySQL connections. 199 | # Default: ~/.my.cnf 200 | read_default_file = ~/.my.cnf 201 | 202 | # Name of the group to use in the MySQL defaults file (.my.cnf). 203 | # Only used in MySQL connections. 204 | # Default: roundup 205 | read_default_group = roundup 206 | 207 | [logging] 208 | 209 | # Path to configuration file for standard Python logging module. 210 | # If this option is set, logging configuration is loaded 211 | # from specified file; options 'filename' and 'level' 212 | # in this section are ignored. 213 | # The path may be either absolute or relative 214 | # to the directory containig this config file. 215 | # Default: 216 | config = 217 | 218 | # Log file name for minimal logging facility built into Roundup. 219 | # If no file name specified, log messages are written on stderr. 220 | # If above 'config' option is set, this option has no effect. 221 | # The path may be either absolute or relative 222 | # to the directory containig this config file. 223 | # Default: 224 | filename = 225 | 226 | # Minimal severity level of messages written to log file. 227 | # If above 'config' option is set, this option has no effect. 228 | # Allowed values: DEBUG, INFO, WARNING, ERROR 229 | # Default: ERROR 230 | level = ERROR 231 | 232 | # Outgoing email options. 233 | # Used for nozy messages and approval requests 234 | [mail] 235 | 236 | # Domain name used for email addresses. 237 | # Default: NO DEFAULT 238 | domain = NODEFAULT 239 | 240 | # SMTP mail host that roundup will use to send mail 241 | # Default: NO DEFAULT 242 | #host = NO DEFAULT 243 | host = localhost 244 | 245 | # SMTP login name. 246 | # Set this if your mail host requires authenticated access. 247 | # If username is not empty, password (below) MUST be set! 248 | # Default: 249 | username = 250 | 251 | # SMTP login password. 252 | # Set this if your mail host requires authenticated access. 253 | # Default: NO DEFAULT 254 | #password = NO DEFAULT 255 | 256 | # If your SMTP mail host provides or requires TLS 257 | # (Transport Layer Security) then set this option to 'yes'. 258 | # Allowed values: yes, no 259 | # Default: no 260 | tls = no 261 | 262 | # If TLS is used, you may set this option to the name 263 | # of a PEM formatted file that contains your private key. 264 | # The path may be either absolute or relative 265 | # to the directory containig this config file. 266 | # Default: 267 | tls_keyfile = 268 | 269 | # If TLS is used, you may set this option to the name 270 | # of a PEM formatted certificate chain file. 271 | # The path may be either absolute or relative 272 | # to the directory containig this config file. 273 | # Default: 274 | tls_certfile = 275 | 276 | # Character set to encode email headers with. 277 | # We use utf-8 by default, as it's the most flexible. 278 | # Some mail readers (eg. Eudora) can't cope with that, 279 | # so you might need to specify a more limited character set 280 | # (eg. iso-8859-1). 281 | # Default: utf-8 282 | charset = utf-8 283 | 284 | # Setting this option makes Roundup to write all outgoing email 285 | # messages to this file *instead* of sending them. 286 | # This option has the same effect as environment variable SENDMAILDEBUG. 287 | # Environment variable takes precedence. 288 | # The path may be either absolute or relative 289 | # to the directory containig this config file. 290 | # Default: 291 | debug = debugmail.txt 292 | 293 | # Roundup Mail Gateway options 294 | [mailgw] 295 | 296 | # Keep email citations when accepting messages. 297 | # Setting this to "no" strips out "quoted" text from the message. 298 | # Signatures are also stripped. 299 | # Allowed values: yes, no 300 | # Default: yes 301 | keep_quoted_text = yes 302 | 303 | # Preserve the email body as is - that is, 304 | # keep the citations _and_ signatures. 305 | # Allowed values: yes, no 306 | # Default: no 307 | leave_body_unchanged = no 308 | 309 | # Default class to use in the mailgw 310 | # if one isn't supplied in email subjects. 311 | # To disable, leave the value blank. 312 | # Default: issue 313 | default_class = issue 314 | 315 | # Default locale name for the tracker mail gateway. 316 | # If this option is not set, mail gateway will use 317 | # the language of the tracker instance. 318 | # Default: 319 | language = 320 | 321 | # Controls the parsing of the [prefix] on subject 322 | # lines in incoming emails. "strict" will return an 323 | # error to the sender if the [prefix] is not recognised. 324 | # "loose" will attempt to parse the [prefix] but just 325 | # pass it through as part of the issue title if not 326 | # recognised. "none" will always pass any [prefix] 327 | # through as part of the issue title. 328 | # Default: strict 329 | subject_prefix_parsing = strict 330 | 331 | # Controls the parsing of the [suffix] on subject 332 | # lines in incoming emails. "strict" will return an 333 | # error to the sender if the [suffix] is not recognised. 334 | # "loose" will attempt to parse the [suffix] but just 335 | # pass it through as part of the issue title if not 336 | # recognised. "none" will always pass any [suffix] 337 | # through as part of the issue title. 338 | # Default: strict 339 | subject_suffix_parsing = strict 340 | 341 | # Defines the brackets used for delimiting the prefix and 342 | # suffix in a subject line. The presence of "suffix" in 343 | # the config option name is a historical artifact and may 344 | # be ignored. 345 | # Default: [] 346 | subject_suffix_delimiters = [] 347 | 348 | # Controls matching of the incoming email subject line 349 | # against issue titles in the case where there is no 350 | # designator [prefix]. "never" turns off matching. 351 | # "creation + interval" or "activity + interval" 352 | # will match an issue for the interval after the issue's 353 | # creation or last activity. The interval is a standard 354 | # Roundup interval. 355 | # Default: always 356 | subject_content_match = always 357 | 358 | # Nosy messages sending 359 | [nosy] 360 | 361 | # Send nosy messages to the author of the message. 362 | # Allowed values: yes, no, new 363 | # Default: no 364 | messages_to_author = yes 365 | 366 | # Where to place the email signature. 367 | # Allowed values: top, bottom, none 368 | # Default: bottom 369 | signature_position = bottom 370 | 371 | # Does the author of a message get placed on the nosy list 372 | # automatically? If 'new' is used, then the author will 373 | # only be added when a message creates a new issue. 374 | # If 'yes', then the author will be added on followups too. 375 | # If 'no', they're never added to the nosy. 376 | # 377 | # Allowed values: yes, no, new 378 | # Default: new 379 | add_author = yes 380 | 381 | # Do the recipients (To:, Cc:) of a message get placed on the 382 | # nosy list? If 'new' is used, then the recipients will 383 | # only be added when a message creates a new issue. 384 | # If 'yes', then the recipients will be added on followups too. 385 | # If 'no', they're never added to the nosy. 386 | # 387 | # Allowed values: yes, no, new 388 | # Default: new 389 | add_recipients = new 390 | 391 | # Controls the email sending from the nosy reactor. If 392 | # "multiple" then a separate email is sent to each 393 | # recipient. If "single" then a single email is sent with 394 | # each recipient as a CC address. 395 | # Default: single 396 | email_sending = multiple 397 | 398 | [django] 399 | # generate on a per-instance basis 400 | secret_key = 'el@4s$*(idwm5-87teftxlksckmy8$tyo7(tm!n-5x)zeuheex' 401 | --------------------------------------------------------------------------------