aboutsummaryrefslogtreecommitdiffstats
path: root/deployment
diff options
context:
space:
mode:
Diffstat (limited to 'deployment')
-rw-r--r--deployment/access_classes/manifests/admin.pp6
-rw-r--r--deployment/access_classes/manifests/committers.pp4
-rw-r--r--deployment/access_classes/manifests/iso_makers.pp4
-rw-r--r--deployment/access_classes/manifests/web.pp4
-rw-r--r--deployment/access_classes/manifests/web_and_artwork.pp5
-rw-r--r--deployment/backups/manifests/init.pp35
-rw-r--r--deployment/common/manifests/base_packages.pp6
-rw-r--r--deployment/common/manifests/default_ssh_root_key.pp65
-rw-r--r--deployment/common/manifests/i18n.pp6
-rw-r--r--deployment/common/manifests/init.pp6
-rw-r--r--deployment/common/manifests/sudo_sysadmin.pp7
-rw-r--r--deployment/common/templates/locale.conf20
-rw-r--r--deployment/common/templates/sudoers.sysadmin1
-rw-r--r--deployment/dns/manifests/reverse_zone.pp2
-rw-r--r--deployment/dns/manifests/zone.pp2
-rw-r--r--deployment/dns/templates/2.1.0.0.0.0.0.1.b.0.e.0.1.0.a.2.ip6.arpa.zone6
-rw-r--r--deployment/dns/templates/7.0.0.0.2.0.0.0.8.7.1.2.2.0.a.2.ip6.arpa.zone10
-rw-r--r--deployment/dns/templates/mageia.fr.zone27
-rw-r--r--deployment/dns/templates/mageia.org.zone260
-rw-r--r--deployment/forums/manifests/init.pp8
-rwxr-xr-x[-rw-r--r--]deployment/lists/manifests/init.pp358
-rw-r--r--deployment/main_mirror/files/mirror/mirror.readme29
-rw-r--r--deployment/main_mirror/manifests/init.pp10
-rw-r--r--deployment/main_mirror/templates/rsyncd.conf59
-rw-r--r--deployment/mga_buildsystem/manifests/buildnode.pp4
-rw-r--r--deployment/mga_buildsystem/manifests/config.pp668
-rw-r--r--deployment/mga_buildsystem/manifests/mainnode.pp21
-rw-r--r--deployment/mgagit/files/git_multimail.py4383
-rw-r--r--deployment/mgagit/manifests/init.pp170
-rw-r--r--deployment/mgagit/manifests/tmpl.pp9
-rwxr-xr-xdeployment/mgagit/templates/git-post-receive-hook314
-rw-r--r--deployment/mgagit/templates/git-post-update-hook12
-rw-r--r--deployment/mgagit/templates/gitolite.rc161
-rw-r--r--deployment/mgagit/templates/group_owned_repo.gl36
-rw-r--r--deployment/mgagit/templates/mgagit.conf57
-rw-r--r--deployment/mgagit/templates/repodef_repo.gl8
-rw-r--r--deployment/releasekey/manifests/init.pp10
-rw-r--r--deployment/releasekey/templates/sign_checksums4
-rw-r--r--deployment/reports/templates/socket.yaml2
-rw-r--r--deployment/repositories/manifests/git.pp11
-rw-r--r--deployment/repositories/manifests/git_mirror.pp22
-rw-r--r--deployment/repositories/manifests/sparkleshare.pp11
-rw-r--r--deployment/repositories/manifests/subversion.pp64
-rw-r--r--deployment/repositories/manifests/svn_mirror.pp12
-rw-r--r--deployment/repositories/templates/puppet_update.sudoers1
-rw-r--r--deployment/shadow/files/login.defs193
-rw-r--r--deployment/shadow/manifests/init.pp19
-rw-r--r--deployment/softwarekey/manifests/init.pp9
-rw-r--r--deployment/tld_redirections/manifests/init.pp14
-rw-r--r--deployment/websites/manifests/archives.pp20
-rw-r--r--deployment/websites/manifests/base.pp6
-rw-r--r--deployment/websites/manifests/doc.pp12
-rw-r--r--deployment/websites/manifests/forum_proxy.pp8
-rw-r--r--deployment/websites/manifests/git.pp10
-rw-r--r--deployment/websites/manifests/hugs.pp10
-rw-r--r--deployment/websites/manifests/meetbot.pp14
-rw-r--r--deployment/websites/manifests/nav.pp19
-rw-r--r--deployment/websites/manifests/perl.pp32
-rw-r--r--deployment/websites/manifests/releases.pp12
-rw-r--r--deployment/websites/manifests/start.pp9
-rw-r--r--deployment/websites/manifests/static.pp8
-rw-r--r--deployment/websites/manifests/svn.pp9
-rw-r--r--deployment/websites/manifests/www.pp51
-rw-r--r--deployment/websites/templates/vhost_meetbot.conf36
-rw-r--r--deployment/websites/templates/vhost_proxy_mailman.conf14
-rw-r--r--deployment/websites/templates/vhost_proxy_mailman_ssl.conf16
-rw-r--r--deployment/websites/templates/vhost_static.conf59
-rw-r--r--deployment/websites/templates/vhost_www.conf12
-rw-r--r--deployment/websites/templates/vhost_www_rewrite.conf22
-rw-r--r--deployment/wikis/manifests/init.pp14
-rw-r--r--deployment/wikis/templates/wiki_settings15
-rw-r--r--deployment/wikis/templates/wiki_vhost.conf11
72 files changed, 6813 insertions, 761 deletions
diff --git a/deployment/access_classes/manifests/admin.pp b/deployment/access_classes/manifests/admin.pp
index 4b9c8f87..186c9c87 100644
--- a/deployment/access_classes/manifests/admin.pp
+++ b/deployment/access_classes/manifests/admin.pp
@@ -1,7 +1,7 @@
-# for server where only admins can connect
+# for server where only admins can connect (allowed by default)
class access_classes::admin {
- pam::multiple_ldap_access { 'admin':
- access_classes => ['mga-sysadmin']
+ class { 'pam::multiple_ldap_access':
+ access_classes => []
}
}
diff --git a/deployment/access_classes/manifests/committers.pp b/deployment/access_classes/manifests/committers.pp
index 81dbdb13..37c0e266 100644
--- a/deployment/access_classes/manifests/committers.pp
+++ b/deployment/access_classes/manifests/committers.pp
@@ -5,9 +5,9 @@ class access_classes::committers {
# user, and erase the password ( see pam_auth.c in openssh code,
# seek badpw )
# so the file must exist
- # permission to use svn, git, etc must be added separatly
+ # permission to use svn, git, etc must be added separately
- pam::multiple_ldap_access { 'committers':
+ class { 'pam::multiple_ldap_access':
access_classes => ['mga-shell_access'],
restricted_shell => true,
}
diff --git a/deployment/access_classes/manifests/iso_makers.pp b/deployment/access_classes/manifests/iso_makers.pp
index 21201587..c645205e 100644
--- a/deployment/access_classes/manifests/iso_makers.pp
+++ b/deployment/access_classes/manifests/iso_makers.pp
@@ -1,5 +1,5 @@
class access_classes::iso_makers {
- pam::multiple_ldap_access { 'iso_makers':
- access_classes => ['mga-iso_makers','mga-sysadmin']
+ class { 'pam::multiple_ldap_access':
+ access_classes => ['mga-iso_makers']
}
}
diff --git a/deployment/access_classes/manifests/web.pp b/deployment/access_classes/manifests/web.pp
index 45a9992e..fa2c7df5 100644
--- a/deployment/access_classes/manifests/web.pp
+++ b/deployment/access_classes/manifests/web.pp
@@ -1,5 +1,5 @@
class access_classes::web {
- pam::multiple_ldap_access { 'web':
- access_classes => ['mga-web','mga-sysadmin']
+ class { 'pam::multiple_ldap_access':
+ access_classes => ['mga-web']
}
}
diff --git a/deployment/access_classes/manifests/web_and_artwork.pp b/deployment/access_classes/manifests/web_and_artwork.pp
deleted file mode 100644
index 9a85bd3d..00000000
--- a/deployment/access_classes/manifests/web_and_artwork.pp
+++ /dev/null
@@ -1,5 +0,0 @@
-class access_classes::web_and_artwork {
- pam::multiple_ldap_access { 'web_artwork':
- access_classes => ['mga-web','mga-sysadmin','mga-artwork']
- }
-}
diff --git a/deployment/backups/manifests/init.pp b/deployment/backups/manifests/init.pp
index 1638286d..ba2d16d5 100644
--- a/deployment/backups/manifests/init.pp
+++ b/deployment/backups/manifests/init.pp
@@ -1,28 +1,23 @@
class backups {
class server {
- $backups_dir = '/backups'
- $confdir = "${backups_dir}/conf"
+ $backups_dir = '/data/backups'
+ $confdir = "${backups_dir}/conf"
- class { 'rsnapshot::base':
- confdir => $confdir,
- }
+ class { 'rsnapshot::base':
+ confdir => $confdir,
+ }
- file { $backups_dir:
- ensure => directory,
- owner => root,
- group => root,
- mode => 700,
- }
+ file { $backups_dir:
+ ensure => directory,
+ owner => root,
+ group => root,
+ mode => '0700',
+ }
- rsnapshot::backup{ 'alamut':
- snapshot_root => "${backups_dir}/alamut",
- backup => [ 'root@alamut.mageia.org:/srv/wiki wiki' ],
- }
-
- rsnapshot::backup{ 'krampouezh':
- snapshot_root => "${backups_dir}/krampouezh",
- backup => [ 'root@krampouezh.mageia.org:/home/irc_bots/meetings meetbot' ],
- }
+ rsnapshot::backup{ 'neru':
+ snapshot_root => "${backups_dir}/neru",
+ backup => [ "root@neru.${::domain}:/home/irc_bots/meetings meetbot" ],
+ }
}
}
diff --git a/deployment/common/manifests/base_packages.pp b/deployment/common/manifests/base_packages.pp
index d1509ebd..091e7c3e 100644
--- a/deployment/common/manifests/base_packages.pp
+++ b/deployment/common/manifests/base_packages.pp
@@ -14,10 +14,10 @@ class common::base_packages {
'lshw',
'lvm2',
'iotop',
- 'wget']
+ 'wget']
- if $::arch == 'x86_64' {
- $package_list += ['mcelog']
+ if $::architecture == 'x86_64' {
+ package { ['mcelog']: }
}
package { $package_list: }
diff --git a/deployment/common/manifests/default_ssh_root_key.pp b/deployment/common/manifests/default_ssh_root_key.pp
index 4d4ee524..ab17466d 100644
--- a/deployment/common/manifests/default_ssh_root_key.pp
+++ b/deployment/common/manifests/default_ssh_root_key.pp
@@ -3,24 +3,34 @@ class common::default_ssh_root_key {
user => 'root'
}
+ ssh_authorized_key { 'ssh_key_misc':
+ # initially removed on 2012-10-17
+ ensure => 'absent',
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAABIwAAAgEA4fpjTvcL09Yzv7iV40TPjiXGHOOS5MldSh5ezSk7AMLVjAAloiidl8O3xwlxwUnjUx5zv1+RlbV76sdiSD32lBht72OZPg0UqQIB8nHeVJBdJ8YpnQ3LynNPPYJ65dvdr0uE2KRlN/1emi2N+O+f2apwc1YiL8nySEK/zLvCKO5xj16bIVuGFilDdp75X/t3C/PDsZU+CUyWL5Ly3T2+ljGc+nEAK9P0PNnvl9bRK9dqu457xjca8nXwWVI1fd6Jnt1jISFdQXy6/+9326Z6aAxvWKCrCvmdg+tAUN3fEj0WXZEPZQ1Ot0tBxKYl+xhV1Jv/ILLbInT0JZkSEKNBnJn4G7O4v+syoMqA7myHre73oGn/ocRWGJskIM33aXrJkZkJ4LkF1GLJPFI4y7bzj024sPAVvBwDrV7inwsOy0DSQ5tCbfX25TTXbK+WMXzz0pBbSi6mPgjtzlSYsLZrTa7ARYhggDG2miuOAFrup8vP7/aH2yZ+hZuF70FsMh4lf/eXDGwfypyfYrjfVSkFfY0ZU294ouTBn3HtHmgFu82vOMvLNtI9UyERluCBpLBXOT8xgM97aWFeUJEKrVxkGIiNwVNylMEYp8r16njv810NdTLA3jZu9CLueVvME6GLsGve5idtGmaYuGYNRnSRx3PQuJZl1Nj7uQHsgAaWdiM=',
+ }
+
ssh_authorized_key { 'ssh_key_dams':
- type => 'ssh-dss',
- key => 'AAAAB3NzaC1kc3MAAACBAP7Dz4U90iWjb8SXVpMsC/snU0Albjsi5rSVFdK5IqG0jcQ3K5F/X8ufUN+yOHxpUKeE6FAPlvDxIQD8hHv53yAoObM4J4h2SVd7xXpIDTXhdQ9kMYbgIQzyI/2jptF77dxlwiH9TirmmpUSb680z55IkVutwSVJUOZCOWFXfa35AAAAFQDqYF7tWvnzM/zeSFsFZ9hqKCj47QAAAIBdmaPCyf4iU9xGUSWi1p7Y6OlUcfu2KgpETy8WdOmZ4lB3MXdGIoK5/LLeLeeGomAVwJMw3twOOzj4e1Hz16WM+fWsMVFnZftLFo9L2LvSQElIEznjIxqfmIsc2Id2c0gI+kEnigOWbBJ7h0O09uT7/eNBysqgseCMErzedy5ZnAAAAIAGrjfWjVtJQa888Sl8KjKM9MXXvgnyCDCBP9i4pncsFOWEWGMWPY0Z9CD0OZYDdvWLnFkrnoMaIvWQU7pb4/u/Tz9Dsm65eQzUaLSGFzROAX6OB47L7spMS4xd6SF+ASawy/aYiHf241zumJLvPkUpXceBv2s7QOp2g6S6qCtypA==',
+ ensure => 'absent',
+ type => 'ssh-dss',
+ key => 'AAAAB3NzaC1kc3MAAACBAP7Dz4U90iWjb8SXVpMsC/snU0Albjsi5rSVFdK5IqG0jcQ3K5F/X8ufUN+yOHxpUKeE6FAPlvDxIQD8hHv53yAoObM4J4h2SVd7xXpIDTXhdQ9kMYbgIQzyI/2jptF77dxlwiH9TirmmpUSb680z55IkVutwSVJUOZCOWFXfa35AAAAFQDqYF7tWvnzM/zeSFsFZ9hqKCj47QAAAIBdmaPCyf4iU9xGUSWi1p7Y6OlUcfu2KgpETy8WdOmZ4lB3MXdGIoK5/LLeLeeGomAVwJMw3twOOzj4e1Hz16WM+fWsMVFnZftLFo9L2LvSQElIEznjIxqfmIsc2Id2c0gI+kEnigOWbBJ7h0O09uT7/eNBysqgseCMErzedy5ZnAAAAIAGrjfWjVtJQa888Sl8KjKM9MXXvgnyCDCBP9i4pncsFOWEWGMWPY0Z9CD0OZYDdvWLnFkrnoMaIvWQU7pb4/u/Tz9Dsm65eQzUaLSGFzROAX6OB47L7spMS4xd6SF+ASawy/aYiHf241zumJLvPkUpXceBv2s7QOp2g6S6qCtypA==',
}
ssh_authorized_key { 'ssh_key_blino':
- type => 'ssh-dss',
- key => 'AAAAB3NzaC1kc3MAAAEBAIuMeFTbzLwcxlKfqSUDmrh2lFVOZyjotQsUm4EGZIh8killmHCBmB8uYvh3ncwvcC8ZwfRU9O8jX6asKJckFIZ37cdHaTQR7fh5ozG4ab652dPND2yKCg1LCwf2x0/Ef1VtyF7jpTG/L9ZaGpeXQ8rykoH4rRnfSdYF0xT7ua9F/J/9ss5FtzQYbQLFMzV3SlXRWp5lzbF4lCyoTyijc8cDrTKeDTu/D5cTpqYxfKUQguGGx0hqUjE3br8r4MPOECqpxAk3gkDr+9mIGftKz07T9aMnHVNNI+hDnjACbbZcG4hZnP99wKmWQ4Pqq7Bten6Z/Hi10E5RiYFyIK8hrR0AAAAVALwhZE/KgdoAM7OV5zxOfOvKrLwJAAABADRU1t5V2XhG07IKgu4PGp9Zgu3v9UkqqPU7F+C8mp2wUw7yTgKaIety8ijShv0qQkF+3YNGj9UnNYeSDWJ62mhMfP6QNQd3RAcbEggPYDjIexoLus44fPGOHtyzvwgSHAGkhBAG9U6GrxTOCUE4ZcZ82r2AdXGzngqnxgvihs9X/thTZu6MuPATueTL6yKShPsFRamgkWmqjJTKP4ggCPHK3FqCiLkrMNbwZ7WECEuodBGou6yCTTGkUXIxGv3/FU96u9FMhqtswClZEElxu+Gajw8gNF8kLnGUSlbubHocfhIAraxfc6s31T+b3Kq6a2JeLhODdgERFM2z/yMbsMMAAAEACqUvqpak3+am+Xz1KOOgTnprpjs8y9cbBU+BzkyhISGNINVSv9fEVEuOIwxW8EZ1gHLORYwAx9onk3WXUKX48DHlMHLwgpahQJnMsuUsJn2QknTiGuML+9MzNrE4ZEoipTEL11UayVcCFYGEB1X0IghX+XmLTGhji6DUBUmepzWN3FXvYMJH50sFLjCok9JszJCgzh8jILp37n8HXgG/FPG5soGG095lHand41s9qdeq4pGchKGDOEia9KAPL6Px5o48dQxxJkMoI8gljFcwVphc0QMmQSqN1paZgnzzwkGp4smuWNxZ+kWdJOceyrlULOsgi9LEkItHZyZtDzufmg==',
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDOyX/M3w0UdN5xwDLpTKj2e7pgXNZIPvWicNkp8BKqEu/ZALJ17QFZro1qrg/fLTsfs15YRMuwQH7MJ9uQsAqyE/aCYt18E/MLdtJSniqdQczSjjCTtB7+KtMh8pXFE5m9KEt0vhutdoB+VoGfbhVaBmjjnDPheM5Qive62askFT0pGlyMdY4PP9q8u10Tiqbb6w0yD7sbtF9GN1HpZBb97YaYyOGu9RpkA+Hb+Ma/faWkbOoP8OIJrGUjhVbSglzKBsEIo/i7+uQ86eMWJHB/o4tN7bU/6QQiGF4pN4E5jKPQHUZsQWI5SAKfkgOEGppYxiMF6pmCdI4Lx9VhttXN',
}
ssh_authorized_key { 'ssh_key_nanar':
- type => 'ssh-dss',
- key => 'AAAAB3NzaC1kc3MAAACBAMLWdzwlo5b9yr1IR5XbbYpESJQpTiZH4gTzVaUIdtbU6S2R41Enn/xZSLgbWcCX79WEcQlfKDS3BcrjWybpwCQD+i1yIA4wmYaQ3KwYBaIsTe5UtPF41hs8Jb8MhTPe9z9hNi5E1R6QQ2wPu3vDAi4zTZ4415ctr6xtW+IDYNOLAAAAFQC9ku78wdBEZKurZj5hJmhU0GSOjwAAAIAeGorkIHQ0Q8iAzKmFQA5PcuuD6X7vaflerTM3srnJOdfMa/Ac7oLV+n5oWj0BhuV09w8dB678rRxl/yVLOgHR9absSicKDkYMZlLU7K1oNFwM4taCdZZ1iyEpJVzzUOVCo8LqK6OZJhbFI0zbarq4YM/1Sr+MIiGv5FK7SCpheAAAAIEAwP95amGY7BgPzyDDFeOkeBPJQA/l7w0dEfG8A+2xui679mGJibhlXiUWqE0NqeDkD17Oc+eOV/ou5DA62tMDSus119JjqYhDEOs0l5dvA6aTzObZDhiUDQbNoS9AIPxgsqdc2vBRxonHUm/7maV8jvWVSy1429CNhnyWKuTe2qU=',
+ ensure => 'absent',
+ type => 'ssh-dss',
+ key => 'AAAAB3NzaC1kc3MAAACBAMLWdzwlo5b9yr1IR5XbbYpESJQpTiZH4gTzVaUIdtbU6S2R41Enn/xZSLgbWcCX79WEcQlfKDS3BcrjWybpwCQD+i1yIA4wmYaQ3KwYBaIsTe5UtPF41hs8Jb8MhTPe9z9hNi5E1R6QQ2wPu3vDAi4zTZ4415ctr6xtW+IDYNOLAAAAFQC9ku78wdBEZKurZj5hJmhU0GSOjwAAAIAeGorkIHQ0Q8iAzKmFQA5PcuuD6X7vaflerTM3srnJOdfMa/Ac7oLV+n5oWj0BhuV09w8dB678rRxl/yVLOgHR9absSicKDkYMZlLU7K1oNFwM4taCdZZ1iyEpJVzzUOVCo8LqK6OZJhbFI0zbarq4YM/1Sr+MIiGv5FK7SCpheAAAAIEAwP95amGY7BgPzyDDFeOkeBPJQA/l7w0dEfG8A+2xui679mGJibhlXiUWqE0NqeDkD17Oc+eOV/ou5DA62tMDSus119JjqYhDEOs0l5dvA6aTzObZDhiUDQbNoS9AIPxgsqdc2vBRxonHUm/7maV8jvWVSy1429CNhnyWKuTe2qU=',
}
ssh_authorized_key { 'ssh_key_dmorgan':
- type => 'ssh-dss',
- key => 'AAAAB3NzaC1kc3MAAACBAOsCjs1EionxMBkyCOXqhDlGUvT/ZORSjqrEhZrro2oPdnMvj3A7IHf1R8+CVVrJlnOHFEwfdC3SB5LYhmUi/XaBq1eqUiVFQLFURrYlrWFh1xSqGUFvvUfMFXOZCn4f9eJYDVaRtWBL7IZCijwZS6bbE0FLW0f6pPzhHtMkSRW/AAAAFQCyg7km5gCZ6W4iRKqr87Wy+LajMwAAAIBZ3+oM/hQ9MS2QkMa8wZk9taEO9PJQHXO3IHyo3wMUj7DYnwgyHQIIeTgPwrE+z0TkM3K3pQlf8xQmsQo7T2kQHCLFZnueEoNB+y+LySLtLDoptYlkqJ9Db0kJti+W8EFc8I+s87HuVdkXpqid222zmRfzYufjbosb8abtGUODXAAAAIBWlhkUEZsbQXkimAnfelHb7EYFnwUgHPSzrzB4xhybma9ofOfM3alZubx9acv94OrAnlvSTfgETKyT0Q+JYvtxZr9srcueSogFq8D8tQoCFJIqpEvjTxjSlg1Fws0zHBH7uO7Kp8zhnuTalhQC1XorFPJD3z40fe62fO6a02EUCQ==',
+ ensure => 'absent',
+ type => 'ssh-dss',
+ key => 'AAAAB3NzaC1kc3MAAACBAOsCjs1EionxMBkyCOXqhDlGUvT/ZORSjqrEhZrro2oPdnMvj3A7IHf1R8+CVVrJlnOHFEwfdC3SB5LYhmUi/XaBq1eqUiVFQLFURrYlrWFh1xSqGUFvvUfMFXOZCn4f9eJYDVaRtWBL7IZCijwZS6bbE0FLW0f6pPzhHtMkSRW/AAAAFQCyg7km5gCZ6W4iRKqr87Wy+LajMwAAAIBZ3+oM/hQ9MS2QkMa8wZk9taEO9PJQHXO3IHyo3wMUj7DYnwgyHQIIeTgPwrE+z0TkM3K3pQlf8xQmsQo7T2kQHCLFZnueEoNB+y+LySLtLDoptYlkqJ9Db0kJti+W8EFc8I+s87HuVdkXpqid222zmRfzYufjbosb8abtGUODXAAAAIBWlhkUEZsbQXkimAnfelHb7EYFnwUgHPSzrzB4xhybma9ofOfM3alZubx9acv94OrAnlvSTfgETKyT0Q+JYvtxZr9srcueSogFq8D8tQoCFJIqpEvjTxjSlg1Fws0zHBH7uO7Kp8zhnuTalhQC1XorFPJD3z40fe62fO6a02EUCQ==',
}
ssh_authorized_key { 'ssh_key_coling':
@@ -29,22 +39,53 @@ class common::default_ssh_root_key {
}
ssh_authorized_key { 'ssh_key_boklm':
- type => 'ssh-dss',
- key => 'AAAAB3NzaC1kc3MAAACBAIGfoferrHXi7m8Hw3wY3HzIvWzlBKRu4aUpOjFgFTw+aPiS842F8B2bqjzUyLVAv13zHB5QjVeAB0YQ1TvMQbew+7CRAgAVWrY/ckMJxSdNk6eKnxlnLA295xBnyc+jdMhdTKisywtlkLP6Au+2eA/sDKELO8tiIQzSUithppU/AAAAFQCP/IlvpJjhxQwgA4UW1Mg7W3MPVwAAAIAc8BA7W9qDaA8/sQiOu6sSueEVnf7QmJzTJuT0ZJ9HDSB39+fQrwjPZqxiTpAfSboBTC0KiuG9ncCZyh6fAmn2i9WSZ6HYkoLBjHU3nu3u18qlT8LqwajUjgp15jgUKWB8OxvO1dPNaLEsvP1BKPTfDoPNPeUeQmb3WaX9S+pVGwAAAIA63gRktdobLeeuRFAfPdQQ7Imi1GwrfKa2QUgowksDxwgBBo796HN41+yF0W2AOZ2lx25KQRF0Wgc5Abm/TV8u3WbzosYbZgUBiGDqyVhIPU/xF+yPEHPYx3G3nwjEZAaxxf+LaeZkY1Yp15O6NAZAzdyV00iG/tO/ciWBPCMeJA==',
+ # requested by boklm on 2014-03-23
+ ensure => 'absent',
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQD4dKlLuPipueSWeX70QbrQ88tSVcfW4efoHS2vYmqbe5VE73yOCLFemDb3yk8PNxsCV5KIIesdKq6uRYiUKfCKJsF+UnFRdTniETsI3gmr895mvJMgdxuYCCRqdhjyKfWSds11cptBJpw2eXpsY6O5GPqVYG23/DWH4sNCFMawek9dWZy21qcGSjyXayek0nqXBLRv1SVNdipMeLVa99haOHViIV+gmqe3FKqT1UsNBSUQ9BdwtN9rvM2Qo1WYgptFffWRmYicSSbJSf8ru0sRcsD2hknlA0Eu5CO7dKD0Nc2cXsuwkiQXW+QKMduB5SGznyRpRBMIPKr1mnwLKEWXlMppgfzoI06xfqmasJZfvtR/dNQ5ugl+8J+AlPo/tegNNrHzzmplx7118kMgqBnYg9zjkju1iv9RblAcWitKjEN+zL795nmkbIPD+bo1Ql/YkMe9bGTHB3isHAAtzvjWHatyKzyq3D/k8+tJxSy91a9JIX5sHvnBev4VrzV+JE7QAT//9ELaG93bIljn8pDKKxjiNexjwiKpDRSB2AAWAMf7Qm3FBXrwfoIsREfGrPAjKsU1YUKC27xBDKSLE2ThqhkSYRKeNoECB5Ab7MhbNx8/sXI/y6YboDnFlFkXSht9WklCiGXpf36F0ei0oiA5O+v4TU6ix/WWrpnTazicKw==',
}
ssh_authorized_key { 'ssh_key_buchan':
- type => 'ssh-dss',
- key => 'AAAAB3NzaC1kc3MAAACBALpYDQtkZcfXdOILynCGa7IAbW4+etmzpIMjw6BfvZOfLT6UPfDwajhDBMBNSbgigxkxxEdsa0/UMIE3Yrpr8YivhbL79sFw2N/FeWCs3Vk8JXNjBGA6itAIz9nwfh6qCDUj2t8LTdOQdYrSFOO7x2dFgeCwi21V27Ga2vqsvkUnAAAAFQD708pfON6Itq/5S+4kkNdNNDKWCwAAAIEAkRQeugul6KmOC0C2EmgVJvKK1qImlwHir08W1LTESnujmRIWLRst8sDoKjJpNevFuHGybPQ3palvM9qTQ84k3NMsJYJZSjSexsKydHJbD4ErKk8W6k+Xo7GAtH4nUcNskbnLHUpfvzm0jWs2yeHS0TCrljuTQwX1UsvGKJanzEoAAACBAIurf3TAfN2FKKIpKt5vyNv2ENBVcxAHN36VH8JP4uDUERg/T0OyLrIxW8px9naI6AQ1o+fPLquJ3Byn9A1RZsvWAQJI/J0oUit1KQM5FKBtXNBuFhIMSLPwbtp5pZ+m0DAFo6IcY1pl1TimGa20ajrToUhDh1NpE2ZK//8fw2i7',
+ ensure => 'absent',
+ type => 'ssh-dss',
+ key => 'AAAAB3NzaC1kc3MAAACBALpYDQtkZcfXdOILynCGa7IAbW4+etmzpIMjw6BfvZOfLT6UPfDwajhDBMBNSbgigxkxxEdsa0/UMIE3Yrpr8YivhbL79sFw2N/FeWCs3Vk8JXNjBGA6itAIz9nwfh6qCDUj2t8LTdOQdYrSFOO7x2dFgeCwi21V27Ga2vqsvkUnAAAAFQD708pfON6Itq/5S+4kkNdNNDKWCwAAAIEAkRQeugul6KmOC0C2EmgVJvKK1qImlwHir08W1LTESnujmRIWLRst8sDoKjJpNevFuHGybPQ3palvM9qTQ84k3NMsJYJZSjSexsKydHJbD4ErKk8W6k+Xo7GAtH4nUcNskbnLHUpfvzm0jWs2yeHS0TCrljuTQwX1UsvGKJanzEoAAACBAIurf3TAfN2FKKIpKt5vyNv2ENBVcxAHN36VH8JP4uDUERg/T0OyLrIxW8px9naI6AQ1o+fPLquJ3Byn9A1RZsvWAQJI/J0oUit1KQM5FKBtXNBuFhIMSLPwbtp5pZ+m0DAFo6IcY1pl1TimGa20ajrToUhDh1NpE2ZK//8fw2i7',
}
ssh_authorized_key { 'ssh_key_tmb':
+ ensure => 'absent',
type => 'ssh-dss',
key => 'AAAAB3NzaC1kc3MAAACBAMFaCUsen6ZYH8hsjGK0tlaguduw4YT2KD3TaDEK24ltKzvQ+NDiPRms1zPhTpRL0p0U5QVdIMxm/asAtuiMLMxdmU+Crry6s110mKKY2930ZEk6N4YJ4DbqSiYe2JBmpJVIEJ6Betgn7yZRR2mRM7j134PddAl8BGG+RUvzib7JAAAAFQDzu/G2R+6oe3vjIbbFpOTyR3PAbwAAAIEAmqXAGybY9CVgGChSztPEdvaZ1xOVGJtmxmlWvitWGpu8m5JBf57VhzdpT4Fsf4fiVZ7NWiwPm1DzqNX7xCH7IPLPK0jQSd937xG9Un584CguNB76aEQXv0Yl5VjOrC3DggIEfZ1KLV7GcpOukw0RerxKz99rYAThp6+qzBIrv38AAACBAKhXi7uNlajescWFjiCZ3fpnxdyGAgtKzvlz60mGKwwNyaQCVmPSmYeBI2tg1qk+0I5K6LZUxWkdhuE1UfvAbIrEdwyD8p53dPg1J9DpdQ1KqApeKqLxO02KJtfomuy3cRQXmdfOTovYN7zAu1NCp51uUNTzhIpDHx0MZ6bsWSFv',
}
+ ssh_authorized_key { 'ssh_key_tmb_rsa':
+ ensure => 'absent',
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQC+PaVLUaIvBg/lDK0esX2xvVe9IspiXq2ES4Ti/KvmTqbsAhgUuW+IR4fxY2vffCdbTo2B247bEh4kTB8P+ZKmrJPv/c18CtidJHKXpV4EwEAD3abeKKYcUlDXyX0zTER64d86yT0PN2eA9BJNfpZeVCMpn32C7tYBzQztp1FVJB+i2oslayKE4Q9FRxpJFzhvepOsUC4ZnePSz7ymeg4Y6vgWjoH9Eo33FSJ0fEm8+/bk8kir9X1wu+BbfQodnkS6wTehXqb0hj1uNIkngy+nA+T+ckhhddlRELKYt44VMp/X8wtCE7Y8nLOi15sQiSIgtrXDIdwoIzfyLKZM1/fH/pYAKL/tjhomHgzSWeyHNA+gFT6B868tnBkSaBMfdAJmNu7RfAobFWmTHp2dF2Q5AsbzqFBWcofO3qvrP1xvz5Ckp+GzmflXdqg2M7XXNP5G1NAAq5dsMGRUBN99xYF25EpmDjbPaSXUDbvtzGY4B2Doc3OhR6Ask4undDvj+oOJw4Ldi9q4xexl1L9P0DbbfGhl/d/0T5snnagIAgXOZvG8Nwywzhv4oIoM2o6C1HNiUea3ODHMMl8f7w0ofDoX7JH4gQvRxgPRniZgBKSTDl7fD0Fh5FAZ6KbcZ2VCit8pLZN0OVHOr7kNBvsRClLe8O8R4S0wACe44U2Jq2U+6Q==',
+ }
+
ssh_authorized_key { 'ssh_key_pterjan':
type => 'ssh-rsa',
key => 'AAAAB3NzaC1yc2EAAAABIwAAAQEAspyZMl5zAkk5SL45zFvtJF7UhXTRb0bEaZ3nuCC1Ql5wM3GWuftqd5zLH88dCu7ZO/BVh213LZTq/UHb6lI7kWalygk53qtdEx2cywjWFOW23Rg6xybatCEZ2/ZrpGZoBGnu63otAp4h2Nnj/VkOio3pGwD8vavmZ4xPrcECPAwtMPJsYf44Ptu2JdXizi4iY8I0/HKitQ113I4NbDcAiMKbTXSbOfqC+ldcgW3+9xShx/kuMFTKeJOy4LI4GR6gykzkV6+vfnalp24x/SIEjuohBarCRQKo4megHqZOzdMYAHqq0QuNubXURNb0Mvz1sE7Y8AFIxwSfXdQGi5hcQQ==',
}
+
+ ssh_authorized_key { 'ssh_key_neoclust':
+ ensure => 'absent',
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDGyI8oIP8SgahPbMZ04Msr/vnI2gf4yx//QhEnZv8b++WIH0oibfK8g5Lz4HXReJRHzNXN1EhKQXoClgAKwv7zqkTLxV44tVcz8cwvfldkKNB+QxfL74JgsxCyNW8mpJdrJ71kbkT4Jt6AxeEd10ltQyqT7QDQMM7fxH8dbOCMcc7jtgOqwPXLy7hEumqkYxNuMxjrAbiDk2Nx0ddP2Ta4NJjSsGzUzSPsGhLVCO3+Wv6Ymss9Vacbe684ERwqz6odi5ZX0utfXXNphqqAckKCxurrI+LoWzt9MgWtR9iJC1joVDqRbggNm6bNNPZIdhmi5/yJrk3x7qwXb7uQNiE7',
+ }
+
+ ssh_authorized_key { 'ssh_key_maat':
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDNQqKZkAhPiMFoeE3uqioaTFLDAP6cTwBsRA7angVMDpAThbYxGK439oe3xaj/cgGrlEApAnCmEPL81in6hubQaTRMR/RigA3FEkoO3H/J2Xng1rD+aVQFFK0EerjzxDa+bxUu7eRcHv1ar49LY7spNjk0LzNtI/n32L+t3WifCE/ithHi80Qh2kMq36kTm10wW4Gxpz9tYzSQz/f7dfrzhX+yOVUbmnevuS1BDeF21hmxmltBFQZvBh3jiUiWeTMHePHaod8jI8voJkxXJ+TxsniJ3AfxgCaMmIoU0a0rBxeTpzQVkUHJUsmsmji8WdeW9J9gNwXYYv2PuSli8iz5mAWG0eo6y2W+tOHNy6RhvcIkPi4pViycxBjQvoxki5nCZDXo1KwWCYoJ0wg5YIrqdqBb70ibAqMOS1wXSO5KwWbUoVYrP+tSvz+i1EQtgEHtamgCzFAkJXQrjXhvJ/L1GVJLsvqTpee+kN/9NnbH8GnHKvnenvE7ecITcMoy8hODulYaqZrx+0jWivTv5UpHO7gX9RwsDB2nviT4rluYWjIugIjCnBJIroD1QP6UWFFwG6MM44QPaByXjz8AC7vw1fNefhWFmS+CT7dQ8Vd2zglP4gQwlWePTC8sASHDLSoe0nIXGXcfY2Af8bThV1fMEteI850thUvIKfrx34z4rw==',
+ }
+
+ ssh_authorized_key { 'ssh_key_wally':
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAABIwAAAQEAsB/PAEQJE/M5c3keyef6rKQvCtTk5cdw6ujXl6n8G7D7Q6h4IgIccd5mYcBU7ij2S5N3lfOQmKJqf2Pa5pByLfXlQnhCLzsgL9X45WJmpsoVK1MzjDY8iY+aL/74tj3wiMzuzAAwwpE3EftyfscxhSwf2e11B3qDzVRmNnxPVKlm85nTygnrZ0ag4nOC6O4yC3Hh1ULhKGtNAsGNF2yRGs7IcN9ytcVhGF3WGJfRI2c2kIuKW/lXxeE04sWWb+k019ys4ah0iQoLja6xVSHgxbVlm3oDz+mGGsPtoSvtoWpvF3q9FKqGclJpboWRMo3jyP6yDRVcTMXUSONmq3N8uw==',
+ }
+
+ ssh_authorized_key { 'ssh_key_danf':
+ type => 'ssh-rsa',
+ key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQCgWFg4EsUkZ5uh34ScVbfwhVdP7kTLRwsojeF+DgmwXSPbM9NUxiCmyFrHuh3m6bxG3BPMwrDskqUrQ3z/5WX6dB/CzSP/j03EkslzaE7eTzIpGt/vKIuZHR+4Z9FZcY1pyoI44rdgW5MVC+yBoJkvBerOkvNzfiRSfQ9R4eopPNTif3vb4MP/cFzFfa3o8NMqHxhgGFhF945NlzCUmnec13sNggx1wGNFHMpWttSaQ0izgvSdb61WSswNnCjBF5t3oyh7DgI80TN/XfXfDWZPjkQUzLrh9inuPollAWfreeInoCmF8ou268efaRoSfRMZ3qdRkJLDDy2Os8eL/d3d',
+ }
}
diff --git a/deployment/common/manifests/i18n.pp b/deployment/common/manifests/i18n.pp
index 9ef731ad..43b1fc3a 100644
--- a/deployment/common/manifests/i18n.pp
+++ b/deployment/common/manifests/i18n.pp
@@ -1,8 +1,12 @@
class common::i18n {
package { 'locales-en': }
- # push the locale everywhere, as it affect facter
+ # push the locale everywhere, as it affects facter
file { '/etc/sysconfig/i18n':
content => template('common/i18n'),
}
+
+ file { '/etc/locale.conf':
+ content => template('common/locale.conf'),
+ }
}
diff --git a/deployment/common/manifests/init.pp b/deployment/common/manifests/init.pp
index acdaea7e..c7b7486d 100644
--- a/deployment/common/manifests/init.pp
+++ b/deployment/common/manifests/init.pp
@@ -8,6 +8,7 @@ class common {
include common::export_ssh_keys
include common::import_ssh_keys
include common::i18n
+ include common::sudo_sysadmin
include ntp
include common::urpmi_update
include puppet::client
@@ -19,6 +20,11 @@ class common {
file { '/srv/':
ensure => directory
}
+
+ host { "${::hostname}.${::domain}":
+ ip => '127.0.0.1',
+ host_aliases => [ "${::hostname}", 'localhost' ],
+ }
}
class default_mageia_server inherits default_mageia_server_no_smtp {
diff --git a/deployment/common/manifests/sudo_sysadmin.pp b/deployment/common/manifests/sudo_sysadmin.pp
new file mode 100644
index 00000000..1247c02c
--- /dev/null
+++ b/deployment/common/manifests/sudo_sysadmin.pp
@@ -0,0 +1,7 @@
+class common::sudo_sysadmin {
+ include sudo
+
+ sudo::sudoers_config { '00-sysadmin':
+ content => template('common/sudoers.sysadmin')
+ }
+}
diff --git a/deployment/common/templates/locale.conf b/deployment/common/templates/locale.conf
new file mode 100644
index 00000000..e9fc2e06
--- /dev/null
+++ b/deployment/common/templates/locale.conf
@@ -0,0 +1,20 @@
+<%-
+# should not be changed
+locale = 'en_US.UTF-8'
+
+-%>
+LC_TELEPHONE=<%= locale %>
+LC_CTYPE=<%= locale %>
+LANGUAGE=<%= locale %>:<%= locale.split('.')[0] %>:<%= locale.split('.')[0].split('_')[0] %>
+LC_MONETARY=<%= locale %>
+LC_ADDRESS=<%= locale %>
+LC_COLLATE=<%= locale %>
+LC_PAPER=<%= locale %>
+LC_NAME=<%= locale %>
+LC_NUMERIC=<%= locale %>
+LC_MEASUREMENT=<%= locale %>
+LC_TIME=<%= locale %>
+LANG=<%= locale %>
+LC_IDENTIFICATION=<%= locale %>
+LC_MESSAGES=<%= locale %>
+
diff --git a/deployment/common/templates/sudoers.sysadmin b/deployment/common/templates/sudoers.sysadmin
new file mode 100644
index 00000000..874b1858
--- /dev/null
+++ b/deployment/common/templates/sudoers.sysadmin
@@ -0,0 +1 @@
+%mga-sysadmin ALL=(ALL) ALL
diff --git a/deployment/dns/manifests/reverse_zone.pp b/deployment/dns/manifests/reverse_zone.pp
index 264830c7..9095251d 100644
--- a/deployment/dns/manifests/reverse_zone.pp
+++ b/deployment/dns/manifests/reverse_zone.pp
@@ -1,5 +1,5 @@
define dns::reverse_zone {
bind::zone::reverse { $name:
- content => template("dns/$name.zone")
+ content => template("dns/${name}.zone")
}
}
diff --git a/deployment/dns/manifests/zone.pp b/deployment/dns/manifests/zone.pp
index c11f7d02..7d4da311 100644
--- a/deployment/dns/manifests/zone.pp
+++ b/deployment/dns/manifests/zone.pp
@@ -1,5 +1,5 @@
define dns::zone {
bind::zone::master { $name:
- content => template("dns/$name.zone")
+ content => template("dns/${name}.zone")
}
}
diff --git a/deployment/dns/templates/2.1.0.0.0.0.0.1.b.0.e.0.1.0.a.2.ip6.arpa.zone b/deployment/dns/templates/2.1.0.0.0.0.0.1.b.0.e.0.1.0.a.2.ip6.arpa.zone
index 166408b4..8ab67138 100644
--- a/deployment/dns/templates/2.1.0.0.0.0.0.1.b.0.e.0.1.0.a.2.ip6.arpa.zone
+++ b/deployment/dns/templates/2.1.0.0.0.0.0.1.b.0.e.0.1.0.a.2.ip6.arpa.zone
@@ -1,10 +1,10 @@
$TTL 3D
@ IN SOA ns0.mageia.org. root.mageia.org. (
- 2012110200 ; Serial
+ 2024090202 ; Serial
3600 ; Refresh
3600 ; Retry
- 2419200 ; Expire
- 86400 ; Minimum TTL
+ 3600000 ; Expire
+ 3600 ; Minimum TTL
)
; nameservers
diff --git a/deployment/dns/templates/7.0.0.0.2.0.0.0.8.7.1.2.2.0.a.2.ip6.arpa.zone b/deployment/dns/templates/7.0.0.0.2.0.0.0.8.7.1.2.2.0.a.2.ip6.arpa.zone
index 8a7007df..fdb83e63 100644
--- a/deployment/dns/templates/7.0.0.0.2.0.0.0.8.7.1.2.2.0.a.2.ip6.arpa.zone
+++ b/deployment/dns/templates/7.0.0.0.2.0.0.0.8.7.1.2.2.0.a.2.ip6.arpa.zone
@@ -1,10 +1,10 @@
$TTL 3D
@ IN SOA ns0.mageia.org. root.mageia.org. (
- 2012092801 ; Serial
+ 2024090202 ; Serial
3600 ; Refresh
3600 ; Retry
- 2419200 ; Expire
- 86400 ; Minimum TTL
+ 3600000 ; Expire
+ 3600 ; Minimum TTL
)
; nameservers
@@ -12,10 +12,8 @@ $TTL 3D
@ IN NS ns1.mageia.org.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR gw-ipv6.mageia.org.
-2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR alamut.mageia.org.
-3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR valstar.mageia.org.
4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR ecosse.mageia.org.
-5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR jonund.mageia.org.
6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR fiona.mageia.org.
7.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR sucuk.mageia.org.
8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR rabbit.mageia.org.
+9.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 IN PTR duvel.mageia.org.
diff --git a/deployment/dns/templates/mageia.fr.zone b/deployment/dns/templates/mageia.fr.zone
deleted file mode 100644
index 70ecc840..00000000
--- a/deployment/dns/templates/mageia.fr.zone
+++ /dev/null
@@ -1,27 +0,0 @@
-; cfengine-distributed file
-; local modifications will be lost
-; $Id$
-$TTL 3D
-@ IN SOA ns0.mageia.org. mageia.fr. (
- 2010110200 ; Serial
- 21600 ; Refresh
- 3600 ; Retry
- 2419200 ; Expire
- 86400 ; Minmun TTL
- )
-
-; nameservers
-@ IN NS ns0.mageia.org.
-@ IN NS ns1.mageia.org.
-
-@ IN MX 10 mx0.zarb.org.
-@ IN MX 20 mx1.zarb.org.
-
-; MX
-;@ IN MX 10 mx0.zarb.org.
-
-; machines
-mageia.fr. IN A 212.85.158.22
-
-; aliases
-www IN CNAME mageia.fr.
diff --git a/deployment/dns/templates/mageia.org.zone b/deployment/dns/templates/mageia.org.zone
index 69b43b75..a04ca19b 100644
--- a/deployment/dns/templates/mageia.org.zone
+++ b/deployment/dns/templates/mageia.org.zone
@@ -1,138 +1,174 @@
+<%-
+ # nodes list defined in puppet/manifests/nodes_ip.pp
+ nodes = scope.lookupvar('::nodes_ipaddr')
+-%>
; puppet-distributed file
; local modifications will be lost
; $Id$
-$TTL 1h
+$TTL 30m
@ IN SOA ns0.mageia.org. root.mageia.org. (
- 2012110200 ; Serial
- 21600 ; Refresh
+ 2025100701 ; Serial
+ 7200 ; Refresh
3600 ; Retry
- 2419200 ; Expire
- 86400 ; Minmun TTL
+ 3600000 ; Expire
+ 300 ; Minimum TTL
)
; nameservers
@ IN NS ns0.mageia.org.
@ IN NS ns1.mageia.org.
-@ IN MX 10 mx0.zarb.org.
-@ IN MX 20 mx1.zarb.org.
+@ IN MX 10 sucuk.mageia.org.
+@ IN MX 20 neru.mageia.org.
+
+; DKIM for mageia.org
+sucuk._domainkey IN TXT "v=DKIM1; k=rsa; t=s; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGH25Jb2Al84XlTfSWuqZL8f6K6b+QhJjvV3zbF1/t31WmLwEt0So+p3FbFeKmaq/e0nJ+wKteTSVZsl3xwux+MaARKJDpEXslEgy+ojCedWqqpP6xLUjPuYPimGPljwkLwDoJxwvjiLa2POebec7C+R/nzaGm2nnTFwYQomqlvQIDAQAB"
+sucuk._domainkey.group IN TXT "v=DKIM1; k=rsa; t=s; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBRrdmAaYpDBHCtzkephaLX9LrMFJvgq84dS0ogTIb0xD32qxQF69FU/gEUlfTjzJooTJQC3PK7R3oLnfoWttMlbHCGg/llSfoSI0gD/4UolZokzWZY3qdqMz+zKi9+bfjz0y4Fwx5EPyda1ihHhVB6c+wq6cekhDNOH8PHhO74QIDAQAB"
+sucuk._domainkey.duvel IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHYgFMZTHMYlymX0WJ17ZvgchABE+5O/c6np1gj5sBV2BPIJGs+h/i+Iq6jLYVhSOWEI+6wQKza/8r3Vr4ddi3/UPDzllfqMnKsbPHC/LscyIkQmpNiO2n0nIUhKbuVU1SsRC1B8svO9iNmEjg33/lrLiaV3DtDbGr0ozmBmeFVwIDAQAB"
+sucuk._domainkey.fiona IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDeFoY9MTeZD4Z3OnxUJvp6Nr5UF6+rBwCg0TwVWwe/17uCQ4M6ptDxPSGgVIMYJowg/VUcbqNLlt56kluC4mO/gVVUyPQe6EjYib+NV5PkvgHx2TOJfb27ANPiZ4f57eEFqmE3eD7SxqUqF9j2Vobt0J+XgFuyFUBzHZsRTNUpzQIDAQAB"
+sucuk._domainkey.forums IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVhhONroS/ayEPs+9fmom34EWsny7asKVxIuyJh8EzvPJmx6ODYtX/tN1ul++3xoFNHeAe5YSSGyK+7EgJ5E5wlhw6FwnHPnYp/eMsShDI2dyfYsQnS2Yc1VXkI9s83ZWaVTL9uPRDETMKDIF+QjljFQZAN+eaH55q9u3EZRrWwIDAQAB"
+sucuk._domainkey.identity IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBv4aqFb9cQQkPB30zRfCtcquWKsP5G2Nhh3HSEdN0fFvOegQnGykuGq6lDED9iJuiNSVGO2cjtWtFTwX3+1/W1AW7pmaUD7U9HzPoZgxGPWtvFcJ/tZ1mjKNoGaPa5vLaVpXwxNKjPUCI+w2t5cM8JPnemW1Vm/LeEJ0XLE0InwIDAQAB"
+sucuk._domainkey.madb IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI9WOO3aRQLLnXc08q9HP15VY79TQZR5GqdBcYu0H+jAiuR+OKz6NUSNoYdeNQ4FSvrz27elW6thNcKQg4wYNT4tsJ8d4OU5ScFcrPJszPucVyMpkl/ybCgVq0CmXgOh1yXYwl2YY4AfzUQ6skpTE5G2abIWBvPOvs8Q92vYJ1nwIDAQAB"
+sucuk._domainkey.rabbit IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZYdG5dEd0CHAYGPRG+OXm2gJTDVpjmsKkn5+4BISToAOXXyogRcJN/P6oPySlG+CyUl5PW/2nBIiiUfHNKxVSa9gPO3vS0nlEppSHulkhth4deNu8YXRgJQp31IgaD0/Cbu7CKcDJbxTKGdnMV7XPKoIxB/Mjn0TxUS+WC2WY6QIDAQAB"
+sucuk._domainkey.sucuk IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdzn4W4Tl4sJ0pfhktNVlWRYFXnIwaMENqmi2vgc/P8M/zVxysVuWPcEwhy+IiVT8tMleXMt9dreErzJS+8ZmMd8oTqRXM55ZzRuBtqiecKnbIrXpecYUhh+2o0BMouTRHZvrPK5PV6Y2PrXkXwLF8qOS/eslZDk7hLRk2XBVDWwIDAQAB"
+sucuk._domainkey.ml IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4uPUsb1kvNCXT1AsEBldhU/9akmeRrRHOQtI8g60K+y2fRRur5l+TJDZ/+bnyVS69AMhyfeWEaWGhQytvmkKZBQyHZ6JzS2him+HT/x7xCYOHlQ5vixy0t4jYqbYZ04pdokJ4jcJ3pU7CFisgzk2Ln7HA4JDD1Dc+kCYbOvivtQIDAQAB"
+sucuk._domainkey.neru IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4uPUsb1kvNCXT1AsEBldhU/9akmeRrRHOQtI8g60K+y2fRRur5l+TJDZ/+bnyVS69AMhyfeWEaWGhQytvmkKZBQyHZ6JzS2him+HT/x7xCYOHlQ5vixy0t4jYqbYZ04pdokJ4jcJ3pU7CFisgzk2Ln7HA4JDD1Dc+kCYbOvivtQIDAQAB"
; TODO use a loop here
-ml IN MX 10 alamut.mageia.org.
-ml IN MX 20 krampouezh.mageia.org.
+ml IN MX 10 sucuk.mageia.org.
+ml IN MX 20 neru.mageia.org.
-group IN MX 10 alamut.mageia.org.
-group IN MX 20 krampouezh.mageia.org.
+; Sender Policy Framework for mailing lists & some automated mails
+@ IN TXT "v=spf1 include:smtp.dnamail.fi mx ~all"
+ml IN TXT "v=spf1 mx ~all"
+group IN TXT "v=spf1 mx ~all"
+group IN MX 10 sucuk.mageia.org.
+group IN MX 20 neru.mageia.org.
-; MX
-;@ IN MX 10 mx0.zarb.org.
; machines
-;mageia.org. IN A 212.85.158.22
-; mageia.org set to IP of champagne :
-mageia.org. IN A 217.70.188.116
-www-zarb IN A 212.85.158.22
-
-krampouezh IN A 95.142.164.207
-krampouezh IN AAAA 2001:4b98:dc0:51:216:3eff:fe6b:4ea9
-
-champagne IN A 217.70.188.116
-champagne IN AAAA 2001:4b98:dc0:41:216:3eff:fe4f:5a18
-
-; lost oasis
-alamut IN A 212.85.158.146
-alamut IN AAAA 2a02:2178:2:7::2
-alamut IN SSHFP 1 1 ef093dc95002c4af6623f02238749fd59fe81eec
-alamut IN SSHFP 2 1 179cfa0660550698c4d98f488772b7c7896d96b5
+<%-
+ nodes_txt = ''
+ nodes.keys.sort.each{|nodename|
+ spf = ''
+ if nodes[nodename].has_key?('ipv4')
+ nodes_txt += nodename + ' IN A ' + nodes[nodename]['ipv4'] + "\n"
+ spf += ' ip4:' + nodes[nodename]['ipv4']
+ end
+ if nodes[nodename].has_key?('ipv6')
+ nodes_txt += nodename + ' IN AAAA ' + nodes[nodename]['ipv6'] + "\n"
+ spf += ' ip6:' + nodes[nodename]['ipv6']
+ end
+ nodes_txt += nodename + ' IN TXT "v=spf1 ' + spf + ' mx:mageia.org ~all" ' + "\n"
+ }
+-%>
+<%= nodes_txt %>
+
+;SSHFP
+
+; sucuk
+ns0 IN A <%= nodes['sucuk']['ipv4'] %>
+ns0 IN AAAA <%= nodes['sucuk']['ipv6'] %>
+; neru
+ns1 IN A <%= nodes['neru']['ipv4'] %>
+ns1 IN AAAA <%= nodes['neru']['ipv6'] %>
+
+; mageia.org set to IP of neru
+mageia.org. IN A <%= nodes['neru']['ipv4'] %>
+mageia.org. IN AAAA <%= nodes['neru']['ipv6'] %>
+
+; madb on mageia.madb.org
+;madb IN A 163.172.201.211
+; temporarily for hosting a redirect while the real madb is down
+madb IN CNAME neru
+
; since we have a subdomain, we cannot use a CNAME
-ml IN A 212.85.158.146
-ml IN AAAA 2a02:2178:2:7::2
-; mga torrent server and tracker
-torrent IN A 212.85.158.146
-torrent IN AAAA 2a02:2178:2:7::2
-
-valstar IN A 212.85.158.147
-valstar IN AAAA 2a02:2178:2:7::3
-valstar IN SSHFP 1 1 f08e336f678d7c4a8772f17770976af7dd4d7a4f
-valstar IN SSHFP 2 1 c0c45ed25aa8a9f1723a1ae077dcee61677a56a6
-
-ecosse IN A 212.85.158.148
-ecosse IN AAAA 2a02:2178:2:7::4
-jonund IN A 212.85.158.149
-jonund IN AAAA 2a02:2178:2:7::5
-fiona IN A 212.85.158.150
-fiona IN AAAA 2a02:2178:2:7::6
-sucuk IN A 212.85.158.151
-sucuk IN AAAA 2a02:2178:2:7::7
-rabbit IN A 212.85.158.152
-rabbit IN AAAA 2a02:2178:2:7::8
-
-arm1 IN A 10.42.0.51
-arm2 IN A 10.42.0.52
-
-; alamut
-ns0 IN A 212.85.158.146
-; krampouezh
-ns1 IN A 95.142.164.207
+ml IN A <%= nodes['sucuk']['ipv4'] %>
+ml IN AAAA <%= nodes['sucuk']['ipv6'] %>
; aliases
-;www IN CNAME www-zarb
-www IN CNAME champagne
-www-test IN CNAME champagne
-static IN CNAME champagne
-start IN CNAME champagne
-blog IN CNAME champagne
-planet IN CNAME champagne
-hugs IN CNAME champagne
-releases IN CNAME champagne
-dashboard IN CNAME champagne
-nav IN CNAME champagne
-doc IN CNAME champagne
-rsync IN CNAME valstar
-
-svn IN CNAME valstar
-git IN CNAME valstar
-meetbot IN CNAME krampouezh
-
-tmp IN CNAME alamut
-
-puppetmaster IN CNAME valstar
-puppet IN CNAME valstar
-pkgsubmit IN CNAME valstar
-binrepo IN CNAME valstar
-repository IN CNAME valstar
-maintdb IN CNAME valstar
-ldap IN CNAME valstar
-
-ldap-master IN CNAME valstar
-ldap-slave-1 IN CNAME krampouezh
-
-identity IN CNAME alamut
-identity-trunk IN CNAME alamut
-mirrors IN CNAME alamut
-epoll IN CNAME alamut
-pgsql IN CNAME alamut
-bugs IN CNAME alamut
-transifex IN CNAME alamut
-svnweb IN CNAME alamut
-xymon IN CNAME alamut
-check IN CNAME alamut
-wiki IN CNAME alamut
-gitweb IN CNAME alamut
-pkgcpan IN CNAME alamut
-perl IN CNAME alamut
-tmp IN CNAME alamut
-
+ldap-slave-1 IN CNAME neru
+
+archives IN CNAME neru
+blog IN CNAME neru
+dashboard IN CNAME neru
+doc IN CNAME neru
+hugs IN CNAME neru
+meetbot IN CNAME neru
+planet IN CNAME neru
+releases IN CNAME neru
+start IN CNAME neru
+static IN CNAME neru
+www-test IN CNAME neru
+
+rsync IN CNAME duvel
+svn IN CNAME duvel
+git IN CNAME duvel
+puppetmaster IN CNAME duvel
+puppet IN CNAME duvel
+pkgsubmit IN CNAME duvel
+binrepo IN CNAME duvel
+repository IN CNAME duvel
+maintdb IN CNAME duvel
+ldap IN CNAME duvel
+ldap-master IN CNAME duvel
+advisories IN CNAME duvel
+projects IN CNAME duvel
bcd IN CNAME rabbit
+epoll IN CNAME sucuk
+forums IN CNAME sucuk
+forum IN CNAME sucuk
+
+send IN CNAME sucuk
+bugs IN CNAME sucuk
+check IN CNAME sucuk
+gitweb IN CNAME sucuk
+identity IN A <%= nodes['sucuk']['ipv4'] %>
+identity-trunk IN CNAME sucuk
+mirrors IN CNAME sucuk
+nav IN CNAME sucuk
+people IN CNAME sucuk
+perl IN CNAME sucuk
+pg IN CNAME sucuk
+pkgcpan IN CNAME sucuk
+svnweb IN CNAME sucuk
+treasurer IN CNAME sucuk
+wiki IN CNAME sucuk
+www IN CNAME sucuk
+xymon IN CNAME sucuk
+
; build nodes aliases
-jonund0 IN CNAME jonund
-jonund1 IN CNAME jonund
ecosse0 IN CNAME ecosse
ecosse1 IN CNAME ecosse
-
-; temporary, until the vm is moved elsewhere
-forums IN CNAME alamut
-forum IN CNAME alamut
-;wiki IN A 88.191.83.84
+rabbit0 IN CNAME rabbit
+rabbit1 IN CNAME rabbit
+rabbit2 IN CNAME rabbit
+ec2aa1-a IN CNAME ec2aa1
+ec2aa1-b IN CNAME ec2aa1
+ec2aa2-a IN CNAME ec2aa2
+ec2aa2-b IN CNAME ec2aa2
+ec2aa3-a IN CNAME ec2aa3
+ec2aa3-b IN CNAME ec2aa3
+ec2x1-a IN CNAME ec2x1
+ec2x1-b IN CNAME ec2x1
+ec2x2-a IN CNAME ec2x2
+ec2x2-b IN CNAME ec2x2
+pktaa1-a IN CNAME pktaa1
+pktaa1-b IN CNAME pktaa1
+pktaa1-c IN CNAME pktaa1
+pktaa1-d IN CNAME pktaa1
+pktaa1-e IN CNAME pktaa1
+pktaa1-f IN CNAME pktaa1
+ociaa1-a IN CNAME ociaa1
+ociaa1-b IN CNAME ociaa1
+ociaa1-c IN CNAME ociaa1
+ociaa2-a IN CNAME ociaa2
+ociaa2-b IN CNAME ociaa2
+ociaa2-c IN CNAME ociaa2
+ncaa1-a IN CNAME ncaa1
+ncaa1-b IN CNAME ncaa1
+ncaa1-c IN CNAME ncaa1
+
+<%# vim: set filetype=bindzone : -%>
diff --git a/deployment/forums/manifests/init.pp b/deployment/forums/manifests/init.pp
index d75bdb75..0ff256cd 100644
--- a/deployment/forums/manifests/init.pp
+++ b/deployment/forums/manifests/init.pp
@@ -5,18 +5,18 @@ class forums {
phpbb::instance { 'de': }
phpbb::redirection_instance{ 'fr':
- url => 'https://forums.mageia.org/en/viewforum.php?f=19'
+ url => "https://forums.${::domain}/en/viewforum.php?f=19"
}
phpbb::redirection_instance{ 'es':
- url => 'https://forums.mageia.org/en/viewforum.php?f=22'
+ url => "https://forums.${::domain}/en/viewforum.php?f=22"
}
phpbb::redirection_instance{ 'zh-cn':
- url => 'https://forums.mageia.org/en/viewforum.php?f=27'
+ url => "https://forums.${::domain}/en/viewforum.php?f=27"
}
phpbb::redirection_instance{ 'pt-br':
- url => 'https://forums.mageia.org/en/viewforum.php?f=28'
+ url => "https://forums.${::domain}/en/viewforum.php?f=28"
}
}
diff --git a/deployment/lists/manifests/init.pp b/deployment/lists/manifests/init.pp
index 511caa9a..3f06aa1f 100644..100755
--- a/deployment/lists/manifests/init.pp
+++ b/deployment/lists/manifests/init.pp
@@ -1,197 +1,377 @@
class lists {
- sympa::public_list {'atelier-discuss':
+ # When adding a new list, also add it to the wiki page :
+ # https://wiki.mageia.org/en/Mailing_lists
+
+ # Note: an e-mail of "FOO@group.${::domain}" corresponds to LDAP group
+ # mga-FOO, but only for a user sending from that group; the members are
+ # not expanded. subscriber_ldap_group and sender_ldap_group are expanded to
+ # their group members.
+
+ sympa::list::announce { 'announce':
+ subject => 'Mageia announces',
+ reply_to => "discuss@ml.${::domain}",
+ sender_email => [ "sysadmin@group.${::domain}"],
+ }
+
+ sympa::list::announce {'atelier-bugs':
+ subject => 'Atelier bug reports from bugzilla',
+ reply_to => "atelier-discuss@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'atelier',
+ }
+
+ sympa::list::announce {'atelier-commits':
+ subject => 'Commits on atelier repositories (Artwork, Web, etc ...)',
+ reply_to => "atelier-discuss@ml.${::domain}",
+ sender_email => [ "root@${::domain}", "subversion_noreply@ml.${::domain}" ],
+ topics => 'atelier',
+ }
+
+ sympa::list::public {'atelier-discuss':
subject => 'Discussions about artwork, web, marketing, communication',
topics => 'atelier',
}
+ sympa::list::public {'basesystem':
+ subject => 'Development discussion list about mageia basesystem',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::private { 'blog-moderation':
+ subject => 'Blog comments moderation',
+ subscriber_ldap_group => 'mga-blog-moderators',
+ sender_email => [ "wordpress@blog.${::domain}" ],
+ topics => 'atelier',
+ }
+
+ sympa::list::public {'bugsquad-discuss':
+ subject => 'Bugsquad team discussions',
+ topics => 'bugsquad',
+ }
+
+ sympa::list::public {'dev':
+ subject => 'Development discussion list',
+ topics => 'developers',
+ }
+
+ sympa::list::public {'discuss':
+ subject => 'General discussion list',
+ topics => 'users',
+ }
+
+ sympa::list::public {'gnome':
+ subject => 'Development discussion list about mageia Gnome integration',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'i18n-discuss':
+ subject => 'Translation team discussions',
+ topics => 'i18n',
+ }
+
+ sympa::list::announce { 'i18n-bugs':
+ subject => 'Translation bug reports from bugzilla',
+ reply_to => "i18n-discuss@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'i18n',
+ }
+
+ sympa::list {'i18n-reports':
+ subject => 'Automated reports for translations',
+ reply_to => "i18n-discuss@ml.${::domain}",
+ sender_subscriber => true,
+ sender_email => [
+ # 'r2d2@vargas.calenco.com',
+ # "blog@${::domain}",
+ "root@${::domain}",
+ "subversion_noreply@ml.${::domain}",
+ ],
+ topics => 'i18n',
+ }
+
# please check that the list use the proper code for
# language ( not to be confused with tld or country code )
- sympa::public_list {'i18n-af':
+ sympa::list::public {'i18n-af':
subject => 'Translation to Afrikaans',
topics => 'i18n',
}
- sympa::public_list {'i18n-de':
+ sympa::list::public {'i18n-ar':
+ subject => 'Translation to Arabic',
+ topics => 'i18n',
+ }
+
+ sympa::list::public {'i18n-de':
subject => 'Translation to German',
topics => 'i18n',
}
- sympa::public_list {'i18n-et':
+ sympa::list::public {'i18n-el':
+ subject => 'Translation to Greek',
+ topics => 'i18n',
+ }
+
+ sympa::list::public {'i18n-en':
+ subject => 'Translation to English',
+ topics => 'i18n',
+ }
+
+ sympa::list::public {'i18n-et':
subject => 'Translation to Estonian',
topics => 'i18n',
}
- sympa::public_list {'i18n-fr':
+ sympa::list::public {'i18n-fr':
subject => 'Translation to French',
topics => 'i18n',
}
- sympa::public_list {'i18n-el':
- subject => 'Translation to Greek',
+ sympa::list::public {'i18n-hu':
+ subject => 'Translation to Hungarian',
topics => 'i18n',
}
- sympa::public_list {'i18n-nl':
- subject => 'Translation to Dutch',
+ sympa::list::public {'i18n-it':
+ subject => 'Translation to Italian',
topics => 'i18n',
}
- sympa::public_list {'i18n-pt_br':
- subject => 'Translation to Brazilian Portuguese',
+ sympa::list::public {'i18n-nl':
+ subject => 'Translation to Dutch',
topics => 'i18n',
}
- sympa::public_list {'i18n-pl':
+ sympa::list::public {'i18n-pl':
subject => 'Translation to Polish',
topics => 'i18n',
}
- sympa::public_list {'i18n-ru':
- subject => 'Translation to Russian',
+ sympa::list::public {'i18n-pt_br':
+ subject => 'Translation to Brazilian Portuguese',
topics => 'i18n',
}
- sympa::public_list {'i18n-tr':
- subject => 'Translation to Turkish',
+ sympa::list::public {'i18n-pt_pt':
+ subject => 'Translation to Portuguese',
topics => 'i18n',
}
- sympa::public_list {'i18n-it':
- subject => 'Translation to Italian',
+ sympa::list::public {'i18n-ro':
+ subject => 'Translation to Romanian',
topics => 'i18n',
}
- sympa::public_list {'i18n-en':
- subject => 'Translation to English',
+ sympa::list::public {'i18n-ru':
+ subject => 'Translation to Russian',
topics => 'i18n',
}
- sympa::public_list {'i18n-ro':
- subject => 'Translation to Romanian',
+ sympa::list::public {'i18n-tr':
+ subject => 'Translation to Turkish',
topics => 'i18n',
}
- sympa::public_list {'i18n-zh_tw':
+ sympa::list::public {'i18n-zh_tw':
subject => 'Translation to Taiwanese',
topics => 'i18n',
}
- sympa::public_list {'qa-discuss':
+ sympa::list::public {'isobuild':
+ subject => 'Development discussion list about Mageia isos',
+ topics => 'developers',
+ }
+
+ sympa::list::public {'java':
+ subject => 'Development discussion list about Java',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'kde':
+ subject => 'Development discussion list about KDE',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'kernel':
+ subject => 'Development discussion list about Kernel',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'mageiatools':
+ subject => 'Development discussion list about Mageiatools',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'perl':
+ subject => 'Development discussion list about Perl',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'php':
+ subject => 'Development discussion list about Php',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'python':
+ subject => 'Development discussion list about Python',
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::public {'qa-discuss':
subject => 'Discussions about QA tasks and requests',
topics => 'qa',
}
- sympa::announce_list_email {'qa-bugs':
+ sympa::list::public {'rpmstack':
+ subject => 'Development discussion list about Mageia rpm stack',
+ topics => 'developers',
+ }
+
+ sympa::list::announce {'qa-bugs':
subject => 'QA bug reports from bugzilla',
- reply_to => "qa-discuss@ml.$::domain",
- sender_email => "bugzilla-daemon@$::domain",
+ reply_to => "qa-discuss@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
topics => 'qa',
}
- sympa::public_list {'forums-discuss':
- subject => 'Discuss forums matters, policies and processes, as well as publish summaries of notable events/feedback',
+ sympa::list::announce {'qa-reports':
+ subject => 'Automated reports from QA tools',
+ reply_to => "qa-discuss@ml.${::domain}",
+ sender_email => [ "buildsystem-daemon@${::domain}" ],
+ topics => 'qa',
+ }
+
+ sympa::list::announce {'qa-commits':
+ subject => 'Update advisories commits',
+ reply_to => "qa-discuss@ml.${::domain}",
+ sender_email => [ "root@${::domain}", "subversion_noreply@ml.${::domain}" ],
+ topics => 'qa',
+ }
+
+ sympa::list::public {'forums-discuss':
+ subject => 'Discuss forums matters, policies and processes, and publish summaries of notable events/feedback',
topics => 'forums',
}
- sympa::announce_list_email {'forums-bugs':
+ sympa::list::announce {'forums-bugs':
subject => 'Forums bug reports from bugzilla',
- reply_to => "forums-discuss@ml.$::domain",
- sender_email => "bugzilla-daemon@$::domain",
+ reply_to => "forums-discuss@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
topics => 'forums',
}
- sympa::public_list {'doc-discuss':
+ sympa::list::public {'doc-discuss':
subject => 'Discussions about Mageia documentation',
topics => 'doc',
}
- sympa::announce_list_email { 'doc-bugs':
+ sympa::list::announce { 'doc-bugs':
subject => 'Documentation bug reports from bugzilla',
- reply_to => "doc-discuss@ml.$::domain",
- sender_email => "bugzilla-daemon@$::domain",
+ reply_to => "doc-discuss@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
topics => 'doc',
}
- sympa::announce_list_email { 'packages-commits':
+ sympa::list::announce { 'packages-commits':
subject => 'Commits on packages repository',
- # FIXME change once we migrate
- reply_to => "mageia-dev@$::domain",
- sender_email => "root@$::domain",
+ reply_to => "dev@ml.${::domain}",
+ sender_email => [ "subversion_noreply@ml.${::domain}", "binrepo_noreply@ml.${::domain}" ],
topics => 'developers',
}
- sympa::announce_list_email { 'mirrors-announce':
+ sympa::list::announce { 'mirrors-announce':
subject => 'Important announces about mirrors updates',
- # FIXME change once we migrate
- reply_to => "mageia-sysadm@$::domain",
- sender_email => "root@$::domain",
+ reply_to => "sysadmin-discuss@ml.${::domain}",
+ sender_email => [ "root@${::domain}" ],
topics => 'sysadmin',
}
- sympa::announce_list_email {'sysadmin-commits':
+ sympa::list::announce {'sysadmin-commits':
subject => 'Commits on sysadmin repository',
- # FIXME change once we migrate
- reply_to => "mageia-sysadm@$::domain",
- sender_email => "root@$::domain",
+ reply_to => "sysadmin-discuss@ml.${::domain}",
+ sender_email => [ "root@${::domain}", "subversion_noreply@ml.${::domain}" ],
+ topics => 'sysadmin',
+ }
+
+ sympa::list::public {'sysadmin-discuss':
+ subject => 'Sysadmin team discussions',
topics => 'sysadmin',
}
- sympa::announce_list_email {'sysadmin-reports':
+ sympa::list::announce {'sysadmin-reports':
subject => 'Automated reports from various pieces of infrastructure',
- # FIXME change once we migrate
- reply_to => "mageia-sysadm@$::domain",
- sender_email => "root@$::domain",
+ reply_to => "sysadmin-discuss@ml.${::domain}",
+ sender_email => [ "root@${::domain}" ],
topics => 'sysadmin',
}
- sympa::announce_list_email { 'sysadmin-bugs':
+ sympa::list::announce { 'sysadmin-bugs':
subject => 'Sysadmin bug reports from bugzilla',
- # FIXME change once we migrate
- reply_to => "mageia-sysadm@$::domain",
- sender_email => "bugzilla-daemon@$::domain",
+ reply_to => "sysadmin-discuss@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
topics => 'sysadmin',
+ critical => true,
}
- sympa::announce_list_email { 'soft-commits':
+ sympa::list::announce { 'soft-commits':
subject => 'Commits on soft repository',
- # FIXME change once we migrate
- reply_to => "mageia-dev@$::domain",
- sender_email => "root@$::domain",
+ reply_to => "dev@ml.${::domain}",
+ sender_email => [ "root@${::domain}", "subversion_noreply@ml.${::domain}" ],
topics => 'developers',
}
- sympa::announce_list_email { 'bugs':
+ sympa::list::announce { 'bugs':
subject => 'Bug reports from bugzilla',
- # FIXME change once we migrate
- reply_to => "mageia-dev@$::domain",
- sender_email => "bugzilla-daemon@$::domain",
+ reply_to => "dev@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
+ topics => 'developers',
+ }
+
+ sympa::list::announce { 'pkg-bugs':
+ subject => 'Packaging bug reports from bugzilla',
+ reply_to => "dev@ml.${::domain}",
+ sender_email => [ "bugzilla_noreply@ml.${::domain}" ],
topics => 'developers',
}
- sympa::announce_list_email { 'updates-announce':
+ sympa::list::announce { 'updates-announce':
subject => 'Packages update for stable release',
- reply_to => "mageia-dev@$::domain",
- sender_email => "buildsystem-daemon@$::domain",
+ reply_to => "dev@ml.${::domain}",
+ sender_email => [ "buildsystem-daemon@${::domain}" ],
topics => 'developers',
}
- sympa::announce_list_email { 'changelog':
+ sympa::list::announce { 'backports-announce':
+ subject => 'Package backports for stable release',
+ reply_to => "dev@ml.${::domain}",
+ sender_ldap_group => "mga-qa-committers",
+ topics => 'developers',
+ }
+
+ sympa::list::announce { 'changelog':
subject => 'Announces for new packages uploaded',
- # FIXME change once we migrate
- reply_to => "mageia-dev@$::domain",
- sender_email => "buildsystem-daemon@$::domain",
+ reply_to => "dev@ml.${::domain}",
+ sender_email => [ "buildsystem-daemon@${::domain}" ],
topics => 'developers',
}
- sympa::announce_list_email { 'board-commits':
+ sympa::list::announce { 'board-commits':
subject => 'Commits on Mageia.Org status and organisation documents',
- reply_to => "board-public@ml.$::domain",
- sender_email => "root@$::domain",
+ reply_to => "board-public@ml.${::domain}",
+ sender_email => [ "root@${::domain}", "subversion_noreply@ml.${::domain}" ],
topics => 'governance',
subscriber_ldap_group => 'mga-board',
}
- sympa::public_restricted_list { 'board-public':
+ sympa::list::public_restricted { 'board-public':
subject => 'Public board discussion',
subscriber_ldap_group => 'mga-board',
topics => 'governance',
@@ -203,14 +383,38 @@ class lists {
topics => 'governance',
}
- sympa::public_restricted_list { 'council':
+ sympa::list::announce {'treasurer-commits':
+ subject => 'Commits on Mageia.Org association treasurer repository',
+ reply_to => "treasurer@${::domain}",
+ sender_email => [ "root@${::domain}", "subversion_noreply@ml.${::domain}" ],
+ topics => 'governance',
+ }
+
+ sympa::list::public_restricted { 'council':
subject => 'Council discussions',
subscriber_ldap_group => 'mga-council',
topics => 'governance',
}
- sympa::public_list {'local-discuss':
+ sympa::list::public {'local-discuss':
subject => 'Discussions about Local Community Team',
topics => 'local',
}
+
+ sympa::list::public {'discuss-fr':
+ subject => 'French discussions about Mageia',
+ topics => 'users',
+ }
+
+ sympa::list::public {'discuss-pt-br':
+ subject => 'Discussions about Mageia in Brazilian Portuguese',
+ topics => 'users',
+ }
+
+ sympa::list::private { 'mageia-association-members':
+ subject => 'Discussions between association members',
+ subscriber_ldap_group => 'mga-association-members',
+ topics => 'Mageia Association Members',
+ }
}
+
diff --git a/deployment/main_mirror/files/mirror/mirror.readme b/deployment/main_mirror/files/mirror/mirror.readme
index 3fd50aa4..5846d12e 100644
--- a/deployment/main_mirror/files/mirror/mirror.readme
+++ b/deployment/main_mirror/files/mirror/mirror.readme
@@ -10,27 +10,37 @@ This document describes the way to implement a Mageia Mirror.
1) Prerequisite
---------------
-The expected size of the mirror is around 700GB.
+The expected size of the mirror is around 1.8-2TB.
+The mirror only contains the last 2 stable releases + the development
+branch called cauldron.
+
+For older unsupported releases, you can find them at the archive
+provided by distrib-coffee mirror:
+https://distrib-coffee.ipsl.jussieu.fr/pub/linux/Mageia-archive/
+
+Look here to see the (planned) end of support date for each version:
+https://www.mageia.org/en/support/#lifecycle
You need rsync software to synchronise the tree.
2) Official source
------------------
-For public mirror, we encourage you to use one of our Tier1 mirror.
+For a public mirror, we encourage you to use one of our Tier1 mirrors.
+
+The servers below synchronise the tree directly from the Mageia rsync server.
-Theses servers synchronises the tree directly from the Mageia rsync server.
+ Check https://mirrors.mageia.org/ for their bandwidths.
+ Check https://mirrors.mageia.org/status for their current statuses.
- o rsync://distrib-coffee.ipsl.jussieu.fr/mageia/
- located in Paris (France)
- o rsync://ftp.LinuxCabal.org/Mageia/
- located in Lucern (Switzerland)
o rsync://mageia.c3sl.ufpr.br/mageia/
located in Curitiba (Brasil)
o rsync://mirrors.kernel.org/mirrors/mageia/
located in USA and Europe
- o rsync://ftp.acc.umu.se/mirror/mageia/
+ o rsync://mirror.accum.se/mirror/mageia/
located in Umea (Sweden)
+ o rsync://mirror.math.princeton.edu/pub/mageia/
+ located in Princeton (USA)
3) Rsync options
----------------
@@ -50,6 +60,7 @@ load on the remote server
-----------------------------
The tree must be synchronized at least every 2 hours.
+Tier 1 mirrors should preferably sync at least every hour.
Please ensure that another rsync process is not started while a first
one is still running. Use a lock file.
@@ -57,7 +68,7 @@ one is still running. Use a lock file.
5) Registering your mirror
--------------------------
-Goto at http://mirrors.mageia.org/new and enter all possible protocol.
+Go to https://mirrors.mageia.org/new and enter all possible protocols.
6) Subscribe to the mirrors-announce mailing list
-------------------------------------------------
diff --git a/deployment/main_mirror/manifests/init.pp b/deployment/main_mirror/manifests/init.pp
index a4e8cc76..9b26a64d 100644
--- a/deployment/main_mirror/manifests/init.pp
+++ b/deployment/main_mirror/manifests/init.pp
@@ -8,14 +8,14 @@ class main_mirror {
$mirror = '/distrib'
file { [$mirror,
- "$mirror/mirror",
- "$mirror/archive"]:
+ "${mirror}/mirror",
+ "${mirror}/archive"]:
ensure => directory,
}
file {
- "$mirror/README": source => 'puppet:///modules/main_mirror/README';
- "$mirror/mirror/mirror.readme": source => 'puppet:///modules/main_mirror/mirror/mirror.readme';
- "$mirror/mirror/paths.readme": source => 'puppet:///modules/main_mirror/mirror/paths.readme';
+ "${mirror}/README": source => 'puppet:///modules/main_mirror/README';
+ "${mirror}/mirror/mirror.readme": source => 'puppet:///modules/main_mirror/mirror/mirror.readme';
+ "${mirror}/mirror/paths.readme": source => 'puppet:///modules/main_mirror/mirror/paths.readme';
}
}
diff --git a/deployment/main_mirror/templates/rsyncd.conf b/deployment/main_mirror/templates/rsyncd.conf
index a8d38b82..9fc93860 100644
--- a/deployment/main_mirror/templates/rsyncd.conf
+++ b/deployment/main_mirror/templates/rsyncd.conf
@@ -8,45 +8,24 @@ gid = nogroup
comment = Mageia Mirror Tree
hosts allow = \
10.42.0.0/24 \
- rabbit.<%= domain %> \
- alamut.<%= domain %> \
- arm1.<%= domain %> \
- arm2.<%= domain %> \
+ 2a02:2178:2:7::/64 \
+ rabbit.<%= @domain %> \
+ sucuk.<%= @domain %> \
distrib-coffee.ipsl.jussieu.fr \
- distribipsl.aero.jussieu.fr \
- ibiblio.org \
- 152.46.7.122 \
- 152.19.134.16 \
- 152.19.134.30 \
- 2610:28:3090:3000:250:56ff:fe96:23 \
- ftp.LinuxCabal.org \
- 178.22.68.98 \
- sagres.c3sl.ufpr.br \
- 2801:82:80ff:8000::c \
- 2801:82:80ff:8000::2 \
- zeus1.kernel.org \
- zeus2.kernel.org \
- zeus3.kernel.org \
- zeus4.kernel.org \
- 2001:4f8:8:10::/64 \
- 2001:4f8:1:10::/64 \
- 2001:500:60:10::/64 \
- 2001:6b0:e:4017::/64 \
- churchill.acc.umu.se \
- 130.239.18.141 \
- 2001:6b0:e:2018::141 \
-
-[newrelease]
- path = /distrib/newrelease/
- comment = Mageia Next Release
- hosts allow = \
- rabbit.<%= domain %> \
-
-[bootstrap]
- path = /distrib/bootstrap/
- comment = Mageia Bootstrap
- hosts allow = \
- rabbit.<%= domain %> \
- distrib-coffee.ipsl.jussieu.fr \
- distribipsl.aero.jussieu.fr \
+ ftp.proxad.net \
+ jobbot0.ibiblio.org \
+ jobbot1.ibiblio.org \
+ mirror.math.princeton.edu \
+ poincare.accum.se \
+ poincare.acc.umu.se \
+ sagres.c3sl.ufpr.br \
+ sv.mirrors.kernel.org \
+ ny.mirrors.kernel.org \
+ 147.75.69.246 \
+ 2001:14ba:a417:eb00::1 \
+ 2001:14ba:a417:eb00::2
+[git]
+ path = /git
+ comment = Mageia Git repos
+ hosts allow = sucuk.<%= @domain %>
diff --git a/deployment/mga_buildsystem/manifests/buildnode.pp b/deployment/mga_buildsystem/manifests/buildnode.pp
new file mode 100644
index 00000000..f6bf70ea
--- /dev/null
+++ b/deployment/mga_buildsystem/manifests/buildnode.pp
@@ -0,0 +1,4 @@
+class mga_buildsystem::buildnode {
+ include mga_buildsystem::config
+ include buildsystem::buildnode
+}
diff --git a/deployment/mga_buildsystem/manifests/config.pp b/deployment/mga_buildsystem/manifests/config.pp
new file mode 100644
index 00000000..c0c62cc4
--- /dev/null
+++ b/deployment/mga_buildsystem/manifests/config.pp
@@ -0,0 +1,668 @@
+class mga_buildsystem::config {
+ class { 'buildsystem::var::signbot':
+ keyid => '80420F66',
+ keyemail => "packages@${::domain}",
+ keyname => 'Mageia Packages',
+ }
+
+ class { 'buildsystem::var::groups':
+ packagers => 'mga-packagers',
+ packagers_committers => 'mga-packagers-committers',
+ }
+
+ class { 'buildsystem::var::webstatus' :
+ package_commit_url => "https://svnweb.${::domain}/packages?view=revision&revision=%d",
+ theme_name => 'mageia',
+ }
+
+ class { 'buildsystem::var::iurt':
+ timeout_multiplier => $::architecture ? {
+ /arm/ => 4,
+ 'aarch64' => 2,
+ default => 1,
+ }
+ }
+
+ class { 'buildsystem::var::scheduler' :
+ admin_mail => "sysadmin@group.${::domain}",
+ build_nodes => {
+ 'i586' => [ 'ecosse0', 'rabbit0', 'ecosse1', 'rabbit1', 'rabbit2' ],
+ 'i686' => [ 'ecosse0', 'rabbit0', 'ecosse1', 'rabbit1', 'rabbit2' ],
+ 'x86_64' => [ 'rabbit0', 'ecosse0', 'rabbit1', 'ecosse1', 'rabbit2' ],
+ 'armv7hl' => [ 'ncaa1-a', 'ncaa1-b', 'ncaa1-c', 'ociaa1-a', 'ociaa1-b'],
+ 'aarch64' => [ 'ncaa1-a', 'ncaa1-b', 'ncaa1-c', 'ociaa1-a', 'ociaa1-b'],
+ },
+ build_nodes_aliases => {
+ 'ecosse0' => "ecosse.${::domain}",
+ 'ecosse1' => "ecosse.${::domain}",
+ 'rabbit0' => "rabbit.${::domain}",
+ 'rabbit1' => "rabbit.${::domain}",
+ 'rabbit2' => "rabbit.${::domain}",
+ 'ociaa1-a' => "ociaa1.${::domain}",
+ 'ociaa1-b' => "ociaa1.${::domain}",
+ 'ociaa1-c' => "ociaa1.${::domain}",
+ },
+ build_src_node => 'duvel',
+ }
+ include buildsystem::var::repository
+ class { 'buildsystem::var::binrepo':
+ uploadmail_from => "binrepo_noreply@ml.${::domain}",
+ uploadmail_to => "packages-commits@ml.${::domain}",
+ }
+
+ $svn_hostname = "svn.${::domain}"
+ $svn_root_packages = "svn://${svn_hostname}/svn/packages"
+ $svn_root_packages_ssh = "svn+ssh://${svn_hostname}/svn/packages"
+ class { 'buildsystem::var::mgarepo':
+ submit_host => "pkgsubmit.${::domain}",
+ svn_hostname => $svn_hostname,
+ svn_root_packages => $svn_root_packages,
+ svn_root_packages_ssh => $svn_root_packages_ssh,
+ oldurl => "${svn_root_packages_ssh}/misc",
+ conf => {
+ 'global' => {
+ 'ldap-server' => "ldap.${::domain}",
+ 'ldap-base' => "ou=People,${::dc_suffix}",
+ 'ldap-filterformat' => '(&(objectClass=inetOrgPerson)(uid=$username))',
+ 'ldap-resultformat' => '$cn <$mail>',
+ }
+ }
+ }
+
+ include stdlib
+
+ $std_arch = ['x86_64', 'i586']
+ $x86_arch = ['x86_64', 'i686']
+ $arm32_arch = ['armv5tl', 'armv7hl']
+ $std_repos = {
+ 'release' => {
+ 'media_type' => [ 'release' ],
+ 'requires' => [],
+ 'order' => 0,
+ },
+ 'updates' => {
+ 'media_type' => [ 'updates' ],
+ 'updates_for' => 'release',
+ 'requires' => [ 'release' ],
+ 'order' => 1,
+ },
+ 'updates_testing' => {
+ 'media_type' => [ 'testing' ],
+ 'noauto' => '1',
+ 'requires' => [ 'updates' ],
+ 'order' => 2,
+ },
+ 'backports' => {
+ 'media_type' => [ 'backports' ],
+ 'noauto' => '1',
+ 'requires' => [ 'updates' ],
+ 'order' => 3,
+ },
+ 'backports_testing' => {
+ 'media_type' => [ 'testing' ],
+ 'noauto' => '1',
+ 'requires' => [ 'backports' ],
+ 'order' => 4,
+ },
+ }
+ $std_medias = {
+ 'core' => {
+ 'repos' => $std_repos,
+ 'media_type' => [ 'official', 'free' ],
+ 'order' => 0,
+ },
+ 'nonfree' => {
+ 'repos' => $std_repos,
+ 'media_type' => [ 'official' ],
+ 'noauto' => '1',
+ 'requires' => [ 'core' ],
+ 'order' => 1,
+ },
+ 'tainted' => {
+ 'repos' => $std_repos,
+ 'media_type' => [ 'official' ],
+ 'noauto' => '1',
+ 'requires' => [ 'core' ],
+ 'order' => 2,
+ },
+ }
+ $std_base_media = [ 'core/release', 'core/updates' ]
+ $infra_medias = {
+ 'infra' => {
+ 'repos' => {
+ 'updates' => {
+ 'media_type' => [ 'updates' ],
+ 'requires' => [ 'release' ],
+ 'order' => 0,
+ },
+ },
+ 'media_type' => [ 'infra' ],
+ 'requires' => [ 'core' ],
+ 'order' => 0,
+ },
+ }
+ $std_macros = {
+ 'distsuffix' => '.mga',
+ 'distribution' => 'Mageia',
+ 'vendor' => 'Mageia.Org',
+ '_real_vendor' => 'mageia',
+ }
+ $repo_allow_from_ips = [
+ $::nodes_ipaddr[duvel][ipv6],
+ $::nodes_ipaddr[duvel][ipv4],
+ $::nodes_ipaddr[ecosse][ipv6],
+ $::nodes_ipaddr[ecosse][ipv4],
+ $::nodes_ipaddr[fiona][ipv6],
+ $::nodes_ipaddr[fiona][ipv4],
+ '10.42.0',
+ $::nodes_ipaddr[rabbit][ipv4],
+ $::nodes_ipaddr[rabbit][ipv6],
+ $::nodes_ipaddr[sucuk][ipv4],
+ $::nodes_ipaddr[sucuk][ipv6],
+ '85.134.55.73',
+ $::nodes_ipaddr[neru][ipv4],
+ $::nodes_ipaddr[neru][ipv6],
+ '2001:bc8:4400:2700::2729',
+ '147.75.83.250',
+ '2604:1380:2000:f100::1',
+ '2a05:d014:e9:2c00::/56',
+ '147.75.69.244/30',
+ '2604:1380:1001:4900::/127',
+ # Will be new neru
+ '51.15.220.93',
+ '2001:bc8:628:1f00::1',
+ # Oracle cloud VMs
+ '2603:c026:c101:f00::/64',
+ $::nodes_ipaddr[ncaa1][ipv4],
+ $::nodes_ipaddr[ncaa1][ipv6],
+ ]
+ $repo_allow_from_domains = [
+ ".${::domain}",
+ ]
+
+ # the list of checks, actions, posts for cauldron in youri-upload
+ $cauldron_youri_upload_targets = {
+ 'checks' => [
+ 'version',
+ 'tag',
+ 'acl',
+ 'rpmlint',
+ 'recency',
+ ],
+ 'actions' => [
+ 'markrelease',
+ 'sign',
+ 'install',
+ 'link',
+ 'unpack_release_notes',
+ 'unpack_gfxboot_theme',
+ 'unpack_meta_task',
+ 'unpack_installer_images',
+ 'unpack_installer_images_nonfree',
+ 'unpack_installer_stage2',
+ 'unpack_installer_advertising',
+ 'unpack_installer_rescue',
+ 'unpack_syslinux',
+ 'unpack_pci_usb_ids',
+ 'archive',
+ 'mail',
+ 'maintdb',
+ ],
+ 'posts' => [
+ 'genhdlist2_zstd',
+ 'createrepo_cauldron',
+ 'appstream_cauldron',
+ 'clean_rpmsrate',
+ 'mirror',
+ ],
+ }
+
+ # TODO: mga >= 6 should use std config + createrepo, not a different one
+ $mga6_youri_upload_targets = {
+ 'checks' => [
+ 'version',
+ 'tag',
+ 'acl',
+ 'rpmlint',
+ 'recency',
+ ],
+ 'actions' => [
+ 'sign',
+ 'install',
+ 'link',
+ 'archive',
+ 'mail',
+ ],
+ 'posts' => [
+ 'genhdlist2',
+ 'createrepo_mga6',
+ 'appstream_mga6',
+ 'mirror',
+ ],
+ }
+
+ $mga7_youri_upload_targets = {
+ 'checks' => [
+ 'version',
+ 'tag',
+ 'acl',
+ 'rpmlint',
+ 'recency',
+ ],
+ 'actions' => [
+ 'sign',
+ 'install',
+ 'link',
+ 'archive',
+ 'mail',
+ ],
+ 'posts' => [
+ 'genhdlist2',
+ 'createrepo_mga7',
+ 'appstream_mga7',
+ 'mirror',
+ ],
+ }
+
+ $mga8_youri_upload_targets = {
+ 'checks' => [
+ 'version',
+ 'tag',
+ 'acl',
+ 'rpmlint',
+ 'recency',
+ ],
+ 'actions' => [
+ 'sign',
+ 'install',
+ 'link',
+ 'archive',
+ 'mail',
+ ],
+ 'posts' => [
+ 'genhdlist2',
+ 'createrepo_mga8',
+ 'appstream_mga8',
+ 'mirror',
+ ],
+ }
+
+ $mga9_youri_upload_targets = {
+ 'checks' => [
+ 'version',
+ 'tag',
+ 'acl',
+ 'rpmlint',
+ 'recency',
+ ],
+ 'actions' => [
+ 'sign',
+ 'install',
+ 'link',
+ 'archive',
+ 'mail',
+ ],
+ 'posts' => [
+ 'genhdlist2',
+ 'createrepo_mga9',
+ 'appstream_mga9',
+ 'mirror',
+ ],
+ }
+
+ # the list of checks, actions, posts for infra distros in youri-upload
+ $infra_youri_upload_targets = {
+ 'checks' => [
+ 'version',
+ 'tag',
+ 'acl',
+ 'rpmlint',
+ 'recency',
+ ],
+ 'actions' => [
+ 'sign',
+ 'install',
+ 'link',
+ 'archive',
+ ],
+ 'posts' => [
+ 'genhdlist2',
+ ],
+ }
+
+ # the list of checks, actions, posts for cauldron in youri-todo
+ $cauldron_youri_todo_targets = {
+ 'checks' => [
+ 'source',
+ 'deps',
+ 'version',
+ 'tag',
+ 'acl',
+ 'host',
+ 'rpmlint',
+ 'recency',
+ 'queue_recency',
+ ],
+ 'actions' => [
+ 'send',
+ 'dependencies',
+ 'rpminfo',
+ 'ulri',
+ ],
+ }
+
+ # the list of checks, actions, posts for stable and infra distros in youri-todo
+ $std_youri_todo_targets = {
+ 'checks' => [
+ 'source',
+ 'version',
+ 'tag',
+ 'acl',
+ 'host',
+ 'rpmlint',
+ 'recency',
+ 'queue_recency',
+ ],
+ 'actions' => [
+ 'send',
+ 'dependencies',
+ 'rpminfo',
+ 'ulri',
+ ],
+ }
+
+ # rpmlint check options for stable and cauldron
+ $mga_rpmlint = {
+ 'config' => '/usr/share/rpmlint/config',
+ 'path' => '/usr/bin/rpmlint',
+ 'results' => [
+ 'buildprereq-use',
+ 'no-description-tag',
+ 'no-summary-tag',
+ 'non-standard-group',
+ 'non-xdg-migrated-menu',
+ 'percent-in-conflicts',
+ 'percent-in-dependency',
+ 'percent-in-obsoletes',
+ 'percent-in-provides',
+ 'summary-ended-with-dot',
+ 'unexpanded-macro',
+ 'unknown-lsb-keyword',
+ 'malformed-line-in-lsb-comment-block',
+ 'empty-%postun',
+ 'empty-%post',
+ 'invalid-desktopfile',
+ 'standard-dir-owned-by-package',
+ 'use-tmp-in-%postun',
+ 'bogus-variable-use-in-%posttrans',
+ 'dir-or-file-in-usr-local',
+ 'dir-or-file-in-tmp',
+ 'dir-or-file-in-mnt',
+ 'dir-or-file-in-opt',
+ 'dir-or-file-in-home',
+ 'dir-or-file-in-var-local',
+ 'tmpfiles-conf-in-etc',
+ 'non-ghost-in-run',
+ 'non-ghost-in-var-run',
+ 'non-ghost-in-var-lock',
+ 'systemd-unit-in-etc',
+ 'udev-rule-in-etc',
+ ],
+ }
+
+ # list of users allowed to submit packages when cauldron is frozen
+ $cauldron_authorized_users = str_join(group_members('mga-release_managers'), '|')
+ $cauldron_version_check = {
+ 'authorized_sections' => '^[a-z]+/updates_testing$',
+ #'authorized_sections' => 'none_section_authorized',
+ #'authorized_packages' => 'none_package_authorized',
+ 'authorized_packages' => 'drak|^(urpmi|perl-(MDK-Common|Gtk3|Glib(-Object-Introspection)?|URPM)|mgaonline|net_monitor|perl_checker|mandi|indexhtml|ldetect(-lst)?|msec|manatools|rpm-(mageia-setup|helper)|(mga-|mageia).*|iurt)$',
+ 'authorized_arches' => 'none',
+ 'authorized_users' => "^${cauldron_authorized_users}\$",
+ 'mode' => 'normal',
+ #'mode' => 'version_freeze',
+ #'mode' => 'freeze',
+ }
+
+ # for EOL distributions
+ $frozen_version_check = {
+ 'authorized_packages' => 'none_package_authorized',
+ 'authorized_sections' => 'none_section_authorized',
+ 'authorized_arches' => 'none',
+ 'mode' => 'freeze',
+ }
+
+ # for supported stable distributions
+ $std_version_check = {
+ 'authorized_packages' => 'none_package_authorized',
+ 'authorized_sections' => '^(core|nonfree|tainted)/(updates_testing|backports_testing)$',
+ 'authorized_arches' => 'none',
+ 'mode' => 'freeze',
+ }
+
+ $infra_authorized_users = str_join(group_members('mga-sysadmin'), '|')
+ $infra_version_check = {
+ 'authorized_users' => "^${infra_authorized_users}\$",
+ 'mode' => 'freeze',
+ }
+
+ class { 'buildsystem::var::distros':
+ default_distro => 'cauldron',
+ repo_allow_from_ips => $repo_allow_from_ips,
+ repo_allow_from_domains => $repo_allow_from_domains,
+ distros => {
+ 'cauldron' => {
+ 'arch' => concat($x86_arch, ['armv7hl', 'aarch64']),
+ 'mandatory_arch' => concat($x86_arch, ['aarch64']),
+ 'no_media_cfg_update' => true,
+ 'medias' => $std_medias,
+ 'base_media' => $std_base_media,
+ 'branch' => 'Devel',
+ 'version' => '10',
+ 'submit_allowed' => "${svn_root_packages}/cauldron",
+ 'macros' => $std_macros,
+ 'youri' => {
+ 'upload' => {
+ 'targets' => $cauldron_youri_upload_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ },
+ },
+ 'todo' => {
+ 'targets' => $cauldron_youri_todo_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ 'version' => $cauldron_version_check,
+ },
+ },
+ },
+ },
+
+
+ '8' => {
+ 'arch' => concat($std_arch, ['armv7hl', 'aarch64']),
+ 'mandatory_arch' => concat($std_arch, ['aarch64']),
+ 'no_media_cfg_update' => true,
+ 'medias' => $std_medias,
+ 'base_media' => $std_base_media,
+ 'branch' => 'Official',
+ 'version' => '8',
+ 'submit_allowed' => "${svn_root_packages}/updates/8",
+ 'backports_allowed' => "${svn_root_packages}/backports/8",
+ 'macros' => $std_macros,
+ 'youri' => {
+ 'upload' => {
+ 'targets' => $mga8_youri_upload_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ },
+ },
+ 'todo' => {
+ 'targets' => $std_youri_todo_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ 'version' => $std_version_check,
+ },
+ },
+ },
+ },
+
+ '9' => {
+ 'arch' => concat($std_arch, ['armv7hl', 'aarch64']),
+ 'mandatory_arch' => concat($std_arch, ['aarch64']),
+ 'no_media_cfg_update' => true,
+ 'medias' => $std_medias,
+ 'base_media' => $std_base_media,
+ 'branch' => 'Official',
+ 'version' => '9',
+ 'submit_allowed' => "${svn_root_packages}/updates/9",
+ 'backports_allowed' => "${svn_root_packages}/backports/9",
+ 'macros' => $std_macros,
+ 'youri' => {
+ 'upload' => {
+ 'targets' => $mga9_youri_upload_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ },
+ },
+ 'todo' => {
+ 'targets' => $std_youri_todo_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ 'version' => $std_version_check,
+ },
+ },
+ },
+ },
+
+ 'infra_8' => {
+ 'arch' => concat($std_arch, ['armv7hl', 'aarch64']),
+ 'medias' => $infra_medias,
+ 'base_media' => [ '8/core/release', '8/core/updates', 'infra/updates' ],
+ 'branch' => 'Official',
+ 'version' => '8',
+ 'submit_allowed' => "${svn_root_packages}/updates/infra_8",
+ 'macros' => $std_macros,
+ 'based_on' => {
+ '8' => {
+ 'core' => [ 'release', 'updates' ],
+ },
+ },
+ 'youri' => {
+ 'upload' => {
+ 'targets' => $infra_youri_upload_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ },
+ },
+ 'todo' => {
+ 'targets' => $std_youri_todo_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ 'version' => $infra_version_check,
+ },
+ },
+ },
+ 'no_mirror' => true,
+ },
+
+ 'infra_9' => {
+ 'arch' => concat($std_arch, ['armv7hl', 'aarch64']),
+ 'medias' => $infra_medias,
+ 'base_media' => [ '9/core/release', '9/core/updates', 'infra/updates' ],
+ 'branch' => 'Official',
+ 'version' => '9',
+ 'submit_allowed' => "${svn_root_packages}/updates/infra_9",
+ 'macros' => $std_macros,
+ 'based_on' => {
+ '9' => {
+ 'core' => [ 'release', 'updates' ],
+ },
+ },
+ 'youri' => {
+ 'upload' => {
+ 'targets' => $infra_youri_upload_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ },
+ },
+ 'todo' => {
+ 'targets' => $std_youri_todo_targets,
+ 'checks' => {
+ 'rpmlint' => $mga_rpmlint,
+ 'version' => $infra_version_check,
+ },
+ },
+ },
+ 'no_mirror' => true,
+ },
+ }
+ }
+ $checks_tag_options = {
+ 'tags' => {
+ 'release' => inline_template("^[^~]*<%= std_macros['distsuffix'] %>\\d+"),
+ 'distribution' => inline_template("^<%= std_macros['distribution'] %>"),
+ 'vendor' => inline_template("^<%= std_macros['vendor'] %>$"),
+ },
+ }
+ class { 'buildsystem::var::youri':
+ packages_archivedir => "${buildsystem::var::scheduler::homedir}/old",
+ youri_conf => {
+ 'upload' => {
+ 'checks' => {
+ 'tag' => {
+ 'options' => $checks_tag_options,
+ },
+ 'rpmlint' => {
+ 'options' => {
+ 'results' => [
+ 'buildprereq-use',
+ 'no-description-tag',
+ 'no-summary-tag',
+ 'non-standard-group',
+ 'non-xdg-migrated-menu',
+ 'percent-in-conflicts',
+ 'percent-in-dependency',
+ 'percent-in-obsoletes',
+ 'percent-in-provides',
+ 'summary-ended-with-dot',
+ 'unexpanded-macro',
+ 'unknown-lsb-keyword',
+ 'malformed-line-in-lsb-comment-block',
+ 'empty-%postun',
+ 'empty-%post',
+ 'invalid-desktopfile',
+ 'standard-dir-owned-by-package',
+ 'use-tmp-in-%postun',
+ 'bogus-variable-use-in-%posttrans',
+ 'dir-or-file-in-usr-local',
+ 'dir-or-file-in-tmp',
+ 'dir-or-file-in-mnt',
+ 'dir-or-file-in-opt',
+ 'dir-or-file-in-home',
+ 'dir-or-file-in-var-local',
+ ],
+ },
+ },
+ },
+ 'actions' => {
+ 'mail' => {
+ 'options' => {
+ 'to' => "changelog@ml.${::domain}",
+ 'reply_to' => "dev@ml.${::domain}",
+ 'from' => "buildsystem-daemon@${::domain}",
+ 'prefix' => 'RPM',
+ },
+ },
+ },
+ },
+ 'todo' => {
+ 'checks' => {
+ 'tag' => {
+ 'options' => $checks_tag_options,
+ },
+ },
+ },
+ }
+ }
+}
diff --git a/deployment/mga_buildsystem/manifests/mainnode.pp b/deployment/mga_buildsystem/manifests/mainnode.pp
index 6ad140d2..b614cdbd 100644
--- a/deployment/mga_buildsystem/manifests/mainnode.pp
+++ b/deployment/mga_buildsystem/manifests/mainnode.pp
@@ -1,27 +1,14 @@
class mga_buildsystem::mainnode {
+ include mga_buildsystem::config
include buildsystem::mainnode
- include buildsystem::sync20101
include buildsystem::release
include buildsystem::maintdb
include buildsystem::binrepo
include buildsystem::repoctl
+ include buildsystem::webstatus
- # Forward ports to arm1 and arm2 ssh, to access them from outside
- xinetd::port_forward {"forward_arm1":
- target_ip => 'arm1.mageia.org',
- target_port => '22',
- port => '4251',
- proto => 'tcp',
- }
- xinetd::port_forward {"forward_arm2":
- target_ip => 'arm2.mageia.org',
- target_port => '22',
- port => '4252',
- proto => 'tcp',
- }
-
- $rpmlint_packages = [ "rpmlint-mageia-policy", "rpmlint-mageia-mga2-policy"]
+ $rpmlint_packages = [ 'rpmlint-mageia-policy']
package { $rpmlint_packages:
- ensure => installed,
+ ensure => installed
}
}
diff --git a/deployment/mgagit/files/git_multimail.py b/deployment/mgagit/files/git_multimail.py
new file mode 100644
index 00000000..0c5c8d7b
--- /dev/null
+++ b/deployment/mgagit/files/git_multimail.py
@@ -0,0 +1,4383 @@
+#! /usr/bin/env python3
+
+__version__ = '1.7.dev'
+
+# Copyright (c) 2015-2022 Matthieu Moy and others
+# Copyright (c) 2012-2014 Michael Haggerty and others
+# Derived from contrib/hooks/post-receive-email, which is
+# Copyright (c) 2007 Andy Parkins
+# and also includes contributions by other authors.
+#
+# This file is part of git-multimail.
+#
+# git-multimail is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version
+# 2 as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Generate notification emails for pushes to a git repository.
+
+This hook sends emails describing changes introduced by pushes to a
+git repository. For each reference that was changed, it emits one
+ReferenceChange email summarizing how the reference was changed,
+followed by one Revision email for each new commit that was introduced
+by the reference change.
+
+Each commit is announced in exactly one Revision email. If the same
+commit is merged into another branch in the same or a later push, then
+the ReferenceChange email will list the commit's SHA1 and its one-line
+summary, but no new Revision email will be generated.
+
+This script is designed to be used as a "post-receive" hook in a git
+repository (see githooks(5)). It can also be used as an "update"
+script, but this usage is not completely reliable and is deprecated.
+
+To help with debugging, this script accepts a --stdout option, which
+causes the emails to be written to standard output rather than sent
+using sendmail.
+
+See the accompanying README file for the complete documentation.
+
+"""
+
+import sys
+import os
+import re
+import bisect
+import socket
+import subprocess
+import shlex
+import optparse
+import logging
+import smtplib
+try:
+ import ssl
+except ImportError:
+ # Python < 2.6 do not have ssl, but that's OK if we don't use it.
+ pass
+import time
+
+import uuid
+import base64
+
+PYTHON3 = sys.version_info >= (3, 0)
+
+if sys.version_info <= (2, 5):
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+
+
+def is_ascii(s):
+ return all(ord(c) < 128 and ord(c) > 0 for c in s)
+
+
+if PYTHON3:
+ def is_string(s):
+ return isinstance(s, str)
+
+ def str_to_bytes(s):
+ return s.encode(ENCODING)
+
+ def bytes_to_str(s, errors='strict'):
+ return s.decode(ENCODING, errors)
+
+ unicode = str
+
+ def write_str(f, msg):
+ # Try outputting with the default encoding. If it fails,
+ # try UTF-8.
+ try:
+ f.buffer.write(msg.encode(sys.getdefaultencoding()))
+ except UnicodeEncodeError:
+ f.buffer.write(msg.encode(ENCODING))
+
+ def read_line(f):
+ # Try reading with the default encoding. If it fails,
+ # try UTF-8.
+ out = f.buffer.readline()
+ try:
+ return out.decode(sys.getdefaultencoding())
+ except UnicodeEncodeError:
+ return out.decode(ENCODING)
+
+ import html
+
+ def html_escape(s):
+ return html.escape(s)
+
+else:
+ def is_string(s):
+ try:
+ return isinstance(s, basestring)
+ except NameError: # Silence Pyflakes warning
+ raise
+
+ def str_to_bytes(s):
+ return s
+
+ def bytes_to_str(s, errors='strict'):
+ return s
+
+ def write_str(f, msg):
+ f.write(msg)
+
+ def read_line(f):
+ return f.readline()
+
+ def next(it):
+ return it.next()
+
+ import cgi
+
+ def html_escape(s):
+ return cgi.escape(s, True)
+
+try:
+ from email.charset import Charset
+ from email.utils import make_msgid
+ from email.utils import getaddresses
+ from email.utils import formataddr
+ from email.utils import formatdate
+ from email.header import Header
+except ImportError:
+ # Prior to Python 2.5, the email module used different names:
+ from email.Charset import Charset
+ from email.Utils import make_msgid
+ from email.Utils import getaddresses
+ from email.Utils import formataddr
+ from email.Utils import formatdate
+ from email.Header import Header
+
+
+DEBUG = False
+
+ZEROS = '0' * 40
+LOGBEGIN = '- Log -----------------------------------------------------------------\n'
+LOGEND = '-----------------------------------------------------------------------\n'
+
+ADDR_HEADERS = set(['from', 'to', 'cc', 'bcc', 'reply-to', 'sender'])
+
+# It is assumed in many places that the encoding is uniformly UTF-8,
+# so changing these constants is unsupported. But define them here
+# anyway, to make it easier to find (at least most of) the places
+# where the encoding is important.
+(ENCODING, CHARSET) = ('UTF-8', 'utf-8')
+
+
+REF_CREATED_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s created'
+ ' (now %(newrev_short)s)'
+ )
+REF_UPDATED_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s updated'
+ ' (%(oldrev_short)s -> %(newrev_short)s)'
+ )
+REF_DELETED_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s deleted'
+ ' (was %(oldrev_short)s)'
+ )
+
+COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s updated: %(oneline)s'
+ )
+
+REFCHANGE_HEADER_TEMPLATE = """\
+Date: %(send_date)s
+To: %(recipients)s
+Subject: %(subject)s
+MIME-Version: 1.0
+Content-Type: text/%(contenttype)s; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+Message-ID: %(msgid)s
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+Thread-Index: %(thread_index)s
+X-Git-Host: %(fqdn)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Oldrev: %(oldrev)s
+X-Git-Newrev: %(newrev)s
+X-Git-NotificationType: ref_changed
+X-Git-Multimail-Version: %(multimail_version)s
+Auto-Submitted: auto-generated
+"""
+
+REFCHANGE_INTRO_TEMPLATE = """\
+This is an automated email from the git hooks/post-receive script.
+
+%(pusher)s pushed a change to %(refname_type)s %(short_refname)s
+in repository %(repo_shortname)s.
+
+"""
+
+
+FOOTER_TEMPLATE = """\
+
+-- \n\
+To stop receiving notification emails like this one, please contact
+%(administrator)s.
+"""
+
+
+REWIND_ONLY_TEMPLATE = """\
+This update removed existing revisions from the reference, leaving the
+reference pointing at a previous point in the repository history.
+
+ * -- * -- N %(refname)s (%(newrev_short)s)
+ \\
+ O -- O -- O (%(oldrev_short)s)
+
+Any revisions marked "omit" are not gone; other references still
+refer to them. Any revisions marked "discard" are gone forever.
+"""
+
+
+NON_FF_TEMPLATE = """\
+This update added new revisions after undoing existing revisions.
+That is to say, some revisions that were in the old version of the
+%(refname_type)s are not in the new version. This situation occurs
+when a user --force pushes a change and generates a repository
+containing something like this:
+
+ * -- * -- B -- O -- O -- O (%(oldrev_short)s)
+ \\
+ N -- N -- N %(refname)s (%(newrev_short)s)
+
+You should already have received notification emails for all of the O
+revisions, and so the following emails describe only the N revisions
+from the common base, B.
+
+Any revisions marked "omit" are not gone; other references still
+refer to them. Any revisions marked "discard" are gone forever.
+"""
+
+
+NO_NEW_REVISIONS_TEMPLATE = """\
+No new revisions were added by this update.
+"""
+
+
+DISCARDED_REVISIONS_TEMPLATE = """\
+This change permanently discards the following revisions:
+"""
+
+
+NO_DISCARDED_REVISIONS_TEMPLATE = """\
+The revisions that were on this %(refname_type)s are still contained in
+other references; therefore, this change does not discard any commits
+from the repository.
+"""
+
+
+NEW_REVISIONS_TEMPLATE = """\
+The %(tot)s revisions listed above as "new" are entirely new to this
+repository and will be described in separate emails. The revisions
+listed as "add" were already present in the repository and have only
+been added to this reference.
+
+"""
+
+
+TAG_CREATED_TEMPLATE = """\
+ at %(newrev_short)-8s (%(newrev_type)s)
+"""
+
+
+TAG_UPDATED_TEMPLATE = """\
+*** WARNING: tag %(short_refname)s was modified! ***
+
+ from %(oldrev_short)-8s (%(oldrev_type)s)
+ to %(newrev_short)-8s (%(newrev_type)s)
+"""
+
+
+TAG_DELETED_TEMPLATE = """\
+*** WARNING: tag %(short_refname)s was deleted! ***
+
+"""
+
+
+# The template used in summary tables. It looks best if this uses the
+# same alignment as TAG_CREATED_TEMPLATE and TAG_UPDATED_TEMPLATE.
+BRIEF_SUMMARY_TEMPLATE = """\
+%(action)8s %(rev_short)-8s %(text)s
+"""
+
+
+NON_COMMIT_UPDATE_TEMPLATE = """\
+This is an unusual reference change because the reference did not
+refer to a commit either before or after the change. We do not know
+how to provide full information about this reference change.
+"""
+
+
+REVISION_HEADER_TEMPLATE = """\
+Date: %(send_date)s
+To: %(recipients)s
+Cc: %(cc_recipients)s
+Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
+MIME-Version: 1.0
+Content-Type: text/%(contenttype)s; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+In-Reply-To: %(reply_to_msgid)s
+References: %(reply_to_msgid)s
+Thread-Index: %(thread_index)s
+X-Git-Host: %(fqdn)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Rev: %(rev)s
+X-Git-NotificationType: diff
+X-Git-Multimail-Version: %(multimail_version)s
+Auto-Submitted: auto-generated
+"""
+
+REVISION_INTRO_TEMPLATE = """\
+This is an automated email from the git hooks/post-receive script.
+
+%(pusher)s pushed a commit to %(refname_type)s %(short_refname)s
+in repository %(repo_shortname)s.
+
+"""
+
+LINK_TEXT_TEMPLATE = """\
+View the commit online:
+%(browse_url)s
+
+"""
+
+LINK_HTML_TEMPLATE = """\
+<p><a href="%(browse_url)s">View the commit online</a>.</p>
+"""
+
+
+REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
+
+
+# Combined, meaning refchange+revision email (for single-commit additions)
+COMBINED_HEADER_TEMPLATE = """\
+Date: %(send_date)s
+To: %(recipients)s
+Subject: %(subject)s
+MIME-Version: 1.0
+Content-Type: text/%(contenttype)s; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+Message-ID: %(msgid)s
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+X-Git-Host: %(fqdn)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Oldrev: %(oldrev)s
+X-Git-Newrev: %(newrev)s
+X-Git-Rev: %(rev)s
+X-Git-NotificationType: ref_changed_plus_diff
+X-Git-Multimail-Version: %(multimail_version)s
+Auto-Submitted: auto-generated
+"""
+
+COMBINED_INTRO_TEMPLATE = """\
+This is an automated email from the git hooks/post-receive script.
+
+%(pusher)s pushed a commit to %(refname_type)s %(short_refname)s
+in repository %(repo_shortname)s.
+
+"""
+
+COMBINED_FOOTER_TEMPLATE = FOOTER_TEMPLATE
+
+
+class CommandError(Exception):
+ def __init__(self, cmd, retcode):
+ self.cmd = cmd
+ self.retcode = retcode
+ Exception.__init__(
+ self,
+ 'Command "%s" failed with retcode %s' % (' '.join(cmd), retcode,)
+ )
+
+
+class ConfigurationException(Exception):
+ pass
+
+
+# The "git" program (this could be changed to include a full path):
+GIT_EXECUTABLE = 'git'
+
+
+# How "git" should be invoked (including global arguments), as a list
+# of words. This variable is usually initialized automatically by
+# read_git_output() via choose_git_command(), but if a value is set
+# here then it will be used unconditionally.
+GIT_CMD = None
+
+
+def choose_git_command():
+ """Decide how to invoke git, and record the choice in GIT_CMD."""
+
+ global GIT_CMD
+
+ if GIT_CMD is None:
+ try:
+ # Check to see whether the "-c" option is accepted (it was
+ # only added in Git 1.7.2). We don't actually use the
+ # output of "git --version", though if we needed more
+ # specific version information this would be the place to
+ # do it.
+ cmd = [GIT_EXECUTABLE, '-c', 'foo.bar=baz', '--version']
+ read_output(cmd)
+ GIT_CMD = [GIT_EXECUTABLE, '-c', 'i18n.logoutputencoding=%s' % (ENCODING,)]
+ except CommandError:
+ GIT_CMD = [GIT_EXECUTABLE]
+
+
+def read_git_output(args, input=None, keepends=False, **kw):
+ """Read the output of a Git command."""
+
+ if GIT_CMD is None:
+ choose_git_command()
+
+ return read_output(GIT_CMD + args, input=input, keepends=keepends, **kw)
+
+
+def read_output(cmd, input=None, keepends=False, **kw):
+ if input:
+ stdin = subprocess.PIPE
+ input = str_to_bytes(input)
+ else:
+ stdin = None
+ errors = 'strict'
+ if 'errors' in kw:
+ errors = kw['errors']
+ del kw['errors']
+ p = subprocess.Popen(
+ tuple(str_to_bytes(w) for w in cmd),
+ stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
+ )
+ (out, err) = p.communicate(input)
+ out = bytes_to_str(out, errors=errors)
+ retcode = p.wait()
+ if retcode:
+ raise CommandError(cmd, retcode)
+ if not keepends:
+ out = out.rstrip('\n\r')
+ return out
+
+
+def read_git_lines(args, keepends=False, **kw):
+ """Return the lines output by Git command.
+
+ Return as single lines, with newlines stripped off."""
+
+ return read_git_output(args, keepends=True, **kw).splitlines(keepends)
+
+
+def git_rev_list_ish(cmd, spec, args=None, **kw):
+ """Common functionality for invoking a 'git rev-list'-like command.
+
+ Parameters:
+ * cmd is the Git command to run, e.g., 'rev-list' or 'log'.
+ * spec is a list of revision arguments to pass to the named
+ command. If None, this function returns an empty list.
+ * args is a list of extra arguments passed to the named command.
+ * All other keyword arguments (if any) are passed to the
+ underlying read_git_lines() function.
+
+ Return the output of the Git command in the form of a list, one
+ entry per output line.
+ """
+ if spec is None:
+ return []
+ if args is None:
+ args = []
+ args = [cmd, '--stdin'] + args
+ spec_stdin = ''.join(s + '\n' for s in spec)
+ return read_git_lines(args, input=spec_stdin, **kw)
+
+
+def git_rev_list(spec, **kw):
+ """Run 'git rev-list' with the given list of revision arguments.
+
+ See git_rev_list_ish() for parameter and return value
+ documentation.
+ """
+ return git_rev_list_ish('rev-list', spec, **kw)
+
+
+def git_log(spec, **kw):
+ """Run 'git log' with the given list of revision arguments.
+
+ See git_rev_list_ish() for parameter and return value
+ documentation.
+ """
+ return git_rev_list_ish('log', spec, **kw)
+
+
+def header_encode(text, header_name=None):
+ """Encode and line-wrap the value of an email header field."""
+
+ # Convert to unicode, if required.
+ if not isinstance(text, unicode):
+ text = unicode(text, 'utf-8')
+
+ if is_ascii(text):
+ charset = 'ascii'
+ else:
+ charset = 'utf-8'
+
+ return Header(text, header_name=header_name, charset=Charset(charset)).encode()
+
+
+def addr_header_encode(text, header_name=None):
+ """Encode and line-wrap the value of an email header field containing
+ email addresses."""
+
+ # Convert to unicode, if required.
+ if not isinstance(text, unicode):
+ text = unicode(text, 'utf-8')
+
+ text = ', '.join(
+ formataddr((header_encode(name), emailaddr))
+ for name, emailaddr in getaddresses([text])
+ )
+
+ if is_ascii(text):
+ charset = 'ascii'
+ else:
+ charset = 'utf-8'
+
+ return Header(text, header_name=header_name, charset=Charset(charset)).encode()
+
+
+class Config(object):
+ def __init__(self, section, git_config=None):
+ """Represent a section of the git configuration.
+
+ If git_config is specified, it is passed to "git config" in
+ the GIT_CONFIG environment variable, meaning that "git config"
+ will read the specified path rather than the Git default
+ config paths."""
+
+ self.section = section
+ if git_config:
+ self.env = os.environ.copy()
+ self.env['GIT_CONFIG'] = git_config
+ else:
+ self.env = None
+
+ @staticmethod
+ def _split(s):
+ """Split NUL-terminated values."""
+
+ words = s.split('\0')
+ assert words[-1] == ''
+ return words[:-1]
+
+ @staticmethod
+ def add_config_parameters(c):
+ """Add configuration parameters to Git.
+
+ c is either an str or a list of str, each element being of the
+ form 'var=val' or 'var', with the same syntax and meaning as
+ the argument of 'git -c var=val'.
+ """
+ if isinstance(c, str):
+ c = (c,)
+ parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
+ if parameters:
+ parameters += ' '
+ # git expects GIT_CONFIG_PARAMETERS to be of the form
+ # "'name1=value1' 'name2=value2' 'name3=value3'"
+ # including everything inside the double quotes (but not the double
+ # quotes themselves). Spacing is critical. Also, if a value contains
+ # a literal single quote that quote must be represented using the
+ # four character sequence: '\''
+ parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c)
+ os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+
+ def get(self, name, default=None):
+ try:
+ values = self._split(read_git_output(
+ ['config', '--get', '--null', '%s.%s' % (self.section, name)],
+ env=self.env, keepends=True,
+ ))
+ assert len(values) == 1
+ return values[0]
+ except CommandError:
+ return default
+
+ def get_bool(self, name, default=None):
+ try:
+ value = read_git_output(
+ ['config', '--get', '--bool', '%s.%s' % (self.section, name)],
+ env=self.env,
+ )
+ except CommandError:
+ return default
+ return value == 'true'
+
+ def get_all(self, name, default=None):
+ """Read a (possibly multivalued) setting from the configuration.
+
+ Return the result as a list of values, or default if the name
+ is unset."""
+
+ try:
+ return self._split(read_git_output(
+ ['config', '--get-all', '--null', '%s.%s' % (self.section, name)],
+ env=self.env, keepends=True,
+ ))
+ except CommandError:
+ t, e, traceback = sys.exc_info()
+ if e.retcode == 1:
+ # "the section or key is invalid"; i.e., there is no
+ # value for the specified key.
+ return default
+ else:
+ raise
+
+ def set(self, name, value):
+ read_git_output(
+ ['config', '%s.%s' % (self.section, name), value],
+ env=self.env,
+ )
+
+ def add(self, name, value):
+ read_git_output(
+ ['config', '--add', '%s.%s' % (self.section, name), value],
+ env=self.env,
+ )
+
+ def __contains__(self, name):
+ return self.get_all(name, default=None) is not None
+
+ # We don't use this method anymore internally, but keep it here in
+ # case somebody is calling it from their own code:
+ def has_key(self, name):
+ return name in self
+
+ def unset_all(self, name):
+ try:
+ read_git_output(
+ ['config', '--unset-all', '%s.%s' % (self.section, name)],
+ env=self.env,
+ )
+ except CommandError:
+ t, e, traceback = sys.exc_info()
+ if e.retcode == 5:
+ # The name doesn't exist, which is what we wanted anyway...
+ pass
+ else:
+ raise
+
+ def set_recipients(self, name, value):
+ self.unset_all(name)
+ for pair in getaddresses([value]):
+ self.add(name, formataddr(pair))
+
+
+def generate_summaries(*log_args):
+ """Generate a brief summary for each revision requested.
+
+ log_args are strings that will be passed directly to "git log" as
+ revision selectors. Iterate over (sha1_short, subject) for each
+ commit specified by log_args (subject is the first line of the
+ commit message as a string without EOLs)."""
+
+ cmd = [
+ 'log', '--abbrev', '--format=%h %s',
+ ] + list(log_args) + ['--']
+ for line in read_git_lines(cmd):
+ yield tuple(line.split(' ', 1))
+
+
+def limit_lines(lines, max_lines):
+ for (index, line) in enumerate(lines):
+ if index < max_lines:
+ yield line
+
+ if index >= max_lines:
+ yield '... %d lines suppressed ...\n' % (index + 1 - max_lines,)
+
+
+def limit_linelength(lines, max_linelength):
+ for line in lines:
+ # Don't forget that lines always include a trailing newline.
+ if len(line) > max_linelength + 1:
+ line = line[:max_linelength - 7] + ' [...]\n'
+ yield line
+
+
+class CommitSet(object):
+ """A (constant) set of object names.
+
+ The set should be initialized with full SHA1 object names. The
+ __contains__() method returns True iff its argument is an
+ abbreviation of any the names in the set."""
+
+ def __init__(self, names):
+ self._names = sorted(names)
+
+ def __len__(self):
+ return len(self._names)
+
+ def __contains__(self, sha1_abbrev):
+ """Return True iff this set contains sha1_abbrev (which might be abbreviated)."""
+
+ i = bisect.bisect_left(self._names, sha1_abbrev)
+ return i < len(self) and self._names[i].startswith(sha1_abbrev)
+
+
+class GitObject(object):
+ def __init__(self, sha1, type=None):
+ if sha1 == ZEROS:
+ self.sha1 = self.type = self.commit_sha1 = None
+ else:
+ self.sha1 = sha1
+ self.type = type or read_git_output(['cat-file', '-t', self.sha1])
+
+ if self.type == 'commit':
+ self.commit_sha1 = self.sha1
+ elif self.type == 'tag':
+ try:
+ self.commit_sha1 = read_git_output(
+ ['rev-parse', '--verify', '%s^0' % (self.sha1,)]
+ )
+ except CommandError:
+ # Cannot deref tag to determine commit_sha1
+ self.commit_sha1 = None
+ else:
+ self.commit_sha1 = None
+
+ self.short = read_git_output(['rev-parse', '--short', sha1])
+
+ def get_summary(self):
+ """Return (sha1_short, subject) for this commit."""
+
+ if not self.sha1:
+ raise ValueError('Empty commit has no summary')
+
+ return next(iter(generate_summaries('--no-walk', self.sha1)))
+
+ def __eq__(self, other):
+ return isinstance(other, GitObject) and self.sha1 == other.sha1
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash(self.sha1)
+
+ def __nonzero__(self):
+ return bool(self.sha1)
+
+ def __bool__(self):
+ """Python 2 backward compatibility"""
+ return self.__nonzero__()
+
+ def __str__(self):
+ return self.sha1 or ZEROS
+
+
+class Change(object):
+ """A Change that has been made to the Git repository.
+
+ Abstract class from which both Revisions and ReferenceChanges are
+ derived. A Change knows how to generate a notification email
+ describing itself."""
+
+ def __init__(self, environment):
+ self.environment = environment
+ self._values = None
+ self._contains_html_diff = False
+
+ def _contains_diff(self):
+ # We do contain a diff, should it be rendered in HTML?
+ if self.environment.commit_email_format == "html":
+ self._contains_html_diff = True
+
+ def _compute_values(self):
+ """Return a dictionary {keyword: expansion} for this Change.
+
+ Derived classes overload this method to add more entries to
+ the return value. This method is used internally by
+ get_values(). The return value should always be a new
+ dictionary."""
+
+ values = self.environment.get_values()
+ fromaddr = self.environment.get_fromaddr(change=self)
+ if fromaddr is not None:
+ values['fromaddr'] = fromaddr
+ values['multimail_version'] = get_version()
+ return values
+
+ # Aliases usable in template strings. Tuple of pairs (destination,
+ # source).
+ VALUES_ALIAS = (
+ ("id", "newrev"),
+ )
+
+ def get_values(self, **extra_values):
+ """Return a dictionary {keyword: expansion} for this Change.
+
+ Return a dictionary mapping keywords to the values that they
+ should be expanded to for this Change (used when interpolating
+ template strings). If any keyword arguments are supplied, add
+ those to the return value as well. The return value is always
+ a new dictionary."""
+
+ if self._values is None:
+ self._values = self._compute_values()
+
+ values = self._values.copy()
+ if extra_values:
+ values.update(extra_values)
+
+ for alias, val in self.VALUES_ALIAS:
+ values[alias] = values[val]
+ return values
+
+ def expand(self, template, **extra_values):
+ """Expand template.
+
+ Expand the template (which should be a string) using string
+ interpolation of the values for this Change. If any keyword
+ arguments are provided, also include those in the keywords
+ available for interpolation."""
+
+ return template % self.get_values(**extra_values)
+
+ def expand_lines(self, template, html_escape_val=False, **extra_values):
+ """Break template into lines and expand each line."""
+
+ values = self.get_values(**extra_values)
+ if html_escape_val:
+ for k in values:
+ if is_string(values[k]):
+ values[k] = html_escape(values[k])
+ for line in template.splitlines(True):
+ yield line % values
+
+ def expand_header_lines(self, template, **extra_values):
+ """Break template into lines and expand each line as an RFC 2822 header.
+
+ Encode values and split up lines that are too long. Silently
+ skip lines that contain references to unknown variables."""
+
+ values = self.get_values(**extra_values)
+ if self._contains_html_diff:
+ self._content_type = 'html'
+ else:
+ self._content_type = 'plain'
+ values['contenttype'] = self._content_type
+
+ for line in template.splitlines():
+ (name, value) = line.split(': ', 1)
+
+ try:
+ value = value % values
+ except KeyError:
+ t, e, traceback = sys.exc_info()
+ if DEBUG:
+ self.environment.log_warning(
+ 'Warning: unknown variable %r in the following line; line skipped:\n'
+ ' %s\n'
+ % (e.args[0], line,)
+ )
+ else:
+ if name.lower() in ADDR_HEADERS:
+ value = addr_header_encode(value, name)
+ else:
+ value = header_encode(value, name)
+ for splitline in ('%s: %s\n' % (name, value)).splitlines(True):
+ yield splitline
+
+ def generate_email_header(self):
+ """Generate the RFC 2822 email headers for this Change, a line at a time.
+
+ The output should not include the trailing blank line."""
+
+ raise NotImplementedError()
+
+ def generate_browse_link(self, base_url):
+ """Generate a link to an online repository browser."""
+ return iter(())
+
+ def generate_email_intro(self, html_escape_val=False):
+ """Generate the email intro for this Change, a line at a time.
+
+ The output will be used as the standard boilerplate at the top
+ of the email body."""
+
+ raise NotImplementedError()
+
+ def generate_email_body(self, push):
+ """Generate the main part of the email body, a line at a time.
+
+ The text in the body might be truncated after a specified
+ number of lines (see multimailhook.emailmaxlines)."""
+
+ raise NotImplementedError()
+
+ def generate_email_footer(self, html_escape_val):
+ """Generate the footer of the email, a line at a time.
+
+ The footer is always included, irrespective of
+ multimailhook.emailmaxlines."""
+
+ raise NotImplementedError()
+
+ def _wrap_for_html(self, lines):
+ """Wrap the lines in HTML <pre> tag when using HTML format.
+
+ Escape special HTML characters and add <pre> and </pre> tags around
+ the given lines if we should be generating HTML as indicated by
+ self._contains_html_diff being set to true.
+ """
+ if self._contains_html_diff:
+ yield "<pre style='margin:0'>\n"
+
+ for line in lines:
+ yield html_escape(line)
+
+ yield '</pre>\n'
+ else:
+ for line in lines:
+ yield line
+
+ def generate_email(self, push, body_filter=None, extra_header_values={}):
+ """Generate an email describing this change.
+
+ Iterate over the lines (including the header lines) of an
+ email describing this change. If body_filter is not None,
+ then use it to filter the lines that are intended for the
+ email body.
+
+ The extra_header_values field is received as a dict and not as
+ **kwargs, to allow passing other keyword arguments in the
+ future (e.g. passing extra values to generate_email_intro()"""
+
+ for line in self.generate_email_header(**extra_header_values):
+ yield line
+ yield '\n'
+ html_escape_val = (self.environment.html_in_intro and
+ self._contains_html_diff)
+ intro = self.generate_email_intro(html_escape_val)
+ if not self.environment.html_in_intro:
+ intro = self._wrap_for_html(intro)
+ for line in intro:
+ yield line
+
+ if self.environment.commitBrowseURL:
+ for line in self.generate_browse_link(self.environment.commitBrowseURL):
+ yield line
+
+ body = self.generate_email_body(push)
+ if body_filter is not None:
+ body = body_filter(body)
+
+ diff_started = False
+ if self._contains_html_diff:
+ # "white-space: pre" is the default, but we need to
+ # specify it again in case the message is viewed in a
+ # webmail which wraps it in an element setting white-space
+ # to something else (Zimbra does this and sets
+ # white-space: pre-line).
+ yield '<pre style="white-space: pre; background: #F8F8F8">'
+ for line in body:
+ if self._contains_html_diff:
+ # This is very, very naive. It would be much better to really
+ # parse the diff, i.e. look at how many lines do we have in
+ # the hunk headers instead of blindly highlighting everything
+ # that looks like it might be part of a diff.
+ bgcolor = ''
+ fgcolor = ''
+ if line.startswith('--- a/'):
+ diff_started = True
+ bgcolor = 'e0e0ff'
+ elif line.startswith('diff ') or line.startswith('index '):
+ diff_started = True
+ fgcolor = '808080'
+ elif diff_started:
+ if line.startswith('+++ '):
+ bgcolor = 'e0e0ff'
+ elif line.startswith('@@'):
+ bgcolor = 'e0e0e0'
+ elif line.startswith('+'):
+ bgcolor = 'e0ffe0'
+ elif line.startswith('-'):
+ bgcolor = 'ffe0e0'
+ elif line.startswith('commit '):
+ fgcolor = '808000'
+ elif line.startswith(' '):
+ fgcolor = '404040'
+
+ # Chop the trailing LF, we don't want it inside <pre>.
+ line = html_escape(line[:-1])
+
+ if bgcolor or fgcolor:
+ style = 'display:block; white-space:pre;'
+ if bgcolor:
+ style += 'background:#' + bgcolor + ';'
+ if fgcolor:
+ style += 'color:#' + fgcolor + ';'
+ # Use a <span style='display:block> to color the
+ # whole line. The newline must be inside the span
+ # to display properly both in Firefox and in
+ # text-based browser.
+ line = "<span style='%s'>%s\n</span>" % (style, line)
+ else:
+ line = line + '\n'
+
+ yield line
+ if self._contains_html_diff:
+ yield '</pre>'
+ html_escape_val = (self.environment.html_in_footer and
+ self._contains_html_diff)
+ footer = self.generate_email_footer(html_escape_val)
+ if not self.environment.html_in_footer:
+ footer = self._wrap_for_html(footer)
+ for line in footer:
+ yield line
+
+ def get_specific_fromaddr(self):
+ """For kinds of Changes which specify it, return the kind-specific
+ From address to use."""
+ return None
+
+
+class Revision(Change):
+ """A Change consisting of a single git commit."""
+
+ CC_RE = re.compile(r'^\s*C[Cc]:\s*(?P<to>[^#]+@[^\s#]*)\s*(#.*)?$')
+
+ def __init__(self, reference_change, rev, num, tot):
+ Change.__init__(self, reference_change.environment)
+ self.reference_change = reference_change
+ self.rev = rev
+ self.change_type = self.reference_change.change_type
+ self.refname = self.reference_change.refname
+ self.num = num
+ self.tot = tot
+ self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1])
+ self.recipients = self.environment.get_revision_recipients(self)
+
+ # -s is short for --no-patch, but -s works on older git's (e.g. 1.7)
+ self.parents = read_git_lines(['show', '-s', '--format=%P',
+ self.rev.sha1])[0].split()
+
+ self.cc_recipients = ''
+ if self.environment.get_scancommitforcc():
+ self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients())
+ if self.cc_recipients:
+ self.environment.log_msg(
+ 'Add %s to CC for %s' % (self.cc_recipients, self.rev.sha1))
+
+ def _cc_recipients(self):
+ cc_recipients = []
+ message = read_git_output(['log', '--no-walk', '--format=%b', self.rev.sha1])
+ lines = message.strip().split('\n')
+ for line in lines:
+ m = re.match(self.CC_RE, line)
+ if m:
+ cc_recipients.append(m.group('to'))
+
+ return cc_recipients
+
+ def _compute_values(self):
+ values = Change._compute_values(self)
+
+ oneline = read_git_output(
+ ['log', '--format=%s', '--no-walk', self.rev.sha1]
+ )
+
+ max_subject_length = self.environment.get_max_subject_length()
+ if max_subject_length > 0 and len(oneline) > max_subject_length:
+ oneline = oneline[:max_subject_length - 6] + ' [...]'
+
+ values['rev'] = self.rev.sha1
+ values['parents'] = ' '.join(self.parents)
+ values['rev_short'] = self.rev.short
+ values['change_type'] = self.change_type
+ values['refname'] = self.refname
+ values['newrev'] = self.rev.sha1
+ values['short_refname'] = self.reference_change.short_refname
+ values['refname_type'] = self.reference_change.refname_type
+ values['reply_to_msgid'] = self.reference_change.msgid
+ values['thread_index'] = self.reference_change.thread_index
+ values['num'] = self.num
+ values['tot'] = self.tot
+ values['recipients'] = self.recipients
+ if self.cc_recipients:
+ values['cc_recipients'] = self.cc_recipients
+ values['oneline'] = oneline
+ values['author'] = self.author
+
+ reply_to = self.environment.get_reply_to_commit(self)
+ if reply_to:
+ values['reply_to'] = reply_to
+
+ return values
+
+ def generate_email_header(self, **extra_values):
+ for line in self.expand_header_lines(
+ REVISION_HEADER_TEMPLATE, **extra_values
+ ):
+ yield line
+
+ def generate_browse_link(self, base_url):
+ if '%(' not in base_url:
+ base_url += '%(id)s'
+ url = "".join(self.expand_lines(base_url))
+ if self._content_type == 'html':
+ for line in self.expand_lines(LINK_HTML_TEMPLATE,
+ html_escape_val=True,
+ browse_url=url):
+ yield line
+ elif self._content_type == 'plain':
+ for line in self.expand_lines(LINK_TEXT_TEMPLATE,
+ html_escape_val=False,
+ browse_url=url):
+ yield line
+ else:
+ raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.")
+
+ def generate_email_intro(self, html_escape_val=False):
+ for line in self.expand_lines(REVISION_INTRO_TEMPLATE,
+ html_escape_val=html_escape_val):
+ yield line
+
+ def generate_email_body(self, push):
+ """Show this revision."""
+
+ for line in read_git_lines(
+ ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
+ keepends=True,
+ errors='replace'):
+ if line.startswith('Date: ') and self.environment.date_substitute:
+ yield self.environment.date_substitute + line[len('Date: '):]
+ else:
+ yield line
+
+ def generate_email_footer(self, html_escape_val):
+ return self.expand_lines(REVISION_FOOTER_TEMPLATE,
+ html_escape_val=html_escape_val)
+
+ def generate_email(self, push, body_filter=None, extra_header_values={}):
+ self._contains_diff()
+ return Change.generate_email(self, push, body_filter, extra_header_values)
+
+ def get_specific_fromaddr(self):
+ return self.environment.from_commit
+
+
+class ReferenceChange(Change):
+ """A Change to a Git reference.
+
+ An abstract class representing a create, update, or delete of a
+ Git reference. Derived classes handle specific types of reference
+ (e.g., tags vs. branches). These classes generate the main
+ reference change email summarizing the reference change and
+ whether it caused any any commits to be added or removed.
+
+ ReferenceChange objects are usually created using the static
+ create() method, which has the logic to decide which derived class
+ to instantiate."""
+
+ REF_RE = re.compile(r'^refs\/(?P<area>[^\/]+)\/(?P<shortname>.*)$')
+
+ @staticmethod
+ def create(environment, oldrev, newrev, refname):
+ """Return a ReferenceChange object representing the change.
+
+ Return an object that represents the type of change that is being
+ made. oldrev and newrev should be SHA1s or ZEROS."""
+
+ old = GitObject(oldrev)
+ new = GitObject(newrev)
+ rev = new or old
+
+ # The revision type tells us what type the commit is, combined with
+ # the location of the ref we can decide between
+ # - working branch
+ # - tracking branch
+ # - unannotated tag
+ # - annotated tag
+ m = ReferenceChange.REF_RE.match(refname)
+ if m:
+ area = m.group('area')
+ short_refname = m.group('shortname')
+ else:
+ area = ''
+ short_refname = refname
+
+ if rev.type == 'tag':
+ # Annotated tag:
+ klass = AnnotatedTagChange
+ elif rev.type == 'commit':
+ if area == 'tags':
+ # Non-annotated tag:
+ klass = NonAnnotatedTagChange
+ elif area == 'heads':
+ # Branch:
+ klass = BranchChange
+ elif area == 'remotes':
+ # Tracking branch:
+ environment.log_warning(
+ '*** Push-update of tracking branch %r\n'
+ '*** - incomplete email generated.'
+ % (refname,)
+ )
+ klass = OtherReferenceChange
+ else:
+ # Some other reference namespace:
+ environment.log_warning(
+ '*** Push-update of strange reference %r\n'
+ '*** - incomplete email generated.'
+ % (refname,)
+ )
+ klass = OtherReferenceChange
+ else:
+ # Anything else (is there anything else?)
+ environment.log_warning(
+ '*** Unknown type of update to %r (%s)\n'
+ '*** - incomplete email generated.'
+ % (refname, rev.type,)
+ )
+ klass = OtherReferenceChange
+
+ return klass(
+ environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+
+ @staticmethod
+ def make_thread_index():
+ """Return a string appropriate for the Thread-Index header,
+ needed by MS Outlook to get threading right.
+
+ The format is (base64-encoded):
+ - 1 byte must be 1
+ - 5 bytes encode a date (hardcoded here)
+ - 16 bytes for a globally unique identifier
+
+ FIXME: Unfortunately, even with the Thread-Index field, MS
+ Outlook doesn't seem to do the threading reliably (see
+ https://github.com/git-multimail/git-multimail/pull/194).
+ """
+ thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes
+ return base64.standard_b64encode(thread_index).decode('ascii')
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ Change.__init__(self, environment)
+ self.change_type = {
+ (False, True): 'create',
+ (True, True): 'update',
+ (True, False): 'delete',
+ }[bool(old), bool(new)]
+ self.refname = refname
+ self.short_refname = short_refname
+ self.old = old
+ self.new = new
+ self.rev = rev
+ self.msgid = make_msgid()
+ self.thread_index = self.make_thread_index()
+ self.diffopts = environment.diffopts
+ self.graphopts = environment.graphopts
+ self.logopts = environment.logopts
+ self.commitlogopts = environment.commitlogopts
+ self.showgraph = environment.refchange_showgraph
+ self.showlog = environment.refchange_showlog
+
+ self.header_template = REFCHANGE_HEADER_TEMPLATE
+ self.intro_template = REFCHANGE_INTRO_TEMPLATE
+ self.footer_template = FOOTER_TEMPLATE
+
+ def _compute_values(self):
+ values = Change._compute_values(self)
+
+ values['change_type'] = self.change_type
+ values['refname_type'] = self.refname_type
+ values['refname'] = self.refname
+ values['short_refname'] = self.short_refname
+ values['msgid'] = self.msgid
+ values['thread_index'] = self.thread_index
+ values['recipients'] = self.recipients
+ values['oldrev'] = str(self.old)
+ values['oldrev_short'] = self.old.short
+ values['newrev'] = str(self.new)
+ values['newrev_short'] = self.new.short
+
+ if self.old:
+ values['oldrev_type'] = self.old.type
+ if self.new:
+ values['newrev_type'] = self.new.type
+
+ reply_to = self.environment.get_reply_to_refchange(self)
+ if reply_to:
+ values['reply_to'] = reply_to
+
+ return values
+
+ def send_single_combined_email(self, known_added_sha1s):
+ """Determine if a combined refchange/revision email should be sent
+
+ If there is only a single new (non-merge) commit added by a
+ change, it is useful to combine the ReferenceChange and
+ Revision emails into one. In such a case, return the single
+ revision; otherwise, return None.
+
+ This method is overridden in BranchChange."""
+
+ return None
+
+ def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}):
+ """Generate an email describing this change AND specified revision.
+
+ Iterate over the lines (including the header lines) of an
+ email describing this change. If body_filter is not None,
+ then use it to filter the lines that are intended for the
+ email body.
+
+ The extra_header_values field is received as a dict and not as
+ **kwargs, to allow passing other keyword arguments in the
+ future (e.g. passing extra values to generate_email_intro()
+
+ This method is overridden in BranchChange."""
+
+ raise NotImplementedError
+
+ def get_subject(self):
+ template = {
+ 'create': REF_CREATED_SUBJECT_TEMPLATE,
+ 'update': REF_UPDATED_SUBJECT_TEMPLATE,
+ 'delete': REF_DELETED_SUBJECT_TEMPLATE,
+ }[self.change_type]
+ return self.expand(template)
+
+ def generate_email_header(self, **extra_values):
+ if 'subject' not in extra_values:
+ extra_values['subject'] = self.get_subject()
+
+ for line in self.expand_header_lines(
+ self.header_template, **extra_values
+ ):
+ yield line
+
+ def generate_email_intro(self, html_escape_val=False):
+ for line in self.expand_lines(self.intro_template,
+ html_escape_val=html_escape_val):
+ yield line
+
+ def generate_email_body(self, push):
+ """Call the appropriate body-generation routine.
+
+ Call one of generate_create_summary() /
+ generate_update_summary() / generate_delete_summary()."""
+
+ change_summary = {
+ 'create': self.generate_create_summary,
+ 'delete': self.generate_delete_summary,
+ 'update': self.generate_update_summary,
+ }[self.change_type](push)
+ for line in change_summary:
+ yield line
+
+ for line in self.generate_revision_change_summary(push):
+ yield line
+
+ def generate_email_footer(self, html_escape_val):
+ return self.expand_lines(self.footer_template,
+ html_escape_val=html_escape_val)
+
+ def generate_revision_change_graph(self, push):
+ if self.showgraph:
+ args = ['--graph'] + self.graphopts
+ for newold in ('new', 'old'):
+ has_newold = False
+ spec = push.get_commits_spec(newold, self)
+ for line in git_log(spec, args=args, keepends=True):
+ if not has_newold:
+ has_newold = True
+ yield '\n'
+ yield 'Graph of %s commits:\n\n' % (
+ {'new': 'new', 'old': 'discarded'}[newold],)
+ yield ' ' + line
+ if has_newold:
+ yield '\n'
+
+ def generate_revision_change_log(self, new_commits_list):
+ if self.showlog:
+ yield '\n'
+ yield 'Detailed log of new commits:\n\n'
+ for line in read_git_lines(
+ ['log', '--no-walk'] +
+ self.logopts +
+ new_commits_list +
+ ['--'],
+ keepends=True,
+ ):
+ yield line
+
+ def generate_new_revision_summary(self, tot, new_commits_list, push):
+ for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=tot):
+ yield line
+ for line in self.generate_revision_change_graph(push):
+ yield line
+ for line in self.generate_revision_change_log(new_commits_list):
+ yield line
+
+ def generate_revision_change_summary(self, push):
+ """Generate a summary of the revisions added/removed by this change."""
+
+ if self.new.commit_sha1 and not self.old.commit_sha1:
+ # A new reference was created. List the new revisions
+ # brought by the new reference (i.e., those revisions that
+ # were not in the repository before this reference
+ # change).
+ sha1s = list(push.get_new_commits(self))
+ sha1s.reverse()
+ tot = len(sha1s)
+ new_revisions = [
+ Revision(self, GitObject(sha1), num=i + 1, tot=tot)
+ for (i, sha1) in enumerate(sha1s)
+ ]
+
+ if new_revisions:
+ yield self.expand('This %(refname_type)s includes the following new commits:\n')
+ yield '\n'
+ for r in new_revisions:
+ (sha1, subject) = r.rev.get_summary()
+ yield r.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='new', text=subject,
+ )
+ yield '\n'
+ for line in self.generate_new_revision_summary(
+ tot, [r.rev.sha1 for r in new_revisions], push):
+ yield line
+ else:
+ for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
+ yield line
+
+ elif self.new.commit_sha1 and self.old.commit_sha1:
+ # A reference was changed to point at a different commit.
+ # List the revisions that were removed and/or added *from
+ # that reference* by this reference change, along with a
+ # diff between the trees for its old and new values.
+
+ # List of the revisions that were added to the branch by
+ # this update. Note this list can include revisions that
+ # have already had notification emails; we want such
+ # revisions in the summary even though we will not send
+ # new notification emails for them.
+ adds = list(generate_summaries(
+ '--topo-order', '--reverse', '%s..%s'
+ % (self.old.commit_sha1, self.new.commit_sha1,)
+ ))
+
+ # List of the revisions that were removed from the branch
+ # by this update. This will be empty except for
+ # non-fast-forward updates.
+ discards = list(generate_summaries(
+ '%s..%s' % (self.new.commit_sha1, self.old.commit_sha1,)
+ ))
+
+ if adds:
+ new_commits_list = push.get_new_commits(self)
+ else:
+ new_commits_list = []
+ new_commits = CommitSet(new_commits_list)
+
+ if discards:
+ discarded_commits = CommitSet(push.get_discarded_commits(self))
+ else:
+ discarded_commits = CommitSet([])
+
+ if discards and adds:
+ for (sha1, subject) in discards:
+ if sha1 in discarded_commits:
+ action = 'discard'
+ else:
+ action = 'omit'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+ for (sha1, subject) in adds:
+ if sha1 in new_commits:
+ action = 'new'
+ else:
+ action = 'add'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+ for line in self.expand_lines(NON_FF_TEMPLATE):
+ yield line
+
+ elif discards:
+ for (sha1, subject) in discards:
+ if sha1 in discarded_commits:
+ action = 'discard'
+ else:
+ action = 'omit'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+ for line in self.expand_lines(REWIND_ONLY_TEMPLATE):
+ yield line
+
+ elif adds:
+ (sha1, subject) = self.old.get_summary()
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='from',
+ rev_short=sha1, text=subject,
+ )
+ for (sha1, subject) in adds:
+ if sha1 in new_commits:
+ action = 'new'
+ else:
+ action = 'add'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+
+ yield '\n'
+
+ if new_commits:
+ for line in self.generate_new_revision_summary(
+ len(new_commits), new_commits_list, push):
+ yield line
+ else:
+ for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
+ yield line
+ for line in self.generate_revision_change_graph(push):
+ yield line
+
+ # The diffstat is shown from the old revision to the new
+ # revision. This is to show the truth of what happened in
+ # this change. There's no point showing the stat from the
+ # base to the new revision because the base is effectively a
+ # random revision at this point - the user will be interested
+ # in what this revision changed - including the undoing of
+ # previous revisions in the case of non-fast-forward updates.
+ yield '\n'
+ yield 'Summary of changes:\n'
+ for line in read_git_lines(
+ ['diff-tree'] +
+ self.diffopts +
+ ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
+ keepends=True,
+ ):
+ yield line
+
+ elif self.old.commit_sha1 and not self.new.commit_sha1:
+ # A reference was deleted. List the revisions that were
+ # removed from the repository by this reference change.
+
+ sha1s = list(push.get_discarded_commits(self))
+ tot = len(sha1s)
+ discarded_revisions = [
+ Revision(self, GitObject(sha1), num=i + 1, tot=tot)
+ for (i, sha1) in enumerate(sha1s)
+ ]
+
+ if discarded_revisions:
+ for line in self.expand_lines(DISCARDED_REVISIONS_TEMPLATE):
+ yield line
+ yield '\n'
+ for r in discarded_revisions:
+ (sha1, subject) = r.rev.get_summary()
+ yield r.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='discard', text=subject,
+ )
+ for line in self.generate_revision_change_graph(push):
+ yield line
+ else:
+ for line in self.expand_lines(NO_DISCARDED_REVISIONS_TEMPLATE):
+ yield line
+
+ elif not self.old.commit_sha1 and not self.new.commit_sha1:
+ for line in self.expand_lines(NON_COMMIT_UPDATE_TEMPLATE):
+ yield line
+
+ def generate_create_summary(self, push):
+ """Called for the creation of a reference."""
+
+ # This is a new reference and so oldrev is not valid
+ (sha1, subject) = self.new.get_summary()
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='at',
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+
+ def generate_update_summary(self, push):
+ """Called for the change of a pre-existing branch."""
+
+ return iter([])
+
+ def generate_delete_summary(self, push):
+ """Called for the deletion of any type of reference."""
+
+ (sha1, subject) = self.old.get_summary()
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='was',
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+
+ def get_specific_fromaddr(self):
+ return self.environment.from_refchange
+
+
+class BranchChange(ReferenceChange):
+ refname_type = 'branch'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_refchange_recipients(self)
+ self._single_revision = None
+
+ def send_single_combined_email(self, known_added_sha1s):
+ if not self.environment.combine_when_single_commit:
+ return None
+
+ # In the sadly-all-too-frequent usecase of people pushing only
+ # one of their commits at a time to a repository, users feel
+ # the reference change summary emails are noise rather than
+ # important signal. This is because, in this particular
+ # usecase, there is a reference change summary email for each
+ # new commit, and all these summaries do is point out that
+ # there is one new commit (which can readily be inferred by
+ # the existence of the individual revision email that is also
+ # sent). In such cases, our users prefer there to be a combined
+ # reference change summary/new revision email.
+ #
+ # So, if the change is an update and it doesn't discard any
+ # commits, and it adds exactly one non-merge commit (gerrit
+ # forces a workflow where every commit is individually merged
+ # and the git-multimail hook fired off for just this one
+ # change), then we send a combined refchange/revision email.
+ try:
+ # If this change is a reference update that doesn't discard
+ # any commits...
+ if self.change_type != 'update':
+ return None
+
+ if read_git_lines(
+ ['merge-base', self.old.sha1, self.new.sha1]
+ ) != [self.old.sha1]:
+ return None
+
+ # Check if this update introduced exactly one non-merge
+ # commit:
+
+ def split_line(line):
+ """Split line into (sha1, [parent,...])."""
+
+ words = line.split()
+ return (words[0], words[1:])
+
+ # Get the new commits introduced by the push as a list of
+ # (sha1, [parent,...])
+ new_commits = [
+ split_line(line)
+ for line in read_git_lines(
+ [
+ 'log', '-3', '--format=%H %P',
+ '%s..%s' % (self.old.sha1, self.new.sha1),
+ ]
+ )
+ ]
+
+ if not new_commits:
+ return None
+
+ # If the newest commit is a merge, save it for a later check
+ # but otherwise ignore it
+ merge = None
+ tot = len(new_commits)
+ if len(new_commits[0][1]) > 1:
+ merge = new_commits[0][0]
+ del new_commits[0]
+
+ # Our primary check: we can't combine if more than one commit
+ # is introduced. We also currently only combine if the new
+ # commit is a non-merge commit, though it may make sense to
+ # combine if it is a merge as well.
+ if not (
+ len(new_commits) == 1 and
+ len(new_commits[0][1]) == 1 and
+ new_commits[0][0] in known_added_sha1s
+ ):
+ return None
+
+ # We do not want to combine revision and refchange emails if
+ # those go to separate locations.
+ rev = Revision(self, GitObject(new_commits[0][0]), 1, tot)
+ if rev.recipients != self.recipients:
+ return None
+
+ # We ignored the newest commit if it was just a merge of the one
+ # commit being introduced. But we don't want to ignore that
+ # merge commit it it involved conflict resolutions. Check that.
+ if merge and merge != read_git_output(['diff-tree', '--cc', merge]):
+ return None
+
+ # We can combine the refchange and one new revision emails
+ # into one. Return the Revision that a combined email should
+ # be sent about.
+ return rev
+ except CommandError:
+ # Cannot determine number of commits in old..new or new..old;
+ # don't combine reference/revision emails:
+ return None
+
+ def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}):
+ values = revision.get_values()
+ if extra_header_values:
+ values.update(extra_header_values)
+ if 'subject' not in extra_header_values:
+ values['subject'] = self.expand(COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values)
+
+ self._single_revision = revision
+ self._contains_diff()
+ self.header_template = COMBINED_HEADER_TEMPLATE
+ self.intro_template = COMBINED_INTRO_TEMPLATE
+ self.footer_template = COMBINED_FOOTER_TEMPLATE
+
+ def revision_gen_link(base_url):
+ # revision is used only to generate the body, and
+ # _content_type is set while generating headers. Get it
+ # from the BranchChange object.
+ revision._content_type = self._content_type
+ return revision.generate_browse_link(base_url)
+ self.generate_browse_link = revision_gen_link
+ for line in self.generate_email(push, body_filter, values):
+ yield line
+
+ def generate_email_body(self, push):
+ '''Call the appropriate body generation routine.
+
+ If this is a combined refchange/revision email, the special logic
+ for handling this combined email comes from this function. For
+ other cases, we just use the normal handling.'''
+
+ # If self._single_revision isn't set; don't override
+ if not self._single_revision:
+ for line in super(BranchChange, self).generate_email_body(push):
+ yield line
+ return
+
+ # This is a combined refchange/revision email; we first provide
+ # some info from the refchange portion, and then call the revision
+ # generate_email_body function to handle the revision portion.
+ adds = list(generate_summaries(
+ '--topo-order', '--reverse', '%s..%s'
+ % (self.old.commit_sha1, self.new.commit_sha1,)
+ ))
+
+ yield self.expand("The following commit(s) were added to %(refname)s by this push:\n")
+ for (sha1, subject) in adds:
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='new',
+ rev_short=sha1, text=subject,
+ )
+
+ yield self._single_revision.rev.short + " is described below\n"
+ yield '\n'
+
+ for line in self._single_revision.generate_email_body(push):
+ yield line
+
+
+class AnnotatedTagChange(ReferenceChange):
+ refname_type = 'annotated tag'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_announce_recipients(self)
+ self.show_shortlog = environment.announce_show_shortlog
+
+ ANNOTATED_TAG_FORMAT = (
+ '%(*objectname)\n'
+ '%(*objecttype)\n'
+ '%(taggername)\n'
+ '%(taggerdate)'
+ )
+
+ def describe_tag(self, push):
+ """Describe the new value of an annotated tag."""
+
+ # Use git for-each-ref to pull out the individual fields from
+ # the tag
+ [tagobject, tagtype, tagger, tagged] = read_git_lines(
+ ['for-each-ref', '--format=%s' % (self.ANNOTATED_TAG_FORMAT,), self.refname],
+ )
+
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='tagging',
+ rev_short=tagobject, text='(%s)' % (tagtype,),
+ )
+ if tagtype == 'commit':
+ # If the tagged object is a commit, then we assume this is a
+ # release, and so we calculate which tag this tag is
+ # replacing
+ try:
+ prevtag = read_git_output(['describe', '--abbrev=0', '%s^' % (self.new,)])
+ except CommandError:
+ prevtag = None
+ if prevtag:
+ yield ' replaces %s\n' % (prevtag,)
+ else:
+ prevtag = None
+ yield ' length %s bytes\n' % (read_git_output(['cat-file', '-s', tagobject]),)
+
+ yield ' by %s\n' % (tagger,)
+ yield ' on %s\n' % (tagged,)
+ yield '\n'
+
+ # Show the content of the tag message; this might contain a
+ # change log or release notes so is worth displaying.
+ yield LOGBEGIN
+ contents = list(read_git_lines(['cat-file', 'tag', self.new.sha1], keepends=True))
+ contents = contents[contents.index('\n') + 1:]
+ if contents and contents[-1][-1:] != '\n':
+ contents.append('\n')
+ for line in contents:
+ yield line
+
+ if self.show_shortlog and tagtype == 'commit':
+ # Only commit tags make sense to have rev-list operations
+ # performed on them
+ yield '\n'
+ if prevtag:
+ # Show changes since the previous release
+ revlist = read_git_output(
+ ['rev-list', '--pretty=short', '%s..%s' % (prevtag, self.new,)],
+ keepends=True,
+ )
+ else:
+ # No previous tag, show all the changes since time
+ # began
+ revlist = read_git_output(
+ ['rev-list', '--pretty=short', '%s' % (self.new,)],
+ keepends=True,
+ )
+ for line in read_git_lines(['shortlog'], input=revlist, keepends=True):
+ yield line
+
+ yield LOGEND
+ yield '\n'
+
+ def generate_create_summary(self, push):
+ """Called for the creation of an annotated tag."""
+
+ for line in self.expand_lines(TAG_CREATED_TEMPLATE):
+ yield line
+
+ for line in self.describe_tag(push):
+ yield line
+
+ def generate_update_summary(self, push):
+ """Called for the update of an annotated tag.
+
+ This is probably a rare event and may not even be allowed."""
+
+ for line in self.expand_lines(TAG_UPDATED_TEMPLATE):
+ yield line
+
+ for line in self.describe_tag(push):
+ yield line
+
+ def generate_delete_summary(self, push):
+ """Called when a non-annotated reference is updated."""
+
+ for line in self.expand_lines(TAG_DELETED_TEMPLATE):
+ yield line
+
+ yield self.expand(' tag was %(oldrev_short)s\n')
+ yield '\n'
+
+
+class NonAnnotatedTagChange(ReferenceChange):
+ refname_type = 'tag'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_refchange_recipients(self)
+
+ def generate_create_summary(self, push):
+ """Called for the creation of an annotated tag."""
+
+ for line in self.expand_lines(TAG_CREATED_TEMPLATE):
+ yield line
+
+ def generate_update_summary(self, push):
+ """Called when a non-annotated reference is updated."""
+
+ for line in self.expand_lines(TAG_UPDATED_TEMPLATE):
+ yield line
+
+ def generate_delete_summary(self, push):
+ """Called when a non-annotated reference is updated."""
+
+ for line in self.expand_lines(TAG_DELETED_TEMPLATE):
+ yield line
+
+ for line in ReferenceChange.generate_delete_summary(self, push):
+ yield line
+
+
+class OtherReferenceChange(ReferenceChange):
+ refname_type = 'reference'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ # We use the full refname as short_refname, because otherwise
+ # the full name of the reference would not be obvious from the
+ # text of the email.
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_refchange_recipients(self)
+
+
+class Mailer(object):
+ """An object that can send emails."""
+
+ def __init__(self, environment):
+ self.environment = environment
+
+ def close(self):
+ pass
+
+ def send(self, lines, to_addrs):
+ """Send an email consisting of lines.
+
+ lines must be an iterable over the lines constituting the
+ header and body of the email. to_addrs is a list of recipient
+ addresses (can be needed even if lines already contains a
+ "To:" field). It can be either a string (comma-separated list
+ of email addresses) or a Python list of individual email
+ addresses.
+
+ """
+
+ raise NotImplementedError()
+
+
+class SendMailer(Mailer):
+ """Send emails using 'sendmail -oi -t'."""
+
+ SENDMAIL_CANDIDATES = [
+ '/usr/sbin/sendmail',
+ '/usr/lib/sendmail',
+ ]
+
+ @staticmethod
+ def find_sendmail():
+ for path in SendMailer.SENDMAIL_CANDIDATES:
+ if os.access(path, os.X_OK):
+ return path
+ else:
+ raise ConfigurationException(
+ 'No sendmail executable found. '
+ 'Try setting multimailhook.sendmailCommand.'
+ )
+
+ def __init__(self, environment, command=None, envelopesender=None):
+ """Construct a SendMailer instance.
+
+ command should be the command and arguments used to invoke
+ sendmail, as a list of strings. If an envelopesender is
+ provided, it will also be passed to the command, via '-f
+ envelopesender'."""
+ super(SendMailer, self).__init__(environment)
+ if command:
+ self.command = command[:]
+ else:
+ self.command = [self.find_sendmail(), '-oi', '-t']
+
+ if envelopesender:
+ self.command.extend(['-f', envelopesender])
+
+ def send(self, lines, to_addrs):
+ try:
+ p = subprocess.Popen(self.command, stdin=subprocess.PIPE)
+ except OSError:
+ self.environment.get_logger().error(
+ '*** Cannot execute command: %s\n' % ' '.join(self.command) +
+ '*** %s\n' % sys.exc_info()[1] +
+ '*** Try setting multimailhook.mailer to "smtp"\n' +
+ '*** to send emails without using the sendmail command.\n'
+ )
+ sys.exit(1)
+ try:
+ lines = (str_to_bytes(line) for line in lines)
+ p.stdin.writelines(lines)
+ except Exception:
+ self.environment.get_logger().error(
+ '*** Error while generating commit email\n'
+ '*** - mail sending aborted.\n'
+ )
+ if hasattr(p, 'terminate'):
+ # subprocess.terminate() is not available in Python 2.4
+ p.terminate()
+ else:
+ import signal
+ os.kill(p.pid, signal.SIGTERM)
+ raise
+ else:
+ p.stdin.close()
+ retcode = p.wait()
+ if retcode:
+ raise CommandError(self.command, retcode)
+
+
+class SMTPMailer(Mailer):
+ """Send emails using Python's smtplib."""
+
+ def __init__(self, environment,
+ envelopesender, smtpserver,
+ smtpservertimeout=10.0, smtpserverdebuglevel=0,
+ smtpencryption='none',
+ smtpuser='', smtppass='',
+ smtpcacerts=''
+ ):
+ super(SMTPMailer, self).__init__(environment)
+ if not envelopesender:
+ self.environment.get_logger().error(
+ 'fatal: git_multimail: cannot use SMTPMailer without a sender address.\n'
+ 'please set either multimailhook.envelopeSender or user.email\n'
+ )
+ sys.exit(1)
+ if smtpencryption == 'ssl' and not (smtpuser and smtppass):
+ raise ConfigurationException(
+ 'Cannot use SMTPMailer with security option ssl '
+ 'without options username and password.'
+ )
+ self.envelopesender = envelopesender
+ self.smtpserver = smtpserver
+ self.smtpservertimeout = smtpservertimeout
+ self.smtpserverdebuglevel = smtpserverdebuglevel
+ self.security = smtpencryption
+ self.username = smtpuser
+ self.password = smtppass
+ self.smtpcacerts = smtpcacerts
+ self.loggedin = False
+ try:
+ def call(klass, server, timeout):
+ try:
+ return klass(server, timeout=timeout)
+ except TypeError:
+ # Old Python versions do not have timeout= argument.
+ return klass(server)
+ if self.security == 'none':
+ self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+ elif self.security == 'ssl':
+ if self.smtpcacerts:
+ raise smtplib.SMTPException(
+ "Checking certificate is not supported for ssl, prefer starttls"
+ )
+ self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
+ elif self.security == 'tls':
+ if 'ssl' not in sys.modules:
+ self.environment.get_logger().error(
+ '*** Your Python version does not have the ssl library installed\n'
+ '*** smtpEncryption=tls is not available.\n'
+ '*** Either upgrade Python to 2.6 or later\n'
+ ' or use git_multimail.py version 1.2.\n')
+ if ':' not in self.smtpserver:
+ self.smtpserver += ':587' # default port for TLS
+ self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+ # start: ehlo + starttls
+ # equivalent to
+ # self.smtp.ehlo()
+ # self.smtp.starttls()
+ # with access to the ssl layer
+ self.smtp.ehlo()
+ if not self.smtp.has_extn("starttls"):
+ raise smtplib.SMTPException("STARTTLS extension not supported by server")
+ resp, reply = self.smtp.docmd("STARTTLS")
+ if resp != 220:
+ raise smtplib.SMTPException("Wrong answer to the STARTTLS command")
+ if self.smtpcacerts:
+ self.smtp.sock = ssl.wrap_socket(
+ self.smtp.sock,
+ ca_certs=self.smtpcacerts,
+ cert_reqs=ssl.CERT_REQUIRED
+ )
+ else:
+ self.smtp.sock = ssl.wrap_socket(
+ self.smtp.sock,
+ cert_reqs=ssl.CERT_NONE
+ )
+ self.environment.get_logger().error(
+ '*** Warning, the server certificate is not verified (smtp) ***\n'
+ '*** set the option smtpCACerts ***\n'
+ )
+ if not hasattr(self.smtp.sock, "read"):
+ # using httplib.FakeSocket with Python 2.5.x or earlier
+ self.smtp.sock.read = self.smtp.sock.recv
+ self.smtp.file = self.smtp.sock.makefile('rb')
+ self.smtp.helo_resp = None
+ self.smtp.ehlo_resp = None
+ self.smtp.esmtp_features = {}
+ self.smtp.does_esmtp = 0
+ # end: ehlo + starttls
+ self.smtp.ehlo()
+ else:
+ sys.stdout.write('*** Error: Control reached an invalid option. ***')
+ sys.exit(1)
+ if self.smtpserverdebuglevel > 0:
+ sys.stdout.write(
+ "*** Setting debug on for SMTP server connection (%s) ***\n"
+ % self.smtpserverdebuglevel)
+ self.smtp.set_debuglevel(self.smtpserverdebuglevel)
+ except Exception:
+ self.environment.get_logger().error(
+ '*** Error establishing SMTP connection to %s ***\n'
+ '*** %s\n'
+ % (self.smtpserver, sys.exc_info()[1]))
+ sys.exit(1)
+
+ def close(self):
+ if hasattr(self, 'smtp'):
+ self.smtp.quit()
+ del self.smtp
+
+ def __del__(self):
+ self.close()
+
+ def send(self, lines, to_addrs):
+ try:
+ if self.username or self.password:
+ if not self.loggedin:
+ self.smtp.login(self.username, self.password)
+ self.loggedin = True
+ msg = ''.join(lines)
+ # turn comma-separated list into Python list if needed.
+ if is_string(to_addrs):
+ to_addrs = [email for (name, email) in getaddresses([to_addrs])]
+ self.smtp.sendmail(self.envelopesender, to_addrs, msg.encode('utf8'))
+ except socket.timeout:
+ self.environment.get_logger().error(
+ '*** Error sending email ***\n'
+ '*** SMTP server timed out (timeout is %s)\n'
+ % self.smtpservertimeout)
+ except smtplib.SMTPResponseException:
+ err = sys.exc_info()[1]
+ self.environment.get_logger().error(
+ '*** Error sending email ***\n'
+ '*** Error %d: %s\n'
+ % (err.smtp_code, bytes_to_str(err.smtp_error)))
+ try:
+ smtp = self.smtp
+ # delete the field before quit() so that in case of
+ # error, self.smtp is deleted anyway.
+ del self.smtp
+ smtp.quit()
+ except:
+ self.environment.get_logger().error(
+ '*** Error closing the SMTP connection ***\n'
+ '*** Exiting anyway ... ***\n'
+ '*** %s\n' % sys.exc_info()[1])
+ sys.exit(1)
+
+
+class OutputMailer(Mailer):
+ """Write emails to an output stream, bracketed by lines of '=' characters.
+
+ This is intended for debugging purposes."""
+
+ SEPARATOR = '=' * 75 + '\n'
+
+ def __init__(self, f, environment=None):
+ super(OutputMailer, self).__init__(environment=environment)
+ self.f = f
+
+ def send(self, lines, to_addrs):
+ write_str(self.f, self.SEPARATOR)
+ for line in lines:
+ write_str(self.f, line)
+ write_str(self.f, self.SEPARATOR)
+
+
+def get_git_dir():
+ """Determine GIT_DIR.
+
+ Determine GIT_DIR either from the GIT_DIR environment variable or
+ from the working directory, using Git's usual rules."""
+
+ try:
+ return read_git_output(['rev-parse', '--git-dir'])
+ except CommandError:
+ sys.stderr.write('fatal: git_multimail: not in a git directory\n')
+ sys.exit(1)
+
+
+class Environment(object):
+ """Describes the environment in which the push is occurring.
+
+ An Environment object encapsulates information about the local
+ environment. For example, it knows how to determine:
+
+ * the name of the repository to which the push occurred
+
+ * what user did the push
+
+ * what users want to be informed about various types of changes.
+
+ An Environment object is expected to have the following methods:
+
+ get_repo_shortname()
+
+ Return a short name for the repository, for display
+ purposes.
+
+ get_repo_path()
+
+ Return the absolute path to the Git repository.
+
+ get_emailprefix()
+
+ Return a string that will be prefixed to every email's
+ subject.
+
+ get_pusher()
+
+ Return the username of the person who pushed the changes.
+ This value is used in the email body to indicate who
+ pushed the change.
+
+ get_pusher_email() (may return None)
+
+ Return the email address of the person who pushed the
+ changes. The value should be a single RFC 2822 email
+ address as a string; e.g., "Joe User <user@example.com>"
+ if available, otherwise "user@example.com". If set, the
+ value is used as the Reply-To address for refchange
+ emails. If it is impossible to determine the pusher's
+ email, this attribute should be set to None (in which case
+ no Reply-To header will be output).
+
+ get_sender()
+
+ Return the address to be used as the 'From' email address
+ in the email envelope.
+
+ get_fromaddr(change=None)
+
+ Return the 'From' email address used in the email 'From:'
+ headers. If the change is known when this function is
+ called, it is passed in as the 'change' parameter. (May
+ be a full RFC 2822 email address like 'Joe User
+ <user@example.com>'.)
+
+ get_administrator()
+
+ Return the name and/or email of the repository
+ administrator. This value is used in the footer as the
+ person to whom requests to be removed from the
+ notification list should be sent. Ideally, it should
+ include a valid email address.
+
+ get_reply_to_refchange()
+ get_reply_to_commit()
+
+ Return the address to use in the email "Reply-To" header,
+ as a string. These can be an RFC 2822 email address, or
+ None to omit the "Reply-To" header.
+ get_reply_to_refchange() is used for refchange emails;
+ get_reply_to_commit() is used for individual commit
+ emails.
+
+ get_ref_filter_regex()
+
+ Return a tuple -- a compiled regex, and a boolean indicating
+ whether the regex picks refs to include (if False, the regex
+ matches on refs to exclude).
+
+ get_default_ref_ignore_regex()
+
+ Return a regex that should be ignored for both what emails
+ to send and when computing what commits are considered new
+ to the repository. Default is "^refs/notes/".
+
+ get_max_subject_length()
+
+ Return an int giving the maximal length for the subject
+ (git log --oneline).
+
+ They should also define the following attributes:
+
+ announce_show_shortlog (bool)
+
+ True iff announce emails should include a shortlog.
+
+ commit_email_format (string)
+
+ If "html", generate commit emails in HTML instead of plain text
+ used by default.
+
+ html_in_intro (bool)
+ html_in_footer (bool)
+
+ When generating HTML emails, the introduction (respectively,
+ the footer) will be HTML-escaped iff html_in_intro (respectively,
+ the footer) is true. When false, only the values used to expand
+ the template are escaped.
+
+ refchange_showgraph (bool)
+
+ True iff refchanges emails should include a detailed graph.
+
+ refchange_showlog (bool)
+
+ True iff refchanges emails should include a detailed log.
+
+ diffopts (list of strings)
+
+ The options that should be passed to 'git diff' for the
+ summary email. The value should be a list of strings
+ representing words to be passed to the command.
+
+ graphopts (list of strings)
+
+ Analogous to diffopts, but contains options passed to
+ 'git log --graph' when generating the detailed graph for
+ a set of commits (see refchange_showgraph)
+
+ logopts (list of strings)
+
+ Analogous to diffopts, but contains options passed to
+ 'git log' when generating the detailed log for a set of
+ commits (see refchange_showlog)
+
+ commitlogopts (list of strings)
+
+ The options that should be passed to 'git log' for each
+ commit mail. The value should be a list of strings
+ representing words to be passed to the command.
+
+ date_substitute (string)
+
+ String to be used in substitution for 'Date:' at start of
+ line in the output of 'git log'.
+
+ quiet (bool)
+ On success do not write to stderr
+
+ stdout (bool)
+ Write email to stdout rather than emailing. Useful for debugging
+
+ combine_when_single_commit (bool)
+
+ True if a combined email should be produced when a single
+ new commit is pushed to a branch, False otherwise.
+
+ from_refchange, from_commit (strings)
+
+ Addresses to use for the From: field for refchange emails
+ and commit emails respectively. Set from
+ multimailhook.fromRefchange and multimailhook.fromCommit
+ by ConfigEnvironmentMixin.
+
+ log_file, error_log_file, debug_log_file (string)
+
+ Name of a file to which logs should be sent.
+
+ verbose (int)
+
+ How verbose the system should be.
+ - 0 (default): show info, errors, ...
+ - 1 : show basic debug info
+ """
+
+ REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
+
+ def __init__(self, osenv=None):
+ self.osenv = osenv or os.environ
+ self.announce_show_shortlog = False
+ self.commit_email_format = "text"
+ self.html_in_intro = False
+ self.html_in_footer = False
+ self.commitBrowseURL = None
+ self.maxcommitemails = 500
+ self.excludemergerevisions = False
+ self.diffopts = ['--stat', '--summary', '--find-copies-harder']
+ self.graphopts = ['--oneline', '--decorate']
+ self.logopts = []
+ self.refchange_showgraph = False
+ self.refchange_showlog = False
+ self.commitlogopts = ['-C', '--stat', '-p', '--cc']
+ self.date_substitute = 'AuthorDate: '
+ self.quiet = False
+ self.stdout = False
+ self.combine_when_single_commit = True
+ self.logger = None
+
+ self.COMPUTED_KEYS = [
+ 'administrator',
+ 'charset',
+ 'emailprefix',
+ 'pusher',
+ 'pusher_email',
+ 'repo_path',
+ 'repo_shortname',
+ 'sender',
+ ]
+
+ self._values = None
+
+ def get_logger(self):
+ """Get (possibly creates) the logger associated to this environment."""
+ if self.logger is None:
+ self.logger = Logger(self)
+ return self.logger
+
+ def get_repo_shortname(self):
+ """Use the last part of the repo path, with ".git" stripped off if present."""
+
+ basename = os.path.basename(os.path.abspath(self.get_repo_path()))
+ m = self.REPO_NAME_RE.match(basename)
+ if m:
+ return m.group('name')
+ else:
+ return basename
+
+ def get_pusher(self):
+ raise NotImplementedError()
+
+ def get_pusher_email(self):
+ return None
+
+ def get_fromaddr(self, change=None):
+ config = Config('user')
+ fromname = config.get('name', default='')
+ fromemail = config.get('email', default='')
+ if fromemail:
+ return formataddr([fromname, fromemail])
+ return self.get_sender()
+
+ def get_administrator(self):
+ return 'the administrator of this repository'
+
+ def get_emailprefix(self):
+ return ''
+
+ def get_repo_path(self):
+ if read_git_output(['rev-parse', '--is-bare-repository']) == 'true':
+ path = get_git_dir()
+ else:
+ path = read_git_output(['rev-parse', '--show-toplevel'])
+ return os.path.abspath(path)
+
+ def get_charset(self):
+ return CHARSET
+
+ def get_values(self):
+ """Return a dictionary {keyword: expansion} for this Environment.
+
+ This method is called by Change._compute_values(). The keys
+ in the returned dictionary are available to be used in any of
+ the templates. The dictionary is created by calling
+ self.get_NAME() for each of the attributes named in
+ COMPUTED_KEYS and recording those that do not return None.
+ The return value is always a new dictionary."""
+
+ if self._values is None:
+ values = {'': ''} # %()s expands to the empty string.
+
+ for key in self.COMPUTED_KEYS:
+ value = getattr(self, 'get_%s' % (key,))()
+ if value is not None:
+ values[key] = value
+
+ self._values = values
+
+ return self._values.copy()
+
+ def get_refchange_recipients(self, refchange):
+ """Return the recipients for notifications about refchange.
+
+ Return the list of email addresses to which notifications
+ about the specified ReferenceChange should be sent."""
+
+ raise NotImplementedError()
+
+ def get_announce_recipients(self, annotated_tag_change):
+ """Return the recipients for notifications about annotated_tag_change.
+
+ Return the list of email addresses to which notifications
+ about the specified AnnotatedTagChange should be sent."""
+
+ raise NotImplementedError()
+
+ def get_reply_to_refchange(self, refchange):
+ return self.get_pusher_email()
+
+ def get_revision_recipients(self, revision):
+ """Return the recipients for messages about revision.
+
+ Return the list of email addresses to which notifications
+ about the specified Revision should be sent. This method
+ could be overridden, for example, to take into account the
+ contents of the revision when deciding whom to notify about
+ it. For example, there could be a scheme for users to express
+ interest in particular files or subdirectories, and only
+ receive notification emails for revisions that affecting those
+ files."""
+
+ raise NotImplementedError()
+
+ def get_reply_to_commit(self, revision):
+ return revision.author
+
+ def get_default_ref_ignore_regex(self):
+ # The commit messages of git notes are essentially meaningless
+ # and "filenames" in git notes commits are an implementation
+ # detail that might surprise users at first. As such, we
+ # would need a completely different method for handling emails
+ # of git notes in order for them to be of benefit for users,
+ # which we simply do not have right now.
+ return "^refs/notes/"
+
+ def get_max_subject_length(self):
+ """Return the maximal subject line (git log --oneline) length.
+ Longer subject lines will be truncated."""
+ raise NotImplementedError()
+
+ def filter_body(self, lines):
+ """Filter the lines intended for an email body.
+
+ lines is an iterable over the lines that would go into the
+ email body. Filter it (e.g., limit the number of lines, the
+ line length, character set, etc.), returning another iterable.
+ See FilterLinesEnvironmentMixin and MaxlinesEnvironmentMixin
+ for classes implementing this functionality."""
+
+ return lines
+
+ def log_msg(self, msg):
+ """Write the string msg on a log file or on stderr.
+
+ Sends the text to stderr by default, override to change the behavior."""
+ self.get_logger().info(msg)
+
+ def log_warning(self, msg):
+ """Write the string msg on a log file or on stderr.
+
+ Sends the text to stderr by default, override to change the behavior."""
+ self.get_logger().warning(msg)
+
+ def log_error(self, msg):
+ """Write the string msg on a log file or on stderr.
+
+ Sends the text to stderr by default, override to change the behavior."""
+ self.get_logger().error(msg)
+
+ def check(self):
+ pass
+
+
+class ConfigEnvironmentMixin(Environment):
+ """A mixin that sets self.config to its constructor's config argument.
+
+ This class's constructor consumes the "config" argument.
+
+ Mixins that need to inspect the config should inherit from this
+ class (1) to make sure that "config" is still in the constructor
+ arguments with its own constructor runs and/or (2) to be sure that
+ self.config is set after construction."""
+
+ def __init__(self, config, **kw):
+ super(ConfigEnvironmentMixin, self).__init__(**kw)
+ self.config = config
+
+
+class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
+ """An Environment that reads most of its information from "git config"."""
+
+ @staticmethod
+ def forbid_field_values(name, value, forbidden):
+ for forbidden_val in forbidden:
+ if value is not None and value.lower() == forbidden_val:
+ raise ConfigurationException(
+ '"%s" is not an allowed setting for %s' % (value, name)
+ )
+
+ def __init__(self, config, **kw):
+ super(ConfigOptionsEnvironmentMixin, self).__init__(
+ config=config, **kw
+ )
+
+ for var, cfg in (
+ ('announce_show_shortlog', 'announceshortlog'),
+ ('refchange_showgraph', 'refchangeShowGraph'),
+ ('refchange_showlog', 'refchangeshowlog'),
+ ('quiet', 'quiet'),
+ ('stdout', 'stdout'),
+ ):
+ val = config.get_bool(cfg)
+ if val is not None:
+ setattr(self, var, val)
+
+ commit_email_format = config.get('commitEmailFormat')
+ if commit_email_format is not None:
+ if commit_email_format != "html" and commit_email_format != "text":
+ self.log_warning(
+ '*** Unknown value for multimailhook.commitEmailFormat: %s\n' %
+ commit_email_format +
+ '*** Expected either "text" or "html". Ignoring.\n'
+ )
+ else:
+ self.commit_email_format = commit_email_format
+
+ html_in_intro = config.get_bool('htmlInIntro')
+ if html_in_intro is not None:
+ self.html_in_intro = html_in_intro
+
+ html_in_footer = config.get_bool('htmlInFooter')
+ if html_in_footer is not None:
+ self.html_in_footer = html_in_footer
+
+ self.commitBrowseURL = config.get('commitBrowseURL')
+
+ self.excludemergerevisions = config.get('excludeMergeRevisions')
+
+ maxcommitemails = config.get('maxcommitemails')
+ if maxcommitemails is not None:
+ try:
+ self.maxcommitemails = int(maxcommitemails)
+ except ValueError:
+ self.log_warning(
+ '*** Malformed value for multimailhook.maxCommitEmails: %s\n'
+ % maxcommitemails +
+ '*** Expected a number. Ignoring.\n'
+ )
+
+ diffopts = config.get('diffopts')
+ if diffopts is not None:
+ self.diffopts = shlex.split(diffopts)
+
+ graphopts = config.get('graphOpts')
+ if graphopts is not None:
+ self.graphopts = shlex.split(graphopts)
+
+ logopts = config.get('logopts')
+ if logopts is not None:
+ self.logopts = shlex.split(logopts)
+
+ commitlogopts = config.get('commitlogopts')
+ if commitlogopts is not None:
+ self.commitlogopts = shlex.split(commitlogopts)
+
+ date_substitute = config.get('dateSubstitute')
+ if date_substitute == 'none':
+ self.date_substitute = None
+ elif date_substitute is not None:
+ self.date_substitute = date_substitute
+
+ reply_to = config.get('replyTo')
+ self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
+ self.forbid_field_values('replyToRefchange',
+ self.__reply_to_refchange,
+ ['author'])
+ self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
+
+ self.from_refchange = config.get('fromRefchange')
+ self.forbid_field_values('fromRefchange',
+ self.from_refchange,
+ ['author', 'none'])
+ self.from_commit = config.get('fromCommit')
+ self.forbid_field_values('fromCommit',
+ self.from_commit,
+ ['none'])
+
+ combine = config.get_bool('combineWhenSingleCommit')
+ if combine is not None:
+ self.combine_when_single_commit = combine
+
+ self.log_file = config.get('logFile', default=None)
+ self.error_log_file = config.get('errorLogFile', default=None)
+ self.debug_log_file = config.get('debugLogFile', default=None)
+ if config.get_bool('Verbose', default=False):
+ self.verbose = 1
+ else:
+ self.verbose = 0
+
+ def get_administrator(self):
+ return (
+ self.config.get('administrator') or
+ self.get_sender() or
+ super(ConfigOptionsEnvironmentMixin, self).get_administrator()
+ )
+
+ def get_repo_shortname(self):
+ return (
+ self.config.get('reponame') or
+ super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
+ )
+
+ def get_emailprefix(self):
+ emailprefix = self.config.get('emailprefix')
+ if emailprefix is not None:
+ emailprefix = emailprefix.strip()
+ if emailprefix:
+ emailprefix += ' '
+ else:
+ emailprefix = '[%(repo_shortname)s] '
+ short_name = self.get_repo_shortname()
+ try:
+ return emailprefix % {'repo_shortname': short_name}
+ except:
+ self.get_logger().error(
+ '*** Invalid multimailhook.emailPrefix: %s\n' % emailprefix +
+ '*** %s\n' % sys.exc_info()[1] +
+ "*** Only the '%(repo_shortname)s' placeholder is allowed\n"
+ )
+ raise ConfigurationException(
+ '"%s" is not an allowed setting for emailPrefix' % emailprefix
+ )
+
+ def get_sender(self):
+ return self.config.get('envelopesender')
+
+ def process_addr(self, addr, change):
+ if addr.lower() == 'author':
+ if hasattr(change, 'author'):
+ return change.author
+ else:
+ return None
+ elif addr.lower() == 'pusher':
+ return self.get_pusher_email()
+ elif addr.lower() == 'none':
+ return None
+ else:
+ return addr
+
+ def get_fromaddr(self, change=None):
+ fromaddr = self.config.get('from')
+ if change:
+ specific_fromaddr = change.get_specific_fromaddr()
+ if specific_fromaddr:
+ fromaddr = specific_fromaddr
+ if fromaddr:
+ fromaddr = self.process_addr(fromaddr, change)
+ if fromaddr:
+ return fromaddr
+ return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr(change)
+
+ def get_reply_to_refchange(self, refchange):
+ if self.__reply_to_refchange is None:
+ return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_refchange(refchange)
+ else:
+ return self.process_addr(self.__reply_to_refchange, refchange)
+
+ def get_reply_to_commit(self, revision):
+ if self.__reply_to_commit is None:
+ return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision)
+ else:
+ return self.process_addr(self.__reply_to_commit, revision)
+
+ def get_scancommitforcc(self):
+ return self.config.get('scancommitforcc')
+
+
+class FilterLinesEnvironmentMixin(Environment):
+ """Handle encoding and maximum line length of body lines.
+
+ email_max_line_length (int or None)
+
+ The maximum length of any single line in the email body.
+ Longer lines are truncated at that length with ' [...]'
+ appended.
+
+ strict_utf8 (bool)
+
+ If this field is set to True, then the email body text is
+ expected to be UTF-8. Any invalid characters are
+ converted to U+FFFD, the Unicode replacement character
+ (encoded as UTF-8, of course).
+
+ """
+
+ def __init__(self, strict_utf8=True,
+ email_max_line_length=500, max_subject_length=500,
+ **kw):
+ super(FilterLinesEnvironmentMixin, self).__init__(**kw)
+ self.__strict_utf8 = strict_utf8
+ self.__email_max_line_length = email_max_line_length
+ self.__max_subject_length = max_subject_length
+
+ def filter_body(self, lines):
+ lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines)
+ if self.__strict_utf8:
+ if not PYTHON3:
+ lines = (line.decode(ENCODING, 'replace') for line in lines)
+ # Limit the line length in Unicode-space to avoid
+ # splitting characters:
+ if self.__email_max_line_length > 0:
+ lines = limit_linelength(lines, self.__email_max_line_length)
+ if not PYTHON3:
+ lines = (line.encode(ENCODING, 'replace') for line in lines)
+ elif self.__email_max_line_length:
+ lines = limit_linelength(lines, self.__email_max_line_length)
+
+ return lines
+
+ def get_max_subject_length(self):
+ return self.__max_subject_length
+
+
+class ConfigFilterLinesEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ FilterLinesEnvironmentMixin,
+ ):
+ """Handle encoding and maximum line length based on config."""
+
+ def __init__(self, config, **kw):
+ strict_utf8 = config.get_bool('emailstrictutf8', default=None)
+ if strict_utf8 is not None:
+ kw['strict_utf8'] = strict_utf8
+
+ email_max_line_length = config.get('emailmaxlinelength')
+ if email_max_line_length is not None:
+ kw['email_max_line_length'] = int(email_max_line_length)
+
+ max_subject_length = config.get('subjectMaxLength', default=email_max_line_length)
+ if max_subject_length is not None:
+ kw['max_subject_length'] = int(max_subject_length)
+
+ super(ConfigFilterLinesEnvironmentMixin, self).__init__(
+ config=config, **kw
+ )
+
+
+class MaxlinesEnvironmentMixin(Environment):
+ """Limit the email body to a specified number of lines."""
+
+ def __init__(self, emailmaxlines, **kw):
+ super(MaxlinesEnvironmentMixin, self).__init__(**kw)
+ self.__emailmaxlines = emailmaxlines
+
+ def filter_body(self, lines):
+ lines = super(MaxlinesEnvironmentMixin, self).filter_body(lines)
+ if self.__emailmaxlines > 0:
+ lines = limit_lines(lines, self.__emailmaxlines)
+ return lines
+
+
+class ConfigMaxlinesEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ MaxlinesEnvironmentMixin,
+ ):
+ """Limit the email body to the number of lines specified in config."""
+
+ def __init__(self, config, **kw):
+ emailmaxlines = int(config.get('emailmaxlines', default='0'))
+ super(ConfigMaxlinesEnvironmentMixin, self).__init__(
+ config=config,
+ emailmaxlines=emailmaxlines,
+ **kw
+ )
+
+
+class FQDNEnvironmentMixin(Environment):
+ """A mixin that sets the host's FQDN to its constructor argument."""
+
+ def __init__(self, fqdn, **kw):
+ super(FQDNEnvironmentMixin, self).__init__(**kw)
+ self.COMPUTED_KEYS += ['fqdn']
+ self.__fqdn = fqdn
+
+ def get_fqdn(self):
+ """Return the fully-qualified domain name for this host.
+
+ Return None if it is unavailable or unwanted."""
+
+ return self.__fqdn
+
+
+class ConfigFQDNEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ FQDNEnvironmentMixin,
+ ):
+ """Read the FQDN from the config."""
+
+ def __init__(self, config, **kw):
+ fqdn = config.get('fqdn')
+ super(ConfigFQDNEnvironmentMixin, self).__init__(
+ config=config,
+ fqdn=fqdn,
+ **kw
+ )
+
+
+class ComputeFQDNEnvironmentMixin(FQDNEnvironmentMixin):
+ """Get the FQDN by calling socket.getfqdn()."""
+
+ def __init__(self, **kw):
+ super(ComputeFQDNEnvironmentMixin, self).__init__(
+ fqdn=self.get_fqdn(),
+ **kw
+ )
+
+ def get_fqdn(self):
+ fqdn = socket.getfqdn()
+ # Sometimes, socket.getfqdn() returns localhost or
+ # localhost.localhost, which isn't very helpful. In this case,
+ # fall-back to socket.gethostname() which may return an actual
+ # hostname.
+ if fqdn == 'localhost' or fqdn == 'localhost.localdomain':
+ fqdn = socket.gethostname()
+ return fqdn
+
+
+class PusherDomainEnvironmentMixin(ConfigEnvironmentMixin):
+ """Deduce pusher_email from pusher by appending an emaildomain."""
+
+ def __init__(self, **kw):
+ super(PusherDomainEnvironmentMixin, self).__init__(**kw)
+ self.__emaildomain = self.config.get('emaildomain')
+
+ def get_pusher_email(self):
+ if self.__emaildomain:
+ # Derive the pusher's full email address in the default way:
+ return '%s@%s' % (self.get_pusher(), self.__emaildomain)
+ else:
+ return super(PusherDomainEnvironmentMixin, self).get_pusher_email()
+
+
+class StaticRecipientsEnvironmentMixin(Environment):
+ """Set recipients statically based on constructor parameters."""
+
+ def __init__(
+ self,
+ refchange_recipients, announce_recipients, revision_recipients, scancommitforcc,
+ **kw
+ ):
+ super(StaticRecipientsEnvironmentMixin, self).__init__(**kw)
+
+ # The recipients for various types of notification emails, as
+ # RFC 2822 email addresses separated by commas (or the empty
+ # string if no recipients are configured). Although there is
+ # a mechanism to choose the recipient lists based on on the
+ # actual *contents* of the change being reported, we only
+ # choose based on the *type* of the change. Therefore we can
+ # compute them once and for all:
+ self.__refchange_recipients = refchange_recipients
+ self.__announce_recipients = announce_recipients
+ self.__revision_recipients = revision_recipients
+
+ def check(self):
+ if not (self.get_refchange_recipients(None) or
+ self.get_announce_recipients(None) or
+ self.get_revision_recipients(None) or
+ self.get_scancommitforcc()):
+ raise ConfigurationException('No email recipients configured!')
+ super(StaticRecipientsEnvironmentMixin, self).check()
+
+ def get_refchange_recipients(self, refchange):
+ if self.__refchange_recipients is None:
+ return super(StaticRecipientsEnvironmentMixin,
+ self).get_refchange_recipients(refchange)
+ return self.__refchange_recipients
+
+ def get_announce_recipients(self, annotated_tag_change):
+ if self.__announce_recipients is None:
+ return super(StaticRecipientsEnvironmentMixin,
+ self).get_refchange_recipients(annotated_tag_change)
+ return self.__announce_recipients
+
+ def get_revision_recipients(self, revision):
+ if self.__revision_recipients is None:
+ return super(StaticRecipientsEnvironmentMixin,
+ self).get_refchange_recipients(revision)
+ return self.__revision_recipients
+
+
+class CLIRecipientsEnvironmentMixin(Environment):
+ """Mixin storing recipients information coming from the
+ command-line."""
+
+ def __init__(self, cli_recipients=None, **kw):
+ super(CLIRecipientsEnvironmentMixin, self).__init__(**kw)
+ self.__cli_recipients = cli_recipients
+
+ def get_refchange_recipients(self, refchange):
+ if self.__cli_recipients is None:
+ return super(CLIRecipientsEnvironmentMixin,
+ self).get_refchange_recipients(refchange)
+ return self.__cli_recipients
+
+ def get_announce_recipients(self, annotated_tag_change):
+ if self.__cli_recipients is None:
+ return super(CLIRecipientsEnvironmentMixin,
+ self).get_announce_recipients(annotated_tag_change)
+ return self.__cli_recipients
+
+ def get_revision_recipients(self, revision):
+ if self.__cli_recipients is None:
+ return super(CLIRecipientsEnvironmentMixin,
+ self).get_revision_recipients(revision)
+ return self.__cli_recipients
+
+
+class ConfigRecipientsEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ StaticRecipientsEnvironmentMixin
+ ):
+ """Determine recipients statically based on config."""
+
+ def __init__(self, config, **kw):
+ super(ConfigRecipientsEnvironmentMixin, self).__init__(
+ config=config,
+ refchange_recipients=self._get_recipients(
+ config, 'refchangelist', 'mailinglist',
+ ),
+ announce_recipients=self._get_recipients(
+ config, 'announcelist', 'refchangelist', 'mailinglist',
+ ),
+ revision_recipients=self._get_recipients(
+ config, 'commitlist', 'mailinglist',
+ ),
+ scancommitforcc=config.get('scancommitforcc'),
+ **kw
+ )
+
+ def _get_recipients(self, config, *names):
+ """Return the recipients for a particular type of message.
+
+ Return the list of email addresses to which a particular type
+ of notification email should be sent, by looking at the config
+ value for "multimailhook.$name" for each of names. Use the
+ value from the first name that is configured. The return
+ value is a (possibly empty) string containing RFC 2822 email
+ addresses separated by commas. If no configuration could be
+ found, raise a ConfigurationException."""
+
+ for name in names:
+ lines = config.get_all(name)
+ if lines is not None:
+ lines = [line.strip() for line in lines]
+ # Single "none" is a special value equivalence to empty string.
+ if lines == ['none']:
+ lines = ['']
+ return ', '.join(lines)
+ else:
+ return ''
+
+
+class StaticRefFilterEnvironmentMixin(Environment):
+ """Set branch filter statically based on constructor parameters."""
+
+ def __init__(self, ref_filter_incl_regex, ref_filter_excl_regex,
+ ref_filter_do_send_regex, ref_filter_dont_send_regex,
+ **kw):
+ super(StaticRefFilterEnvironmentMixin, self).__init__(**kw)
+
+ if ref_filter_incl_regex and ref_filter_excl_regex:
+ raise ConfigurationException(
+ "Cannot specify both a ref inclusion and exclusion regex.")
+ self.__is_inclusion_filter = bool(ref_filter_incl_regex)
+ default_exclude = self.get_default_ref_ignore_regex()
+ if ref_filter_incl_regex:
+ ref_filter_regex = ref_filter_incl_regex
+ elif ref_filter_excl_regex:
+ ref_filter_regex = ref_filter_excl_regex + '|' + default_exclude
+ else:
+ ref_filter_regex = default_exclude
+ try:
+ self.__compiled_regex = re.compile(ref_filter_regex)
+ except Exception:
+ raise ConfigurationException(
+ 'Invalid Ref Filter Regex "%s": %s' % (ref_filter_regex, sys.exc_info()[1]))
+
+ if ref_filter_do_send_regex and ref_filter_dont_send_regex:
+ raise ConfigurationException(
+ "Cannot specify both a ref doSend and dontSend regex.")
+ self.__is_do_send_filter = bool(ref_filter_do_send_regex)
+ if ref_filter_do_send_regex:
+ ref_filter_send_regex = ref_filter_do_send_regex
+ elif ref_filter_dont_send_regex:
+ ref_filter_send_regex = ref_filter_dont_send_regex
+ else:
+ ref_filter_send_regex = '.*'
+ self.__is_do_send_filter = True
+ try:
+ self.__send_compiled_regex = re.compile(ref_filter_send_regex)
+ except Exception:
+ raise ConfigurationException(
+ 'Invalid Ref Filter Regex "%s": %s' %
+ (ref_filter_send_regex, sys.exc_info()[1]))
+
+ def get_ref_filter_regex(self, send_filter=False):
+ if send_filter:
+ return self.__send_compiled_regex, self.__is_do_send_filter
+ else:
+ return self.__compiled_regex, self.__is_inclusion_filter
+
+
+class ConfigRefFilterEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ StaticRefFilterEnvironmentMixin
+ ):
+ """Determine branch filtering statically based on config."""
+
+ def _get_regex(self, config, key):
+ """Get a list of whitespace-separated regex. The refFilter* config
+ variables are multivalued (hence the use of get_all), and we
+ allow each entry to be a whitespace-separated list (hence the
+ split on each line). The whole thing is glued into a single regex."""
+ values = config.get_all(key)
+ if values is None:
+ return values
+ items = []
+ for line in values:
+ for i in line.split():
+ items.append(i)
+ if items == []:
+ return None
+ return '|'.join(items)
+
+ def __init__(self, config, **kw):
+ super(ConfigRefFilterEnvironmentMixin, self).__init__(
+ config=config,
+ ref_filter_incl_regex=self._get_regex(config, 'refFilterInclusionRegex'),
+ ref_filter_excl_regex=self._get_regex(config, 'refFilterExclusionRegex'),
+ ref_filter_do_send_regex=self._get_regex(config, 'refFilterDoSendRegex'),
+ ref_filter_dont_send_regex=self._get_regex(config, 'refFilterDontSendRegex'),
+ **kw
+ )
+
+
+class ProjectdescEnvironmentMixin(Environment):
+ """Make a "projectdesc" value available for templates.
+
+ By default, it is set to the first line of $GIT_DIR/description
+ (if that file is present and appears to be set meaningfully)."""
+
+ def __init__(self, **kw):
+ super(ProjectdescEnvironmentMixin, self).__init__(**kw)
+ self.COMPUTED_KEYS += ['projectdesc']
+
+ def get_projectdesc(self):
+ """Return a one-line description of the project."""
+
+ git_dir = get_git_dir()
+ try:
+ projectdesc = open(os.path.join(git_dir, 'description')).readline().strip()
+ if projectdesc and not projectdesc.startswith('Unnamed repository'):
+ return projectdesc
+ except IOError:
+ pass
+
+ return 'UNNAMED PROJECT'
+
+
+class GenericEnvironmentMixin(Environment):
+ def get_pusher(self):
+ return self.osenv.get('USER', self.osenv.get('USERNAME', 'unknown user'))
+
+
+class GitoliteEnvironmentHighPrecMixin(Environment):
+ def get_pusher(self):
+ return self.osenv.get('GL_USER', 'unknown user')
+
+
+class GitoliteEnvironmentLowPrecMixin(
+ ConfigEnvironmentMixin,
+ Environment):
+
+ def get_repo_shortname(self):
+ # The gitolite environment variable $GL_REPO is a pretty good
+ # repo_shortname (though it's probably not as good as a value
+ # the user might have explicitly put in his config).
+ return (
+ self.osenv.get('GL_REPO', None) or
+ super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
+ )
+
+ @staticmethod
+ def _compile_regex(re_template):
+ return (
+ re.compile(re_template % x)
+ for x in (
+ r'BEGIN\s+USER\s+EMAILS',
+ r'([^\s]+)\s+(.*)',
+ r'END\s+USER\s+EMAILS',
+ ))
+
+ def get_fromaddr(self, change=None):
+ GL_USER = self.osenv.get('GL_USER')
+ if GL_USER is not None:
+ # Find the path to gitolite.conf. Note that gitolite v3
+ # did away with the GL_ADMINDIR and GL_CONF environment
+ # variables (they are now hard-coded).
+ GL_ADMINDIR = self.osenv.get(
+ 'GL_ADMINDIR',
+ os.path.expanduser(os.path.join('~', '.gitolite')))
+ GL_CONF = self.osenv.get(
+ 'GL_CONF',
+ os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
+
+ mailaddress_map = self.config.get('MailaddressMap')
+ # If relative, consider relative to GL_CONF:
+ if mailaddress_map:
+ mailaddress_map = os.path.join(os.path.dirname(GL_CONF),
+ mailaddress_map)
+ if os.path.isfile(mailaddress_map):
+ f = open(mailaddress_map, 'rU')
+ try:
+ # Leading '#' is optional
+ re_begin, re_user, re_end = self._compile_regex(
+ r'^(?:\s*#)?\s*%s\s*$')
+ for l in f:
+ l = l.rstrip('\n')
+ if re_begin.match(l) or re_end.match(l):
+ continue # Ignore these lines
+ m = re_user.match(l)
+ if m:
+ if m.group(1) == GL_USER:
+ return m.group(2)
+ else:
+ continue # Not this user, but not an error
+ raise ConfigurationException(
+ "Syntax error in mail address map.\n"
+ "Check file {}.\n"
+ "Line: {}".format(mailaddress_map, l))
+
+ finally:
+ f.close()
+
+ if os.path.isfile(GL_CONF):
+ f = open(GL_CONF, 'rU')
+ try:
+ in_user_emails_section = False
+ re_begin, re_user, re_end = self._compile_regex(
+ r'^\s*#\s*%s\s*$')
+ for l in f:
+ l = l.rstrip('\n')
+ if not in_user_emails_section:
+ if re_begin.match(l):
+ in_user_emails_section = True
+ continue
+ if re_end.match(l):
+ break
+ m = re_user.match(l)
+ if m and m.group(1) == GL_USER:
+ return m.group(2)
+ finally:
+ f.close()
+ return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
+
+
+class IncrementalDateTime(object):
+ """Simple wrapper to give incremental date/times.
+
+ Each call will result in a date/time a second later than the
+ previous call. This can be used to falsify email headers, to
+ increase the likelihood that email clients sort the emails
+ correctly."""
+
+ def __init__(self):
+ self.time = time.time()
+ self.next = self.__next__ # Python 2 backward compatibility
+
+ def __next__(self):
+ formatted = formatdate(self.time, True)
+ self.time += 1
+ return formatted
+
+
+class StashEnvironmentHighPrecMixin(Environment):
+ def __init__(self, user=None, repo=None, **kw):
+ super(StashEnvironmentHighPrecMixin,
+ self).__init__(user=user, repo=repo, **kw)
+ self.__user = user
+ self.__repo = repo
+
+ def get_pusher(self):
+ return re.match(r'(.*?)\s*<', self.__user).group(1)
+
+ def get_pusher_email(self):
+ return self.__user
+
+
+class StashEnvironmentLowPrecMixin(Environment):
+ def __init__(self, user=None, repo=None, **kw):
+ super(StashEnvironmentLowPrecMixin, self).__init__(**kw)
+ self.__repo = repo
+ self.__user = user
+
+ def get_repo_shortname(self):
+ return self.__repo
+
+ def get_fromaddr(self, change=None):
+ return self.__user
+
+
+class GerritEnvironmentHighPrecMixin(Environment):
+ def __init__(self, project=None, submitter=None, update_method=None, **kw):
+ super(GerritEnvironmentHighPrecMixin,
+ self).__init__(submitter=submitter, project=project, **kw)
+ self.__project = project
+ self.__submitter = submitter
+ self.__update_method = update_method
+ "Make an 'update_method' value available for templates."
+ self.COMPUTED_KEYS += ['update_method']
+
+ def get_pusher(self):
+ if self.__submitter:
+ if self.__submitter.find('<') != -1:
+ # Submitter has a configured email, we transformed
+ # __submitter into an RFC 2822 string already.
+ return re.match(r'(.*?)\s*<', self.__submitter).group(1)
+ else:
+ # Submitter has no configured email, it's just his name.
+ return self.__submitter
+ else:
+ # If we arrive here, this means someone pushed "Submit" from
+ # the gerrit web UI for the CR (or used one of the programmatic
+ # APIs to do the same, such as gerrit review) and the
+ # merge/push was done by the Gerrit user. It was technically
+ # triggered by someone else, but sadly we have no way of
+ # determining who that someone else is at this point.
+ return 'Gerrit' # 'unknown user'?
+
+ def get_pusher_email(self):
+ if self.__submitter:
+ return self.__submitter
+ else:
+ return super(GerritEnvironmentHighPrecMixin, self).get_pusher_email()
+
+ def get_default_ref_ignore_regex(self):
+ default = super(GerritEnvironmentHighPrecMixin, self).get_default_ref_ignore_regex()
+ return default + '|^refs/changes/|^refs/cache-automerge/|^refs/meta/'
+
+ def get_revision_recipients(self, revision):
+ # Merge commits created by Gerrit when users hit "Submit this patchset"
+ # in the Web UI (or do equivalently with REST APIs or the gerrit review
+ # command) are not something users want to see an individual email for.
+ # Filter them out.
+ committer = read_git_output(['log', '--no-walk', '--format=%cN',
+ revision.rev.sha1])
+ if committer == 'Gerrit Code Review':
+ return []
+ else:
+ return super(GerritEnvironmentHighPrecMixin, self).get_revision_recipients(revision)
+
+ def get_update_method(self):
+ return self.__update_method
+
+
+class GerritEnvironmentLowPrecMixin(Environment):
+ def __init__(self, project=None, submitter=None, **kw):
+ super(GerritEnvironmentLowPrecMixin, self).__init__(**kw)
+ self.__project = project
+ self.__submitter = submitter
+
+ def get_repo_shortname(self):
+ return self.__project
+
+ def get_fromaddr(self, change=None):
+ if self.__submitter and self.__submitter.find('<') != -1:
+ return self.__submitter
+ else:
+ return super(GerritEnvironmentLowPrecMixin, self).get_fromaddr(change)
+
+
+class GiteaEnvironmentHighPrecMixin(Environment):
+ def get_pusher(self):
+ return self.osenv.get('GITEA_PUSHER_NAME', 'unknown user')
+
+ def get_pusher_email(self):
+ return self.osenv.get('GITEA_PUSHER_EMAIL')
+
+
+class GiteaEnvironmentLowPrecMixin(Environment):
+ def get_repo_shortname(self):
+ return self.osenv.get('GITEA_REPO_NAME', 'unknown repository')
+
+ def get_fromaddr(self, change=None):
+ # GITEA_PUSHER_NAME doesn't include the full name, just the user name
+ # at Gitea level, so it doesn't seem useful to use it and there
+ # doesn't seem to be any simple way to get it from Gitea neither.
+ return self.osenv.get('GITEA_PUSHER_EMAIL')
+
+
+class Push(object):
+ """Represent an entire push (i.e., a group of ReferenceChanges).
+
+ It is easy to figure out what commits were added to a *branch* by
+ a Reference change:
+
+ git rev-list change.old..change.new
+
+ or removed from a *branch*:
+
+ git rev-list change.new..change.old
+
+ But it is not quite so trivial to determine which entirely new
+ commits were added to the *repository* by a push and which old
+ commits were discarded by a push. A big part of the job of this
+ class is to figure out these things, and to make sure that new
+ commits are only detailed once even if they were added to multiple
+ references.
+
+ The first step is to determine the "other" references--those
+ unaffected by the current push. They are computed by listing all
+ references then removing any affected by this push. The results
+ are stored in Push._other_ref_sha1s.
+
+ The commits contained in the repository before this push were
+
+ git rev-list other1 other2 other3 ... change1.old change2.old ...
+
+ Where "changeN.old" is the old value of one of the references
+ affected by this push.
+
+ The commits contained in the repository after this push are
+
+ git rev-list other1 other2 other3 ... change1.new change2.new ...
+
+ The commits added by this push are the difference between these
+ two sets, which can be written
+
+ git rev-list \
+ ^other1 ^other2 ... \
+ ^change1.old ^change2.old ... \
+ change1.new change2.new ...
+
+ The commits removed by this push can be computed by
+
+ git rev-list \
+ ^other1 ^other2 ... \
+ ^change1.new ^change2.new ... \
+ change1.old change2.old ...
+
+ The last point is that it is possible that other pushes are
+ occurring simultaneously to this one, so reference values can
+ change at any time. It is impossible to eliminate all race
+ conditions, but we reduce the window of time during which problems
+ can occur by translating reference names to SHA1s as soon as
+ possible and working with SHA1s thereafter (because SHA1s are
+ immutable)."""
+
+ # A map {(changeclass, changetype): integer} specifying the order
+ # that reference changes will be processed if multiple reference
+ # changes are included in a single push. The order is significant
+ # mostly because new commit notifications are threaded together
+ # with the first reference change that includes the commit. The
+ # following order thus causes commits to be grouped with branch
+ # changes (as opposed to tag changes) if possible.
+ SORT_ORDER = dict(
+ (value, i) for (i, value) in enumerate([
+ (BranchChange, 'update'),
+ (BranchChange, 'create'),
+ (AnnotatedTagChange, 'update'),
+ (AnnotatedTagChange, 'create'),
+ (NonAnnotatedTagChange, 'update'),
+ (NonAnnotatedTagChange, 'create'),
+ (BranchChange, 'delete'),
+ (AnnotatedTagChange, 'delete'),
+ (NonAnnotatedTagChange, 'delete'),
+ (OtherReferenceChange, 'update'),
+ (OtherReferenceChange, 'create'),
+ (OtherReferenceChange, 'delete'),
+ ])
+ )
+
+ def __init__(self, environment, changes, ignore_other_refs=False):
+ self.changes = sorted(changes, key=self._sort_key)
+ self.__other_ref_sha1s = None
+ self.__cached_commits_spec = {}
+ self.environment = environment
+
+ if ignore_other_refs:
+ self.__other_ref_sha1s = set()
+
+ @classmethod
+ def _sort_key(klass, change):
+ return (klass.SORT_ORDER[change.__class__, change.change_type], change.refname,)
+
+ @property
+ def _other_ref_sha1s(self):
+ """The GitObjects referred to by references unaffected by this push.
+ """
+ if self.__other_ref_sha1s is None:
+ # The refnames being changed by this push:
+ updated_refs = set(
+ change.refname
+ for change in self.changes
+ )
+
+ # The SHA-1s of commits referred to by all references in this
+ # repository *except* updated_refs:
+ sha1s = set()
+ fmt = (
+ '%(objectname) %(objecttype) %(refname)\n'
+ '%(*objectname) %(*objecttype) %(refname)'
+ )
+ ref_filter_regex, is_inclusion_filter = \
+ self.environment.get_ref_filter_regex()
+ for line in read_git_lines(
+ ['for-each-ref', '--format=%s' % (fmt,)]):
+ (sha1, type, name) = line.split(' ', 2)
+ if (sha1 and type == 'commit' and
+ name not in updated_refs and
+ include_ref(name, ref_filter_regex, is_inclusion_filter)):
+ sha1s.add(sha1)
+
+ self.__other_ref_sha1s = sha1s
+
+ return self.__other_ref_sha1s
+
+ def _get_commits_spec_incl(self, new_or_old, reference_change=None):
+ """Get new or old SHA-1 from one or each of the changed refs.
+
+ Return a list of SHA-1 commit identifier strings suitable as
+ arguments to 'git rev-list' (or 'git log' or ...). The
+ returned identifiers are either the old or new values from one
+ or all of the changed references, depending on the values of
+ new_or_old and reference_change.
+
+ new_or_old is either the string 'new' or the string 'old'. If
+ 'new', the returned SHA-1 identifiers are the new values from
+ each changed reference. If 'old', the SHA-1 identifiers are
+ the old values from each changed reference.
+
+ If reference_change is specified and not None, only the new or
+ old reference from the specified reference is included in the
+ return value.
+
+ This function returns None if there are no matching revisions
+ (e.g., because a branch was deleted and new_or_old is 'new').
+ """
+
+ if not reference_change:
+ incl_spec = sorted(
+ getattr(change, new_or_old).sha1
+ for change in self.changes
+ if getattr(change, new_or_old)
+ )
+ if not incl_spec:
+ incl_spec = None
+ elif not getattr(reference_change, new_or_old).commit_sha1:
+ incl_spec = None
+ else:
+ incl_spec = [getattr(reference_change, new_or_old).commit_sha1]
+ return incl_spec
+
+ def _get_commits_spec_excl(self, new_or_old):
+ """Get exclusion revisions for determining new or discarded commits.
+
+ Return a list of strings suitable as arguments to 'git
+ rev-list' (or 'git log' or ...) that will exclude all
+ commits that, depending on the value of new_or_old, were
+ either previously in the repository (useful for determining
+ which commits are new to the repository) or currently in the
+ repository (useful for determining which commits were
+ discarded from the repository).
+
+ new_or_old is either the string 'new' or the string 'old'. If
+ 'new', the commits to be excluded are those that were in the
+ repository before the push. If 'old', the commits to be
+ excluded are those that are currently in the repository. """
+
+ old_or_new = {'old': 'new', 'new': 'old'}[new_or_old]
+ excl_revs = self._other_ref_sha1s.union(
+ getattr(change, old_or_new).sha1
+ for change in self.changes
+ if getattr(change, old_or_new).type in ['commit', 'tag']
+ )
+ return ['^' + sha1 for sha1 in sorted(excl_revs)]
+
+ def get_commits_spec(self, new_or_old, reference_change=None):
+ """Get rev-list arguments for added or discarded commits.
+
+ Return a list of strings suitable as arguments to 'git
+ rev-list' (or 'git log' or ...) that select those commits
+ that, depending on the value of new_or_old, are either new to
+ the repository or were discarded from the repository.
+
+ new_or_old is either the string 'new' or the string 'old'. If
+ 'new', the returned list is used to select commits that are
+ new to the repository. If 'old', the returned value is used
+ to select the commits that have been discarded from the
+ repository.
+
+ If reference_change is specified and not None, the new or
+ discarded commits are limited to those that are reachable from
+ the new or old value of the specified reference.
+
+ This function returns None if there are no added (or discarded)
+ revisions.
+ """
+ key = (new_or_old, reference_change)
+ if key not in self.__cached_commits_spec:
+ ret = self._get_commits_spec_incl(new_or_old, reference_change)
+ if ret is not None:
+ ret.extend(self._get_commits_spec_excl(new_or_old))
+ self.__cached_commits_spec[key] = ret
+ return self.__cached_commits_spec[key]
+
+ def get_new_commits(self, reference_change=None):
+ """Return a list of commits added by this push.
+
+ Return a list of the object names of commits that were added
+ by the part of this push represented by reference_change. If
+ reference_change is None, then return a list of *all* commits
+ added by this push."""
+
+ spec = self.get_commits_spec('new', reference_change)
+ return git_rev_list(spec)
+
+ def get_discarded_commits(self, reference_change):
+ """Return a list of commits discarded by this push.
+
+ Return a list of the object names of commits that were
+ entirely discarded from the repository by the part of this
+ push represented by reference_change."""
+
+ spec = self.get_commits_spec('old', reference_change)
+ return git_rev_list(spec)
+
+ def send_emails(self, mailer, body_filter=None):
+ """Use send all of the notification emails needed for this push.
+
+ Use send all of the notification emails (including reference
+ change emails and commit emails) needed for this push. Send
+ the emails using mailer. If body_filter is not None, then use
+ it to filter the lines that are intended for the email
+ body."""
+
+ # The sha1s of commits that were introduced by this push.
+ # They will be removed from this set as they are processed, to
+ # guarantee that one (and only one) email is generated for
+ # each new commit.
+ unhandled_sha1s = set(self.get_new_commits())
+ send_date = IncrementalDateTime()
+ for change in self.changes:
+ sha1s = []
+ for sha1 in reversed(list(self.get_new_commits(change))):
+ if sha1 in unhandled_sha1s:
+ sha1s.append(sha1)
+ unhandled_sha1s.remove(sha1)
+
+ # Check if we've got anyone to send to
+ if not change.recipients:
+ # mga: avoid unnecessary error messages when the summary
+ # email address is not configured (used for i18n mails).
+# change.environment.log_warning(
+# '*** no recipients configured so no email will be sent\n'
+# '*** for %r update %s->%s'
+# % (change.refname, change.old.sha1, change.new.sha1,)
+# )
+ pass
+ else:
+ if not change.environment.quiet:
+ change.environment.log_msg(
+ 'Sending notification emails to: %s' % (change.recipients,))
+ extra_values = {'send_date': next(send_date)}
+
+ rev = change.send_single_combined_email(sha1s)
+ if rev:
+ mailer.send(
+ change.generate_combined_email(self, rev, body_filter, extra_values),
+ rev.recipients,
+ )
+ # This change is now fully handled; no need to handle
+ # individual revisions any further.
+ continue
+ else:
+ mailer.send(
+ change.generate_email(self, body_filter, extra_values),
+ change.recipients,
+ )
+
+ max_emails = change.environment.maxcommitemails
+ if max_emails and len(sha1s) > max_emails:
+ change.environment.log_warning(
+ '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s) +
+ '*** Try setting multimailhook.maxCommitEmails to a greater value\n' +
+ '*** Currently, multimailhook.maxCommitEmails=%d' % max_emails
+ )
+ return
+
+ for (num, sha1) in enumerate(sha1s):
+ rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s))
+ if len(rev.parents) > 1 and change.environment.excludemergerevisions:
+ # skipping a merge commit
+ continue
+ if not rev.recipients and rev.cc_recipients:
+ change.environment.log_msg('*** Replacing Cc: with To:')
+ rev.recipients = rev.cc_recipients
+ rev.cc_recipients = None
+ if rev.recipients:
+ extra_values = {'send_date': next(send_date)}
+ mailer.send(
+ rev.generate_email(self, body_filter, extra_values),
+ rev.recipients,
+ )
+
+ # Consistency check:
+ if unhandled_sha1s:
+ change.environment.log_error(
+ 'ERROR: No emails were sent for the following new commits:\n'
+ ' %s'
+ % ('\n '.join(sorted(unhandled_sha1s)),)
+ )
+
+
+def include_ref(refname, ref_filter_regex, is_inclusion_filter):
+ does_match = bool(ref_filter_regex.search(refname))
+ if is_inclusion_filter:
+ return does_match
+ else: # exclusion filter -- we include the ref if the regex doesn't match
+ return not does_match
+
+
+def run_as_post_receive_hook(environment, mailer):
+ environment.check()
+ send_filter_regex, send_is_inclusion_filter = environment.get_ref_filter_regex(True)
+ ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(False)
+ changes = []
+ while True:
+ line = read_line(sys.stdin)
+ if line == '':
+ break
+ (oldrev, newrev, refname) = line.strip().split(' ', 2)
+ environment.get_logger().debug(
+ "run_as_post_receive_hook: oldrev=%s, newrev=%s, refname=%s" %
+ (oldrev, newrev, refname))
+
+ if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
+ continue
+ if not include_ref(refname, send_filter_regex, send_is_inclusion_filter):
+ continue
+ changes.append(
+ ReferenceChange.create(environment, oldrev, newrev, refname)
+ )
+ if not changes:
+ mailer.close()
+ return
+ push = Push(environment, changes)
+ try:
+ push.send_emails(mailer, body_filter=environment.filter_body)
+ finally:
+ mailer.close()
+
+
+def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
+ environment.check()
+ send_filter_regex, send_is_inclusion_filter = environment.get_ref_filter_regex(True)
+ ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(False)
+ if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
+ return
+ if not include_ref(refname, send_filter_regex, send_is_inclusion_filter):
+ return
+ changes = [
+ ReferenceChange.create(
+ environment,
+ read_git_output(['rev-parse', '--verify', oldrev]),
+ read_git_output(['rev-parse', '--verify', newrev]),
+ refname,
+ ),
+ ]
+ if not changes:
+ mailer.close()
+ return
+ push = Push(environment, changes, force_send)
+ try:
+ push.send_emails(mailer, body_filter=environment.filter_body)
+ finally:
+ mailer.close()
+
+
+def check_ref_filter(environment):
+ send_filter_regex, send_is_inclusion = environment.get_ref_filter_regex(True)
+ ref_filter_regex, ref_is_inclusion = environment.get_ref_filter_regex(False)
+
+ def inc_exc_lusion(b):
+ if b:
+ return 'inclusion'
+ else:
+ return 'exclusion'
+
+ if send_filter_regex:
+ sys.stdout.write("DoSend/DontSend filter regex (" +
+ (inc_exc_lusion(send_is_inclusion)) +
+ '): ' + send_filter_regex.pattern +
+ '\n')
+ if send_filter_regex:
+ sys.stdout.write("Include/Exclude filter regex (" +
+ (inc_exc_lusion(ref_is_inclusion)) +
+ '): ' + ref_filter_regex.pattern +
+ '\n')
+ sys.stdout.write(os.linesep)
+
+ sys.stdout.write(
+ "Refs marked as EXCLUDE are excluded by either refFilterInclusionRegex\n"
+ "or refFilterExclusionRegex. No emails will be sent for commits included\n"
+ "in these refs.\n"
+ "Refs marked as DONT-SEND are excluded by either refFilterDoSendRegex or\n"
+ "refFilterDontSendRegex, but not by either refFilterInclusionRegex or\n"
+ "refFilterExclusionRegex. Emails will be sent for commits included in these\n"
+ "refs only when the commit reaches a ref which isn't excluded.\n"
+ "Refs marked as DO-SEND are not excluded by any filter. Emails will\n"
+ "be sent normally for commits included in these refs.\n")
+
+ sys.stdout.write(os.linesep)
+
+ for refname in read_git_lines(['for-each-ref', '--format', '%(refname)']):
+ sys.stdout.write(refname)
+ if not include_ref(refname, ref_filter_regex, ref_is_inclusion):
+ sys.stdout.write(' EXCLUDE')
+ elif not include_ref(refname, send_filter_regex, send_is_inclusion):
+ sys.stdout.write(' DONT-SEND')
+ else:
+ sys.stdout.write(' DO-SEND')
+
+ sys.stdout.write(os.linesep)
+
+
+def show_env(environment, out):
+ out.write('Environment values:\n')
+ for (k, v) in sorted(environment.get_values().items()):
+ if k: # Don't show the {'' : ''} pair.
+ out.write(' %s : %r\n' % (k, v))
+ out.write('\n')
+ # Flush to avoid interleaving with further log output
+ out.flush()
+
+
+def check_setup(environment):
+ environment.check()
+ show_env(environment, sys.stdout)
+ sys.stdout.write("Now, checking that git-multimail's standard input "
+ "is properly set ..." + os.linesep)
+ sys.stdout.write("Please type some text and then press Return" + os.linesep)
+ stdin = sys.stdin.readline()
+ sys.stdout.write("You have just entered:" + os.linesep)
+ sys.stdout.write(stdin)
+ sys.stdout.write("git-multimail seems properly set up." + os.linesep)
+
+
+def choose_mailer(config, environment):
+ mailer = config.get('mailer', default='sendmail')
+
+ if mailer == 'smtp':
+ smtpserver = config.get('smtpserver', default='localhost')
+ smtpservertimeout = float(config.get('smtpservertimeout', default=10.0))
+ smtpserverdebuglevel = int(config.get('smtpserverdebuglevel', default=0))
+ smtpencryption = config.get('smtpencryption', default='none')
+ smtpuser = config.get('smtpuser', default='')
+ smtppass = config.get('smtppass', default='')
+ smtpcacerts = config.get('smtpcacerts', default='')
+ mailer = SMTPMailer(
+ environment,
+ envelopesender=(environment.get_sender() or environment.get_fromaddr()),
+ smtpserver=smtpserver, smtpservertimeout=smtpservertimeout,
+ smtpserverdebuglevel=smtpserverdebuglevel,
+ smtpencryption=smtpencryption,
+ smtpuser=smtpuser,
+ smtppass=smtppass,
+ smtpcacerts=smtpcacerts
+ )
+ elif mailer == 'sendmail':
+ command = config.get('sendmailcommand')
+ if command:
+ command = shlex.split(command)
+ mailer = SendMailer(environment,
+ command=command, envelopesender=environment.get_sender())
+ else:
+ environment.log_error(
+ 'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer +
+ 'please use one of "smtp" or "sendmail".'
+ )
+ sys.exit(1)
+ return mailer
+
+
+KNOWN_ENVIRONMENTS = {
+ 'generic': {'highprec': GenericEnvironmentMixin},
+ 'gitolite': {'highprec': GitoliteEnvironmentHighPrecMixin,
+ 'lowprec': GitoliteEnvironmentLowPrecMixin},
+ 'stash': {'highprec': StashEnvironmentHighPrecMixin,
+ 'lowprec': StashEnvironmentLowPrecMixin},
+ 'gerrit': {'highprec': GerritEnvironmentHighPrecMixin,
+ 'lowprec': GerritEnvironmentLowPrecMixin},
+ 'gitea': {'highprec': GiteaEnvironmentHighPrecMixin,
+ 'lowprec': GiteaEnvironmentLowPrecMixin},
+ }
+
+
+def choose_environment(config, osenv=None, env=None, recipients=None,
+ hook_info=None):
+ env_name = choose_environment_name(config, env, osenv)
+ environment_klass = build_environment_klass(env_name)
+ env = build_environment(environment_klass, env_name, config,
+ osenv, recipients, hook_info)
+ return env
+
+
+def choose_environment_name(config, env, osenv):
+ if not osenv:
+ osenv = os.environ
+
+ if not env:
+ env = config.get('environment')
+
+ if not env:
+ if 'GL_USER' in osenv and 'GL_REPO' in osenv:
+ env = 'gitolite'
+ elif 'GITEA_PUSHER_NAME' in osenv and 'GITEA_REPO_NAME' in osenv:
+ env = 'gitea'
+ else:
+ env = 'generic'
+ return env
+
+
+COMMON_ENVIRONMENT_MIXINS = [
+ ConfigRecipientsEnvironmentMixin,
+ CLIRecipientsEnvironmentMixin,
+ ConfigRefFilterEnvironmentMixin,
+ ProjectdescEnvironmentMixin,
+ ConfigMaxlinesEnvironmentMixin,
+ ComputeFQDNEnvironmentMixin,
+ ConfigFilterLinesEnvironmentMixin,
+ PusherDomainEnvironmentMixin,
+ ConfigOptionsEnvironmentMixin,
+ ]
+
+
+def build_environment_klass(env_name):
+ if 'class' in KNOWN_ENVIRONMENTS[env_name]:
+ return KNOWN_ENVIRONMENTS[env_name]['class']
+
+ environment_mixins = []
+ known_env = KNOWN_ENVIRONMENTS[env_name]
+ if 'highprec' in known_env:
+ high_prec_mixin = known_env['highprec']
+ environment_mixins.append(high_prec_mixin)
+ environment_mixins = environment_mixins + COMMON_ENVIRONMENT_MIXINS
+ if 'lowprec' in known_env:
+ low_prec_mixin = known_env['lowprec']
+ environment_mixins.append(low_prec_mixin)
+ environment_mixins.append(Environment)
+ klass_name = env_name.capitalize() + 'Environment'
+ environment_klass = type(
+ klass_name,
+ tuple(environment_mixins),
+ {},
+ )
+ KNOWN_ENVIRONMENTS[env_name]['class'] = environment_klass
+ return environment_klass
+
+
+GiteaEnvironment = build_environment_klass('gitea')
+GerritEnvironment = build_environment_klass('gerrit')
+StashEnvironment = build_environment_klass('stash')
+GitoliteEnvironment = build_environment_klass('gitolite')
+GenericEnvironment = build_environment_klass('generic')
+
+
+def build_environment(environment_klass, env, config,
+ osenv, recipients, hook_info):
+ environment_kw = {
+ 'osenv': osenv,
+ 'config': config,
+ }
+
+ if env == 'stash':
+ environment_kw['user'] = hook_info['stash_user']
+ environment_kw['repo'] = hook_info['stash_repo']
+ elif env == 'gerrit':
+ environment_kw['project'] = hook_info['project']
+ environment_kw['submitter'] = hook_info['submitter']
+ environment_kw['update_method'] = hook_info['update_method']
+
+ environment_kw['cli_recipients'] = recipients
+
+ return environment_klass(**environment_kw)
+
+
+def get_version():
+ oldcwd = os.getcwd()
+ try:
+ try:
+ os.chdir(os.path.dirname(os.path.realpath(__file__)))
+ git_version = read_git_output(['describe', '--tags', 'HEAD'])
+ if git_version == __version__:
+ return git_version
+ else:
+ return '%s (%s)' % (__version__, git_version)
+ except:
+ pass
+ finally:
+ os.chdir(oldcwd)
+ return __version__
+
+
+def compute_gerrit_options(options, args, required_gerrit_options,
+ raw_refname):
+ if None in required_gerrit_options:
+ raise SystemExit("Error: Specify all of --oldrev, --newrev, --refname, "
+ "and --project; or none of them.")
+
+ if options.environment not in (None, 'gerrit'):
+ raise SystemExit("Non-gerrit environments incompatible with --oldrev, "
+ "--newrev, --refname, and --project")
+ options.environment = 'gerrit'
+
+ if args:
+ raise SystemExit("Error: Positional parameters not allowed with "
+ "--oldrev, --newrev, and --refname.")
+
+ # Gerrit oddly omits 'refs/heads/' in the refname when calling
+ # ref-updated hook; put it back.
+ git_dir = get_git_dir()
+ if (not os.path.exists(os.path.join(git_dir, raw_refname)) and
+ os.path.exists(os.path.join(git_dir, 'refs', 'heads',
+ raw_refname))):
+ options.refname = 'refs/heads/' + options.refname
+
+ # New revisions can appear in a gerrit repository either due to someone
+ # pushing directly (in which case options.submitter will be set), or they
+ # can press "Submit this patchset" in the web UI for some CR (in which
+ # case options.submitter will not be set and gerrit will not have provided
+ # us the information about who pressed the button).
+ #
+ # Note for the nit-picky: I'm lumping in REST API calls and the ssh
+ # gerrit review command in with "Submit this patchset" button, since they
+ # have the same effect.
+ if options.submitter:
+ update_method = 'pushed'
+ # The submitter argument is almost an RFC 2822 email address; change it
+ # from 'User Name (email@domain)' to 'User Name <email@domain>' so it is
+ options.submitter = options.submitter.replace('(', '<').replace(')', '>')
+ else:
+ update_method = 'submitted'
+ # Gerrit knew who submitted this patchset, but threw that information
+ # away when it invoked this hook. However, *IF* Gerrit created a
+ # merge to bring the patchset in (project 'Submit Type' is either
+ # "Always Merge", or is "Merge if Necessary" and happens to be
+ # necessary for this particular CR), then it will have the committer
+ # of that merge be 'Gerrit Code Review' and the author will be the
+ # person who requested the submission of the CR. Since this is fairly
+ # likely for most gerrit installations (of a reasonable size), it's
+ # worth the extra effort to try to determine the actual submitter.
+ rev_info = read_git_lines(['log', '--no-walk', '--merges',
+ '--format=%cN%n%aN <%aE>', options.newrev])
+ if rev_info and rev_info[0] == 'Gerrit Code Review':
+ options.submitter = rev_info[1]
+
+ # We pass back refname, oldrev, newrev as args because then the
+ # gerrit ref-updated hook is much like the git update hook
+ return (options,
+ [options.refname, options.oldrev, options.newrev],
+ {'project': options.project, 'submitter': options.submitter,
+ 'update_method': update_method})
+
+
+def check_hook_specific_args(options, args):
+ raw_refname = options.refname
+ # Convert each string option unicode for Python3.
+ if PYTHON3:
+ opts = ['environment', 'recipients', 'oldrev', 'newrev', 'refname',
+ 'project', 'submitter', 'stash_user', 'stash_repo']
+ for opt in opts:
+ if not hasattr(options, opt):
+ continue
+ obj = getattr(options, opt)
+ if obj:
+ enc = obj.encode('utf-8', 'surrogateescape')
+ dec = enc.decode('utf-8', 'replace')
+ setattr(options, opt, dec)
+
+ # First check for stash arguments
+ if (options.stash_user is None) != (options.stash_repo is None):
+ raise SystemExit("Error: Specify both of --stash-user and "
+ "--stash-repo or neither.")
+ if options.stash_user:
+ options.environment = 'stash'
+ return options, args, {'stash_user': options.stash_user,
+ 'stash_repo': options.stash_repo}
+
+ # Finally, check for gerrit specific arguments
+ required_gerrit_options = (options.oldrev, options.newrev, options.refname,
+ options.project)
+ if required_gerrit_options != (None,) * 4:
+ return compute_gerrit_options(options, args, required_gerrit_options,
+ raw_refname)
+
+ # No special options in use, just return what we started with
+ return options, args, {}
+
+
+class Logger(object):
+ def parse_verbose(self, verbose):
+ if verbose > 0:
+ return logging.DEBUG
+ else:
+ return logging.INFO
+
+ def create_log_file(self, environment, name, path, verbosity):
+ log_file = logging.getLogger(name)
+ file_handler = logging.FileHandler(path)
+ log_fmt = logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s")
+ file_handler.setFormatter(log_fmt)
+ log_file.addHandler(file_handler)
+ log_file.setLevel(verbosity)
+ return log_file
+
+ def __init__(self, environment):
+ self.environment = environment
+ self.loggers = []
+ stderr_log = logging.getLogger('git_multimail.stderr')
+
+ class EncodedStderr(object):
+ def write(self, x):
+ write_str(sys.stderr, x)
+
+ def flush(self):
+ sys.stderr.flush()
+
+ stderr_handler = logging.StreamHandler(EncodedStderr())
+ stderr_log.addHandler(stderr_handler)
+ stderr_log.setLevel(self.parse_verbose(environment.verbose))
+ self.loggers.append(stderr_log)
+
+ if environment.debug_log_file is not None:
+ debug_log_file = self.create_log_file(
+ environment, 'git_multimail.debug', environment.debug_log_file, logging.DEBUG)
+ self.loggers.append(debug_log_file)
+
+ if environment.log_file is not None:
+ log_file = self.create_log_file(
+ environment, 'git_multimail.file', environment.log_file, logging.INFO)
+ self.loggers.append(log_file)
+
+ if environment.error_log_file is not None:
+ error_log_file = self.create_log_file(
+ environment, 'git_multimail.error', environment.error_log_file, logging.ERROR)
+ self.loggers.append(error_log_file)
+
+ def info(self, msg, *args, **kwargs):
+ for l in self.loggers:
+ l.info(msg, *args, **kwargs)
+
+ def debug(self, msg, *args, **kwargs):
+ for l in self.loggers:
+ l.debug(msg, *args, **kwargs)
+
+ def warning(self, msg, *args, **kwargs):
+ for l in self.loggers:
+ l.warning(msg, *args, **kwargs)
+
+ def error(self, msg, *args, **kwargs):
+ for l in self.loggers:
+ l.error(msg, *args, **kwargs)
+
+
+def main(args):
+ parser = optparse.OptionParser(
+ description=__doc__,
+ usage='%prog [OPTIONS]\n or: %prog [OPTIONS] REFNAME OLDREV NEWREV',
+ )
+
+ parser.add_option(
+ '--environment', '--env', action='store', type='choice',
+ choices=list(KNOWN_ENVIRONMENTS.keys()), default=None,
+ help=(
+ 'Choose type of environment is in use. Default is taken from '
+ 'multimailhook.environment if set; otherwise "generic".'
+ ),
+ )
+ parser.add_option(
+ '--stdout', action='store_true', default=False,
+ help='Output emails to stdout rather than sending them.',
+ )
+ parser.add_option(
+ '--recipients', action='store', default=None,
+ help='Set list of email recipients for all types of emails.',
+ )
+ parser.add_option(
+ '--show-env', action='store_true', default=False,
+ help=(
+ 'Write to stderr the values determined for the environment '
+ '(intended for debugging purposes), then proceed normally.'
+ ),
+ )
+ parser.add_option(
+ '--force-send', action='store_true', default=False,
+ help=(
+ 'Force sending refchange email when using as an update hook. '
+ 'This is useful to work around the unreliable new commits '
+ 'detection in this mode.'
+ ),
+ )
+ parser.add_option(
+ '-c', metavar="<name>=<value>", action='append',
+ help=(
+ 'Pass a configuration parameter through to git. The value given '
+ 'will override values from configuration files. See the -c option '
+ 'of git(1) for more details. (Only works with git >= 1.7.3)'
+ ),
+ )
+ parser.add_option(
+ '--version', '-v', action='store_true', default=False,
+ help=(
+ "Display git-multimail's version"
+ ),
+ )
+
+ parser.add_option(
+ '--python-version', action='store_true', default=False,
+ help=(
+ "Display the version of Python used by git-multimail"
+ ),
+ )
+
+ parser.add_option(
+ '--check-ref-filter', action='store_true', default=False,
+ help=(
+ 'List refs and show information on how git-multimail '
+ 'will process them.'
+ )
+ )
+
+ # The following options permit this script to be run as a gerrit
+ # ref-updated hook. See e.g.
+ # code.google.com/p/gerrit/source/browse/Documentation/config-hooks.txt
+ # We suppress help for these items, since these are specific to gerrit,
+ # and we don't want users directly using them any way other than how the
+ # gerrit ref-updated hook is called.
+ parser.add_option('--oldrev', action='store', help=optparse.SUPPRESS_HELP)
+ parser.add_option('--newrev', action='store', help=optparse.SUPPRESS_HELP)
+ parser.add_option('--refname', action='store', help=optparse.SUPPRESS_HELP)
+ parser.add_option('--project', action='store', help=optparse.SUPPRESS_HELP)
+ parser.add_option('--submitter', action='store', help=optparse.SUPPRESS_HELP)
+
+ # The following allow this to be run as a stash asynchronous post-receive
+ # hook (almost identical to a git post-receive hook but triggered also for
+ # merges of pull requests from the UI). We suppress help for these items,
+ # since these are specific to stash.
+ parser.add_option('--stash-user', action='store', help=optparse.SUPPRESS_HELP)
+ parser.add_option('--stash-repo', action='store', help=optparse.SUPPRESS_HELP)
+
+ (options, args) = parser.parse_args(args)
+ (options, args, hook_info) = check_hook_specific_args(options, args)
+
+ if options.version:
+ sys.stdout.write('git-multimail version ' + get_version() + '\n')
+ return
+
+ if options.python_version:
+ sys.stdout.write('Python version ' + sys.version + '\n')
+ return
+
+ if options.c:
+ Config.add_config_parameters(options.c)
+
+ config = Config('multimailhook')
+
+ environment = None
+ try:
+ environment = choose_environment(
+ config, osenv=os.environ,
+ env=options.environment,
+ recipients=options.recipients,
+ hook_info=hook_info,
+ )
+
+ if options.show_env:
+ show_env(environment, sys.stderr)
+
+ if options.stdout or environment.stdout:
+ mailer = OutputMailer(sys.stdout, environment)
+ else:
+ mailer = choose_mailer(config, environment)
+
+ must_check_setup = os.environ.get('GIT_MULTIMAIL_CHECK_SETUP')
+ if must_check_setup == '':
+ must_check_setup = False
+ if options.check_ref_filter:
+ check_ref_filter(environment)
+ elif must_check_setup:
+ check_setup(environment)
+ # Dual mode: if arguments were specified on the command line, run
+ # like an update hook; otherwise, run as a post-receive hook.
+ elif args:
+ if len(args) != 3:
+ parser.error('Need zero or three non-option arguments')
+ (refname, oldrev, newrev) = args
+ environment.get_logger().debug(
+ "run_as_update_hook: refname=%s, oldrev=%s, newrev=%s, force_send=%s" %
+ (refname, oldrev, newrev, options.force_send))
+ run_as_update_hook(environment, mailer, refname, oldrev, newrev, options.force_send)
+ else:
+ run_as_post_receive_hook(environment, mailer)
+ except ConfigurationException:
+ sys.exit(sys.exc_info()[1])
+ except SystemExit:
+ raise
+ except Exception:
+ t, e, tb = sys.exc_info()
+ import traceback
+ sys.stderr.write('\n') # Avoid mixing message with previous output
+ msg = (
+ 'Exception \'' + t.__name__ +
+ '\' raised. Please report this as a bug to\n'
+ 'https://github.com/git-multimail/git-multimail/issues\n'
+ 'with the information below:\n\n'
+ 'git-multimail version ' + get_version() + '\n'
+ 'Python version ' + sys.version + '\n' +
+ traceback.format_exc())
+ try:
+ environment.get_logger().error(msg)
+ except:
+ sys.stderr.write(msg)
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/deployment/mgagit/manifests/init.pp b/deployment/mgagit/manifests/init.pp
new file mode 100644
index 00000000..42753b03
--- /dev/null
+++ b/deployment/mgagit/manifests/init.pp
@@ -0,0 +1,170 @@
+class mgagit(
+ $git_dir = '/git',
+ $ldap_server = "ldap.${::domain}",
+ $binddn = 'uid=mgagit,ou=People,dc=mageia,dc=org',
+ $vhost = "projects.${::domain}",
+ $bindpw
+){
+ $git_login = 'git'
+ $git_homedir = "/var/lib/${git_login}"
+ $gitolite_dir = "${git_homedir}/.gitolite"
+ $gitolite_keydir = "${gitolite_dir}/keydir"
+ $gitolite_tmpldir = '/etc/mgagit/tmpl'
+ $gitolite_confdir = "${gitolite_dir}/conf"
+ $gitolite_hooksdir = "${gitolite_dir}/hooks"
+ $gitolite_commonhooksdir = "${gitolite_hooksdir}/common"
+ $gitolite_conf = "${gitolite_confdir}/gitolite.conf"
+ $gitoliterc = "${git_homedir}/.gitolite.rc"
+ $bindpwfile = '/etc/mgagit.secret'
+ $reposconf_dir = "${git_homedir}/repos-config"
+ $vhostdir = "${git_homedir}/www"
+
+ package { ['mgagit', 'gitolite', 'python3-bugz']:
+ ensure => installed,
+ }
+
+ group { $git_login:
+ ensure => present,
+ }
+
+ user { $git_login:
+ ensure => present,
+ home => $git_homedir,
+ managehome => true,
+ gid => $git_login,
+ }
+
+ file { '/etc/mgagit.conf':
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0644',
+ content => template('mgagit/mgagit.conf'),
+ require => Package['mgagit'],
+ }
+
+ file { "${gitolite_commonhooksdir}/git_multimail.py":
+ ensure => present,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0644',
+ source => 'puppet:///modules/mgagit/git_multimail.py',
+ require => File[$gitolite_commonhooksdir],
+ }
+
+ file { "${gitolite_commonhooksdir}/post-receive":
+ ensure => present,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0755',
+ content => template('mgagit/git-post-receive-hook'),
+ require => File[$gitolite_commonhooksdir],
+ }
+
+ file { "${gitolite_commonhooksdir}/post-update":
+ ensure => present,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0755',
+ content => template('mgagit/git-post-update-hook'),
+ require => File[$gitolite_commonhooksdir],
+ }
+
+ file { $gitolite_tmpldir:
+ ensure => directory,
+ owner => root,
+ group => root,
+ mode => '0755',
+ }
+
+ file { "${gitolite_tmpldir}/group.gl":
+ ensure => 'link',
+ target => '/usr/share/mgagit/tmpl/group.gl',
+ }
+
+ file { "${gitolite_tmpldir}/repodef_repo.gl":
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0644',
+ content => template('mgagit/repodef_repo.gl'),
+ }
+
+ mgagit::tmpl { 'artwork':
+ tmpldir => $gitolite_tmpldir,
+ ml => 'atelier',
+ }
+
+ mgagit::tmpl { 'doc':
+ tmpldir => $gitolite_tmpldir,
+ ml => 'atelier', # NB This is wrong, we should have a doc-commits@ ML (and thus remove this line)
+ }
+
+ mgagit::tmpl { 'infrastructure':
+ tmpldir => $gitolite_tmpldir,
+ group => 'sysadmin',
+ ml => 'sysadmin',
+ }
+
+ mgagit::tmpl { 'org':
+ tmpldir => $gitolite_tmpldir,
+ group => 'board',
+ ml => 'board',
+ }
+
+ mgagit::tmpl { 'qa':
+ tmpldir => $gitolite_tmpldir,
+ }
+
+ mgagit::tmpl { 'soft':
+ tmpldir => $gitolite_tmpldir,
+ group => 'packagers-committers',
+ }
+
+ mgagit::tmpl { 'web':
+ tmpldir => $gitolite_tmpldir,
+ ml => 'atelier',
+ }
+
+ file { [$gitolite_dir, $gitolite_keydir, $gitolite_confdir,
+ $gitolite_hooksdir, $gitolite_commonhooksdir,
+ $reposconf_dir, $vhostdir]:
+ ensure => directory,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0755',
+ }
+
+ file { $gitoliterc:
+ ensure => present,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0644',
+ content => template('mgagit/gitolite.rc'),
+ }
+
+ file { $bindpwfile:
+ ensure => present,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0600',
+ content => inline_template('<%= @bindpw %>'),
+ }
+
+ file { $git_dir:
+ ensure => directory,
+ owner => $git_login,
+ group => $git_login,
+ mode => '0755',
+ }
+
+ file { "${git_homedir}/repositories":
+ ensure => 'link',
+ target => $git_dir,
+ }
+
+ apache::vhost::base { $vhost:
+ location => $vhostdir,
+ }
+}
+# vim: sw=2
diff --git a/deployment/mgagit/manifests/tmpl.pp b/deployment/mgagit/manifests/tmpl.pp
new file mode 100644
index 00000000..ef188ed2
--- /dev/null
+++ b/deployment/mgagit/manifests/tmpl.pp
@@ -0,0 +1,9 @@
+define mgagit::tmpl($tmpldir, $group = $title, $ml = $title) {
+ file { "${tmpldir}/${title}_repo.gl":
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0644',
+ content => template('mgagit/group_owned_repo.gl'),
+ }
+}
diff --git a/deployment/mgagit/templates/git-post-receive-hook b/deployment/mgagit/templates/git-post-receive-hook
new file mode 100755
index 00000000..68da3200
--- /dev/null
+++ b/deployment/mgagit/templates/git-post-receive-hook
@@ -0,0 +1,314 @@
+#!/usr/bin/python3
+
+import configparser
+import os
+import re
+import sys
+import urllib.error
+import urllib.parse
+import urllib.request
+import xmlrpc.client
+from dataclasses import dataclass
+
+LIBDIR = '<%= @gitolite_commonhooksdir %>'
+sys.path.insert(0, LIBDIR)
+
+import bugz.settings
+
+import git_multimail
+
+# When editing this list, remember to edit the same list in
+# modules/cgit/templates/filter.commit-links.sh
+BUG_REFS = {
+ 'Mageia': { 're': re.compile('mga#([0-9]+)'), 'replace': 'https://bugs.mageia.org/%s' },
+ 'Red Hat': { 're': re.compile('rhbz#([0-9]+)'), 'replace': 'https://bugzilla.redhat.com/show_bug.cgi?id=%s' },
+ 'Free Desktop': { 're': re.compile('fdo#([0-9]+)'), 'replace': 'https://bugs.freedesktop.org/show_bug.cgi?id=%s' },
+ 'KDE': { 're': re.compile('(?:bko|kde)#([0-9]+)'), 'replace': 'https://bugs.kde.org/show_bug.cgi?id=%s' },
+ 'GNOME': { 're': re.compile('(?:bgo|gnome)#([0-9]+)'), 'replace': 'https://bugzilla.gnome.org/show_bug.cgi?id=%s' },
+ 'Launchpad': { 're': re.compile('lp#([0-9]+)'), 'replace': 'https://launchpad.net/bugs/%s' },
+}
+
+COMMIT_RE = re.compile('^commit ([a-f0-9]{40})')
+COMMIT_REPLACE = 'https://gitweb.mageia.org/%s/commit/?id=%s'
+
+MAGEIA_BUGZILLA_URL = 'https://bugs.mageia.org/xmlrpc.cgi'
+MAGEIA_BUGZILLA_PASSWORD_FILE = '.gitzilla-password'
+MAGEIA_BUGZILLA_APIKEY_FILE = '.gitzilla-apikey' # this holds a Bugzilla API key
+GITWEB_UPDATE_URL = 'http://gitweb.mageia.org:8000'
+
+# Bugzilla user
+USER_LOGIN = 'bot'
+
+# Recipient of i18n notifications
+I18N_MAIL = 'i18n-reports@ml.mageia.org'
+
+# Set to 1..3 for debug logging (WARNING: this will show passwords to git committers!)
+DEBUG = 0
+
+git_multimail.FOOTER_TEMPLATE = """\
+
+-- \n\
+Mageia Git Monkeys.
+"""
+git_multimail.REVISION_FOOTER_TEMPLATE = git_multimail.FOOTER_TEMPLATE
+
+I18N_REVISION_HEADER_TEMPLATE = """\
+Date: %(send_date)s
+To: %(recipients)s
+Subject: %(emailprefix)s%(oneline)s
+MIME-Version: 1.0
+Content-Type: text/plain; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+In-Reply-To: %(reply_to_msgid)s
+References: %(reply_to_msgid)s
+X-Git-Host: %(fqdn)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Rev: %(rev)s
+Auto-Submitted: auto-generated
+"""
+
+
+REPO_NAME_RE = re.compile(r'^/git/(?P<name>.+?)(?:\.git)?$')
+
+
+# Log a debug message when logging is enabled
+def debug(s, *a):
+ if DEBUG > 0:
+ print(s % a)
+
+
+def repo_shortname():
+ basename = os.path.abspath(git_multimail.get_git_dir())
+ m = REPO_NAME_RE.match(basename)
+ if m:
+ return m.group('name')
+ else:
+ return basename
+
+
+# Override the Environment class to generate an appropriate short name which is
+# used in git links and as an email prefix
+class MageiaEnvironment(git_multimail.Environment):
+ def get_repo_shortname(self):
+ return repo_shortname()
+
+
+git_multimail.Environment = MageiaEnvironment
+
+
+# Override the Revision class to inject gitweb/cgit links and any referenced
+# bug URLs
+class MageiaLinksRevision(git_multimail.Revision):
+ def __init__(self, reference_change, rev, num, tot):
+ super().__init__(reference_change, rev, num, tot)
+ self.bz = None
+
+ def bugzilla_init(self):
+ if not self.bz:
+ tokenfile = os.path.join(os.environ['HOME'], MAGEIA_BUGZILLA_APIKEY_FILE)
+ token = None
+ try:
+ token = open(tokenfile, 'r').readline().rstrip()
+ except IOError:
+ # Assume username/password will be used instead
+ pass
+
+ passwordfile = os.path.join(os.environ['HOME'], MAGEIA_BUGZILLA_PASSWORD_FILE)
+ pword = None
+ try:
+ pword = open(passwordfile, 'r').readline().rstrip()
+ except IOError:
+ print('Error: no Bugzilla credentials available; trying anyway')
+ # There's no real point in continuing, but why not
+
+ class ConfigSettings:
+ pass
+
+ cfg = ConfigSettings()
+ cfg.connection = 'Mageia'
+ if token:
+ # If an API key is found, that will be used instead of user/password
+ cfg.key = token
+ cfg.user = USER_LOGIN
+ cfg.password = pword
+ cfg.base = MAGEIA_BUGZILLA_URL
+ cfg.debug = DEBUG
+
+ cfile = configparser.ConfigParser()
+ cfile.add_section(cfg.connection)
+ self.bz = bugz.settings.Settings(cfg, cfile)
+
+ def generate_email_body(self, push):
+ """Show this revision."""
+ output = git_multimail.read_git_lines(
+ ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
+ keepends=True,
+ )
+ bugs = {}
+ commit = None
+ idx = 0
+ # Modify the mail output on-the-fly to add links; this is sensitive to
+ # the mail format produced by git_multimail. Also, update Mageia
+ # Bugzilla if a bug reference is found.
+ for line in output:
+ idx += 1
+ if line == "---\n":
+ if commit and COMMIT_REPLACE:
+ output.insert(idx, "\n")
+ output.insert(idx, " %s\n" % (COMMIT_REPLACE % (self.environment.get_repo_shortname(), commit)))
+ output.insert(idx, " Commit Link:\n")
+ idx += 3
+ if bugs:
+ output.insert(idx, " Bug links:\n")
+ idx += 1
+ for tracker, bugnos in bugs.items():
+ output.insert(idx, " %s\n" % tracker)
+ idx += 1
+ for bugno in bugnos:
+ output.insert(idx, " %s\n" % (BUG_REFS[tracker]['replace'] % bugno))
+ idx += 1
+ output.insert(idx, "\n")
+ idx += 1
+
+ # Attempt to modify Bugzilla
+ if "Mageia" in bugs:
+ try:
+ self.bugzilla_init()
+
+ # Mask email address
+ comment = None
+ # Suppress the "Bug links:" section if only one bug
+ # is referenced
+ if len(bugs) == 1 and len(bugs['Mageia']) == 1:
+ comment = output[0:idx-4]
+ else:
+ comment = output[0:idx]
+ comment[1] = re.sub(r'^(Author: [^@]*)@.*(>)?', r'\1@...>', comment[1])
+ comment = "".join(comment)
+
+ params = {}
+ params['ids'] = bugs['Mageia']
+ params['comment'] = { 'body': comment }
+ self.bz.call_bz(self.bz.bz.Bug.update, params)
+ print("Updated bugzilla bugs: %s" % ", ".join(bugs['Mageia']))
+ except:
+ print("Unable to post to bugzilla bugs: %s :(" % ", ".join(bugs['Mageia']))
+ print(sys.exc_info()[1])
+
+ break
+
+ m = COMMIT_RE.search(line)
+ if m:
+ commit = m.group(1)
+ for tracker in BUG_REFS.keys():
+ foundbugs = BUG_REFS[tracker]['re'].findall(line)
+ if foundbugs:
+ if tracker not in bugs:
+ bugs[tracker] = foundbugs
+ else:
+ bugs[tracker] = list(set(bugs[tracker] + foundbugs))
+
+ return output
+
+
+# Override the Revision class to inject gitweb/cgit links and any referenced
+# bug URLs
+class MageiaI18NRevision(git_multimail.Revision):
+ """A Change consisting of a single git commit."""
+
+ def __init__(self, reference_change, rev, num, tot):
+ super().__init__(reference_change, rev, num, tot)
+
+ # Don't send to any of the normal recipients
+ self.recipients = False
+ self.cc_recipients = ''
+
+ i18n_folders = []
+ # Check files and find i18n folders
+ for line in git_multimail.read_git_lines(['ls-tree', '-rd', self.rev.sha1]):
+ (modetypesha1, name) = line.split("\t", 1)
+ if name.endswith("/.tx"):
+ i18n_folders.append(os.path.dirname(name))
+
+ if i18n_folders:
+ self.output = git_multimail.read_git_lines(
+ ['log', '-C', '--stat', '-p', '--no-walk', self.rev.sha1, '--'] + i18n_folders,
+ keepends=True,
+ )
+ if self.output:
+ # We have some output so let's send the mail...
+ self.recipients = I18N_MAIL
+ print(f'Sending i8n notification to {self.recipients}')
+
+
+ def generate_email_body(self, push):
+ """Show this revision."""
+
+ return self.output
+
+
+def main():
+ # Attempt to write a last-updated file for cgit cosmetics
+ git_dir = git_multimail.get_git_dir()
+ infowebdir = os.path.join(git_dir, 'info', 'web')
+ lastupdated = git_multimail.read_git_output(
+ ['for-each-ref', '--sort=-committerdate', "--format=%(committerdate:iso8601)", '--count=1', 'refs/heads'],
+ )
+ try:
+ if not os.path.exists(infowebdir):
+ os.makedirs(infowebdir)
+ with open(os.path.join(infowebdir, 'last-modified'), 'w') as modfile:
+ modfile.write(lastupdated)
+ except Exception:
+ debug('Warning: could not update git last-modified date: %s', sys.exc_info()[1])
+
+ # Contact the on-the-pull service on the gitweb server with the updated repo
+ try:
+ req = urllib.request.Request(GITWEB_UPDATE_URL, (repo_shortname() + '.git').encode('utf-8'))
+ req.add_header('Content-Type', 'x-git/repo')
+ fp = urllib.request.urlopen(req, timeout=5)
+ fp.close()
+ except Exception:
+ debug('Warning: could not contact gitweb server: %s', sys.exc_info()[1])
+
+ config = git_multimail.Config('multimailhook')
+
+ try:
+ environment = git_multimail.choose_environment(
+ config, osenv=os.environ,
+ )
+
+ mailer = git_multimail.choose_mailer(config, environment)
+ # For testing...send mail to stdout only
+ #mailer = git_multimail.OutputMailer(sys.stdout)
+
+ changes = []
+ for line in sys.stdin:
+ (oldrev, newrev, refname) = line.strip().split(' ', 2)
+ changes.append(
+ git_multimail.ReferenceChange.create(environment, oldrev, newrev, refname)
+ )
+ push = git_multimail.Push(environment, changes)
+
+ # First pass - regular commit mails
+ git_multimail.Revision = MageiaLinksRevision
+ push.send_emails(mailer, body_filter=environment.filter_body)
+
+ # Second pass - i18n commit mails
+ git_multimail.REVISION_HEADER_TEMPLATE = I18N_REVISION_HEADER_TEMPLATE
+ git_multimail.Revision = MageiaI18NRevision
+ # Don't send the summary email, so nuke the change recipients
+ for change in push.changes:
+ change.recipients = False
+ push.send_emails(mailer, body_filter=environment.filter_body)
+
+ except git_multimail.ConfigurationException as e:
+ sys.exit(str(e))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/deployment/mgagit/templates/git-post-update-hook b/deployment/mgagit/templates/git-post-update-hook
new file mode 100644
index 00000000..a2550ad3
--- /dev/null
+++ b/deployment/mgagit/templates/git-post-update-hook
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+if [ "${GL_REPO:0:28}" == "infrastructure/repositories/" ]; then
+ OK=no
+ (unset GIT_DIR && mgagit glconf && mgagit glrun) >/dev/null 2>&1 && OK=yes
+ if [ "$OK" == "yes" ]; then
+ echo
+ echo " *** Repository definitions updated"
+ echo
+ fi
+fi
+exit 0 \ No newline at end of file
diff --git a/deployment/mgagit/templates/gitolite.rc b/deployment/mgagit/templates/gitolite.rc
new file mode 100644
index 00000000..c4c925e6
--- /dev/null
+++ b/deployment/mgagit/templates/gitolite.rc
@@ -0,0 +1,161 @@
+# configuration variables for gitolite
+
+# This file is in perl syntax. But you do NOT need to know perl to edit it --
+# just mind the commas, use single quotes unless you know what you're doing,
+# and make sure the brackets and braces stay matched up!
+
+# (Tip: perl allows a comma after the last item in a list also!)
+
+# HELP for commands can be had by running the command with "-h".
+
+# HELP for all the other FEATURES can be found in the documentation (look for
+# "list of non-core programs shipped with gitolite" in the master index) or
+# directly in the corresponding source file.
+
+%RC = (
+
+ # ------------------------------------------------------------------
+
+ # default umask gives you perms of '0700'; see the rc file docs for
+ # how/why you might change this
+ UMASK => 0022,
+
+ # look for "git-config" in the documentation
+ GIT_CONFIG_KEYS => 'gitweb\.description gitweb\.owner multimailhook\.mailingList multimailhook\.emailDomain multimailhook\.envelopeSender',
+
+ # comment out if you don't need all the extra detail in the logfile
+ LOG_EXTRA => 1,
+
+ # roles. add more roles (like MANAGER, TESTER, ...) here.
+ # WARNING: if you make changes to this hash, you MUST run 'gitolite
+ # compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
+ ROLES => {
+ READERS => 1,
+ WRITERS => 1,
+ },
+
+ # ------------------------------------------------------------------
+
+ # rc variables used by various features
+
+ # the 'info' command prints this as additional info, if it is set
+ # SITE_INFO => 'Please see http://blahblah/gitolite for more help',
+
+ # the 'desc' command uses this
+ # WRITER_CAN_UPDATE_DESC => 1,
+
+ # the CpuTime feature uses these
+ # display user, system, and elapsed times to user after each git operation
+ # DISPLAY_CPU_TIME => 1,
+ # display a warning if total CPU times (u, s, cu, cs) crosses this limit
+ # CPU_TIME_WARN_LIMIT => 0.1,
+
+ # the Mirroring feature needs this
+ # HOSTNAME => "foo",
+
+ # if you enabled 'Shell', you need this
+ # SHELL_USERS_LIST => "$ENV{HOME}/.gitolite.shell-users",
+
+ # ------------------------------------------------------------------
+
+ # List of commands and features to enable
+
+ ENABLE => [
+
+ # COMMANDS
+
+ # These are the commands enabled by default
+ 'help',
+ 'desc',
+ 'info',
+ 'perms',
+ 'writable',
+
+ # Uncomment or add new commands here.
+ # 'create',
+ # 'fork',
+ # 'mirror',
+ # 'sskm',
+ # 'D',
+
+ # These FEATURES are enabled by default.
+
+ # essential (unless you're using smart-http mode)
+ 'ssh-authkeys',
+
+ # creates git-config entities from gitolite.conf file entries like 'config foo.bar = baz'
+ 'git-config',
+
+ # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
+ 'daemon',
+
+ # creates projects.list file; if you don't use gitweb, comment this out
+ 'gitweb',
+
+ # These FEATURES are disabled by default; uncomment to enable. If you
+ # need to add new ones, ask on the mailing list :-)
+
+ # user-visible behaviour
+
+ # prevent wild repos auto-create on fetch/clone
+ # 'no-create-on-read',
+ # no auto-create at all (don't forget to enable the 'create' command!)
+ # 'no-auto-create',
+
+ # access a repo by another (possibly legacy) name
+ # 'Alias',
+
+ # give some users direct shell access
+ # 'Shell',
+
+ # set default roles from lines like 'option default.roles-1 = ...', etc.
+ # 'set-default-roles',
+
+ # system admin stuff
+
+ # enable mirroring (don't forget to set the HOSTNAME too!)
+ # 'Mirroring',
+
+ # allow people to submit pub files with more than one key in them
+ # 'ssh-authkeys-split',
+
+ # selective read control hack
+ # 'partial-copy',
+
+ # manage local, gitolite-controlled, copies of read-only upstream repos
+ # 'upstream',
+
+ # updates 'description' file instead of 'gitweb.description' config item
+ 'cgit',
+
+ # performance, logging, monitoring...
+
+ # be nice
+ # 'renice 10',
+
+ # log CPU times (user, system, cumulative user, cumulative system)
+ # 'CpuTime',
+
+ # syntactic_sugar for gitolite.conf and included files
+
+ # allow backslash-escaped continuation lines in gitolite.conf
+ # 'continuation-lines',
+
+ # create implicit user groups from directory names in keydir/
+ # 'keysubdirs-as-groups',
+
+ # allow simple line-oriented macros
+ # 'macros',
+
+ ],
+
+);
+
+# ------------------------------------------------------------------------------
+# per perl rules, this should be the last line in such a file:
+1;
+
+# Local variables:
+# mode: perl
+# End:
+# vim: set syn=perl:
diff --git a/deployment/mgagit/templates/group_owned_repo.gl b/deployment/mgagit/templates/group_owned_repo.gl
new file mode 100644
index 00000000..14431d6c
--- /dev/null
+++ b/deployment/mgagit/templates/group_owned_repo.gl
@@ -0,0 +1,36 @@
+[% SET maintainer = r.repos.$repo.maintainer %]
+repo [% repo %]
+ RW+ master$ = [% maintainer %]
+ RW+ distro/ = [% maintainer %]
+ RW+ topic/ = [% maintainer %]
+ RW+ refs/tags/ = [% maintainer %]
+[% IF r.repos.$repo.lockdown != 'yes' -%]
+ RW master$ = @mga-<%= @group %>
+ RW distro/ = @mga-<%= @group %>
+ RW+ topic/ = @mga-<%= @group %>
+ RW refs/tags/ = @mga-<%= @group %>
+ RW master$ = @mga-i18n-committers
+ RW distro/ = @mga-i18n-committers
+ RW topic/ = @mga-i18n-committers
+[% END -%]
+ RW+ master$ = @mga-sysadmin
+ RW+ distro/ = @mga-sysadmin
+ RW+ topic/ = @mga-sysadmin
+ RW+ refs/tags/ = @mga-sysadmin
+ RW+ user/USER/ = @all
+ R = @all
+[% IF r.repos.$repo.noemail -%]
+ config multimailhook.mailingList = ""
+[% ELSE -%]
+[% IF r.repos.$repo.mailingList -%]
+ config multimailhook.mailingList = "[% r.repos.$repo.mailingList %]"
+[% ELSE -%]
+ config multimailhook.mailingList = "<%= @ml %>-commits@ml.mageia.org"
+[% END -%]
+[% END -%]
+ config multimailhook.emailDomain = "mageia.org"
+ config multimailhook.envelopeSender = "root@mageia.org"
+ config gitweb.description = "[% r.repos.$repo.description %]"
+[% IF r.users.$maintainer -%]
+ config gitweb.owner = "[% r.users.$maintainer.cn.0 %] [[% maintainer %]]"
+[% END -%]
diff --git a/deployment/mgagit/templates/mgagit.conf b/deployment/mgagit/templates/mgagit.conf
new file mode 100644
index 00000000..82b1d66a
--- /dev/null
+++ b/deployment/mgagit/templates/mgagit.conf
@@ -0,0 +1,57 @@
+---
+use_ldap: yes
+ldapserver: <%= @ldap_server %>
+binddn: <%= @binddn %>
+bindpwfile: <%= @bindpwfile %>
+pubkey_dir: <%= @gitolite_keydir %>
+tmpl_dir: <%= @gitolite_tmpldir %>
+gitolite_config: <%= @gitolite_conf %>
+run_gitolite: yes
+repodef_dir: <%= @reposconf_dir %>
+repos_config:
+ - prefix: infrastructure/repositories
+ gl_template: repodef_repo
+ repos:
+ - name: artwork
+ maintainer: '@mga-sysadmin'
+ description: Artwork repository definitions
+ - name: doc
+ maintainer: '@mga-sysadmin'
+ description: Documentation repository definitions
+ - name: infrastructure
+ maintainer: '@mga-sysadmin'
+ description: Infrastructure repository definitions
+ - name: org
+ maintainer: '@mga-sysadmin'
+ description: Organization repository definitions
+ - name: qa
+ maintainer: '@mga-sysadmin'
+ description: QA repository definitions
+ - name: software
+ maintainer: '@mga-sysadmin'
+ description: Software repository definitions
+ - name: web
+ maintainer: '@mga-sysadmin'
+ description: Website repository definitions
+ - prefix: artwork
+ gl_template: artwork_repo
+ git_url: file:///git/infrastructure/repositories/artwork.git
+ - prefix: doc
+ gl_template: doc_repo
+ git_url: file:///git/infrastructure/repositories/doc.git
+ - prefix: infrastructure
+ gl_template: infrastructure_repo
+ git_url: file:///git/infrastructure/repositories/infrastructure.git
+ - prefix: org
+ gl_template: org_repo
+ git_url: file:///git/infrastructure/repositories/org.git
+ - prefix: qa
+ gl_template: qa_repo
+ git_url: file:///git/infrastructure/repositories/qa.git
+ - prefix: software
+ gl_template: soft_repo
+ git_url: file:///git/infrastructure/repositories/software.git
+ - prefix: web
+ gl_template: web_repo
+ git_url: file:///git/infrastructure/repositories/web.git
+www_dir: <%= @vhostdir %>
diff --git a/deployment/mgagit/templates/repodef_repo.gl b/deployment/mgagit/templates/repodef_repo.gl
new file mode 100644
index 00000000..12c946f5
--- /dev/null
+++ b/deployment/mgagit/templates/repodef_repo.gl
@@ -0,0 +1,8 @@
+repo [% repo %]
+ RW master$ = [% r.repos.$repo.maintainer %]
+ RW+ user/USER/ = @all
+ R = @all
+ config multimailhook.mailingList = "sysadmin-commits@ml.mageia.org"
+ config multimailhook.emailDomain = "mageia.org"
+ config multimailhook.envelopeSender = "root@mageia.org"
+ config gitweb.description = "[% r.repos.$repo.description %]"
diff --git a/deployment/releasekey/manifests/init.pp b/deployment/releasekey/manifests/init.pp
index 66f66b60..a3c99526 100644
--- a/deployment/releasekey/manifests/init.pp
+++ b/deployment/releasekey/manifests/init.pp
@@ -1,7 +1,7 @@
class releasekey {
$sign_login = 'releasekey'
- $sign_home_dir = "/var/lib/$sign_login"
- $sign_keydir = "$sign_home_dir/keys"
+ $sign_home_dir = "/var/lib/${sign_login}"
+ $sign_keydir = "${sign_home_dir}/keys"
group { $sign_login: }
user { $sign_login:
@@ -12,16 +12,16 @@ class releasekey {
}
gnupg::keys{ 'release':
- email => "release@$::domain",
+ email => "release@${::domain}",
#FIXME there should be a variable somewhere to change the name of the distribution
key_name => 'Mageia Release',
login => $sign_login,
- batchdir => "$sign_home_dir/batches",
+ batchdir => "${sign_home_dir}/batches",
keydir => $sign_keydir,
require => User[$sign_login],
}
- mga-common::local_script { 'sign_checksums':
+ mga_common::local_script { 'sign_checksums':
content => template('releasekey/sign_checksums'),
}
}
diff --git a/deployment/releasekey/templates/sign_checksums b/deployment/releasekey/templates/sign_checksums
index 762dde7b..5edf7e57 100644
--- a/deployment/releasekey/templates/sign_checksums
+++ b/deployment/releasekey/templates/sign_checksums
@@ -6,6 +6,6 @@ fi
directory=$1
cd "$directory"
-for chksum in *.md5 *.sha1; do
- gpg --homedir "<%= sign_keydir %>" --yes --sign "$chksum"
+for chksum in *.md5 *.sha3 *.sha512; do
+ gpg --homedir "<%= @sign_keydir %>" --yes --sign "$chksum"
done
diff --git a/deployment/reports/templates/socket.yaml b/deployment/reports/templates/socket.yaml
index 075d7a9f..6b0a8b33 100644
--- a/deployment/reports/templates/socket.yaml
+++ b/deployment/reports/templates/socket.yaml
@@ -1,2 +1,2 @@
---
-socket_path: /var/lib/ii/<%= nick %>/<%= server %>/<%= channel %>/in
+socket_path: /var/lib/ii/<%= @nick %>/<%= @server %>/<%= @channel %>/in
diff --git a/deployment/repositories/manifests/git.pp b/deployment/repositories/manifests/git.pp
deleted file mode 100644
index 46445647..00000000
--- a/deployment/repositories/manifests/git.pp
+++ /dev/null
@@ -1,11 +0,0 @@
-class repositories::git {
- git::repository { '/git/forum':
- description => "Reference code for forum.$::domain",
- group => 'mga-forum-developers',
- }
-
- git::repository { '/git/initscripts':
- description => 'Source for initscripts package',
- group => 'mga-packagers-committers',
- }
-}
diff --git a/deployment/repositories/manifests/git_mirror.pp b/deployment/repositories/manifests/git_mirror.pp
index 9d213441..7384b5a8 100644
--- a/deployment/repositories/manifests/git_mirror.pp
+++ b/deployment/repositories/manifests/git_mirror.pp
@@ -1,16 +1,16 @@
class repositories::git_mirror {
- file { '/git':
- ensure => directory,
- }
+ #file { '/git':
+ # ensure => directory,
+ #}
- git::mirror { '/git/forum/':
- description => "Reference code for forum.$::domain",
- source => "git://git.$::domain/forum/",
- }
+ #git::mirror { '/git/forum/':
+ # description => "Reference code for forum.${::domain}",
+ # source => "git://git.${::domain}/forum/",
+ #}
- git::mirror { '/git/initscripts/':
- description => 'Reference code for Initscripts',
- source => "git://git.$::domain/initscripts/",
- }
+ #git::mirror { '/git/initscripts/':
+ # description => 'Reference code for Initscripts',
+ # source => "git://git.${::domain}/initscripts/",
+ #}
}
diff --git a/deployment/repositories/manifests/sparkleshare.pp b/deployment/repositories/manifests/sparkleshare.pp
deleted file mode 100644
index 302e07c1..00000000
--- a/deployment/repositories/manifests/sparkleshare.pp
+++ /dev/null
@@ -1,11 +0,0 @@
-class repositories::sparkleshare {
- $sp_dir = '/git/sparkleshare'
- file { $sp_dir:
- ensure => directory,
- }
-
- git::repository { "$sp_dir/test":
- description => 'Test sparkleshare repository',
- group => 'mga-packagers-committers',
- }
-}
diff --git a/deployment/repositories/manifests/subversion.pp b/deployment/repositories/manifests/subversion.pp
index 56b6db6d..4c4ef847 100644
--- a/deployment/repositories/manifests/subversion.pp
+++ b/deployment/repositories/manifests/subversion.pp
@@ -1,15 +1,19 @@
class repositories::subversion {
- Subversion::Repository {
- cia_post => true,
- cia_ignore_author => '^schedbot$',
- }
+ # Be sure that any mailing list found in commit_mail here whitelists
+ # "subversion_noreply@ml.mageia.org" as a sender by adding it to
+ # sender_email in its sympa::list::X configuration in
+ # deployment/lists/manifests/init.pp
subversion::repository { '/svn/adm/':
group => 'mga-sysadmin',
- commit_mail => ["sysadmin-commits@ml.$::domain"],
+ commit_mail => ["sysadmin-commits@ml.${::domain}"],
syntax_check => ['check_puppet_templates','check_puppet'],
- cia_module => 'sysadm',
+ }
+
+ subversion::repository { '/svn/advisories':
+ group => 'mga-qa-committers',
+ commit_mail => ["qa-commits@ml.${::domain}"],
}
sudo::sudoers_config { 'puppet_update':
@@ -22,55 +26,49 @@ class repositories::subversion {
subversion::repository { '/svn/org/':
group => 'mga-board',
- commit_mail => ["board-commits@ml.$::domain"],
- cia_post => true,
- cia_module => 'org',
+ commit_mail => ["board-commits@ml.${::domain}"],
}
subversion::repository { '/svn/soft/':
group => 'mga-packagers',
- commit_mail => ["soft-commits@ml.$::domain"],
+ commit_mail => ["soft-commits@ml.${::domain}"],
syntax_check => ['check_po'],
- cia_module => 'soft',
- i18n_mail => ["mageia-i18n@$::domain"],
+ i18n_mail => ["i18n-reports@ml.${::domain}"],
}
subversion::repository { '/svn/soft_publish/':
group => 'mga-packagers',
- commit_mail => ["soft-commits@ml.$::domain"],
- cia_post => true,
- cia_module => 'soft_publish',
+ commit_mail => ["soft-commits@ml.${::domain}"],
}
subversion::repository { '/svn/web/':
group => 'mga-web',
+ commit_mail => ["atelier-commits@ml.${::domain}"],
syntax_check => ['check_php'],
- cia_module => 'web',
}
subversion::repository { '/svn/packages/':
group => 'mga-packagers-committers',
no_binary => true,
- commit_mail => ["packages-commits@ml.$::domain"],
- cia_module => 'packages',
- }
-
- file { '/svn/binrepos/':
- ensure => directory,
- mode => '0700',
+ commit_mail => ["packages-commits@ml.${::domain}"],
+ nonmaintainer_mail => true,
}
- subversion::repository { '/svn/binrepos/cauldron/':
- group => 'mga-packagers-committers',
- cia_module => 'binrepos',
- }
-
- file { '/svn/binrepos/updates/':
- ensure => directory
+ subversion::repository { '/svn/test-irker/':
+ group => 'mga-packagers',
+ no_binary => true,
+ commit_mail => ["tmb@${::domain}"],
+ irker_conf => {
+ project => 'mageia',
+ repo => 'testrepo',
+ tinyifier => 'https://is.gd/create.php?format=simple&url=',
+ urlprefix => "https://svnweb.${::domain}/%(repo)?view=revision&revision=",
+ channels => '{irc://chat.freenode.net/commits, irc://chat.freenode.net/test-irker}',
+ },
}
- subversion::repository { '/svn/binrepos/updates/1/':
- group => 'mga-packagers-committers',
- cia_module => 'binrepos_1',
+ subversion::repository { '/svn/treasurer/':
+ group => 'mga-treasurer',
+ commit_mail => ["treasurer-commits@ml.${::domain}"],
}
}
diff --git a/deployment/repositories/manifests/svn_mirror.pp b/deployment/repositories/manifests/svn_mirror.pp
index 570b3326..d71e896d 100644
--- a/deployment/repositories/manifests/svn_mirror.pp
+++ b/deployment/repositories/manifests/svn_mirror.pp
@@ -4,11 +4,13 @@ class repositories::svn_mirror {
}
subversion::mirror_repository {
- '/svn/adm/': source => "svn://svn.$::domain/svn/adm/";
- '/svn/soft/': source => "svn://svn.$::domain/svn/soft/";
- '/svn/web/': source => "svn://svn.$::domain/svn/web/";
- '/svn/packages/': source => "svn://svn.$::domain/svn/packages/";
- '/svn/org/': source => "svn://svn.$::domain/svn/org/";
+ '/svn/adm/': source => "svn://svn.${::domain}/svn/adm/";
+ '/svn/advisories/':source => "svn://svn.${::domain}/svn/advisories/";
+ '/svn/soft/': source => "svn://svn.${::domain}/svn/soft/";
+ '/svn/web/': source => "svn://svn.${::domain}/svn/web/";
+ '/svn/packages/': source => "svn://svn.${::domain}/svn/packages/";
+ '/svn/org/': source => "svn://svn.${::domain}/svn/org/";
+ '/svn/treasurer/': source => "svn://svn.${::domain}/svn/treasurer/";
}
# no binrepos, too big to mirror
diff --git a/deployment/repositories/templates/puppet_update.sudoers b/deployment/repositories/templates/puppet_update.sudoers
index 154bc9c1..42235771 100644
--- a/deployment/repositories/templates/puppet_update.sudoers
+++ b/deployment/repositories/templates/puppet_update.sudoers
@@ -1,2 +1 @@
%mga-sysadmin ALL= NOPASSWD: /usr/bin/svn update -q --non-interactive --accept theirs-full /etc/puppet
-
diff --git a/deployment/shadow/files/login.defs b/deployment/shadow/files/login.defs
deleted file mode 100644
index 4d966b60..00000000
--- a/deployment/shadow/files/login.defs
+++ /dev/null
@@ -1,193 +0,0 @@
-# *REQUIRED*
-# Directory where mailboxes reside, _or_ name of file, relative to the
-# home directory. If you _do_ define both, MAIL_DIR takes precedence.
-# QMAIL_DIR is for Qmail
-#
-#QMAIL_DIR Maildir
-MAIL_DIR /var/spool/mail
-#MAIL_FILE .mail
-
-# Password aging controls:
-#
-# PASS_MAX_DAYS Maximum number of days a password may be used.
-# PASS_MIN_DAYS Minimum number of days allowed between password changes.
-# PASS_MIN_LEN Minimum acceptable password length.
-# PASS_WARN_AGE Number of days warning given before a password expires.
-#
-PASS_MAX_DAYS 99999
-PASS_MIN_DAYS 0
-#PASS_MIN_LEN 5
-PASS_WARN_AGE 7
-
-#
-# Min/max values for automatic uid selection in useradd
-#
-UID_MIN 500
-UID_MAX 2000
-
-#
-# Min/max values for automatic gid selection in groupadd
-#
-GID_MIN 500
-GID_MAX 2000
-
-#
-# If defined, this command is run when removing a user.
-# It should remove any at/cron/print jobs etc. owned by
-# the user to be removed (passed as the first argument).
-#
-# USERDEL_CMD /usr/sbin/userdel_local
-
-#
-# If useradd should create home directories for users by default
-# On RH systems, we do. This option is ORed with the -m flag on
-# useradd command line.
-#
-CREATE_HOME yes
-
-#
-# The password hashing method and iteration count to use for group
-# passwords that may be set with gpasswd(1).
-#
-CRYPT_PREFIX $2a$
-CRYPT_ROUNDS 8
-
-#
-# Whether to use tcb password shadowing scheme. Use 'yes' if using
-# tcb and 'no' if using /etc/shadow
-#
-USE_TCB no
-
-#
-# Whether newly created tcb-style shadow files should be readable by
-# group "auth".
-#
-TCB_AUTH_GROUP yes
-
-#
-# Whether useradd should create symlinks rather than directories under
-# /etc/tcb for newly created accounts with UIDs over 1000. See tcb(5)
-# for information on why this may be needed.
-#
-TCB_SYMLINKS no
-
-#
-# Delay in seconds before being allowed another attempt after a login failure
-#
-FAIL_DELAY 3
-
-#
-# Enable display of unknown usernames when login failures are recorded.
-#
-LOG_UNKFAIL_ENAB no
-
-#
-# Enable logging of successful logins
-#
-LOG_OK_LOGINS no
-
-#
-# Enable "syslog" logging of su activity - in addition to sulog file logging.
-# SYSLOG_SG_ENAB does the same for newgrp and sg.
-#
-SYSLOG_SU_ENAB yes
-SYSLOG_SG_ENAB yes
-
-#
-# If defined, either full pathname of a file containing device names or
-# a ":" delimited list of device names. Root logins will be allowed only
-# upon these devices.
-#
-CONSOLE /etc/securetty
-#CONSOLE console:tty01:tty02:tty03:tty04
-
-#
-# If defined, the command name to display when running "su -". For
-# example, if this is defined as "su" then a "ps" will display the
-# command is "-su". If not defined, then "ps" would display the
-# name of the shell actually being run, e.g. something like "-sh".
-#
-SU_NAME su
-
-#
-# If defined, file which inhibits all the usual chatter during the login
-# sequence. If a full pathname, then hushed mode will be enabled if the
-# user's name or shell are found in the file. If not a full pathname, then
-# hushed mode will be enabled if the file exists in the user's home directory.
-#
-HUSHLOGIN_FILE .hushlogin
-#HUSHLOGIN_FILE /etc/hushlogins
-
-#
-# *REQUIRED* The default PATH settings, for superuser and normal users.
-#
-# (they are minimal, add the rest in the shell startup files)
-ENV_SUPATH PATH=/sbin:/bin:/usr/sbin:/usr/bin
-ENV_PATH PATH=/bin:/usr/bin
-
-#
-# Terminal permissions
-#
-# TTYGROUP Login tty will be assigned this group ownership.
-# TTYPERM Login tty will be set to this permission.
-#
-# If you have a "write" program which is "setgid" to a special group
-# which owns the terminals, define TTYGROUP to the group number and
-# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign
-# TTYPERM to either 622 or 600.
-#
-TTYGROUP tty
-TTYPERM 0600
-
-#
-# Login configuration initializations:
-#
-# ERASECHAR Terminal ERASE character ('\010' = backspace).
-# KILLCHAR Terminal KILL character ('\025' = CTRL/U).
-# UMASK Default "umask" value.
-# ULIMIT Default "ulimit" value.
-#
-# The ERASECHAR and KILLCHAR are used only on System V machines.
-# The ULIMIT is used only if the system supports it.
-# (now it works with setrlimit too; ulimit is in 512-byte units)
-#
-# Prefix these values with "0" to get octal, "0x" to get hexadecimal.
-#
-ERASECHAR 0177
-KILLCHAR 025
-UMASK 022
-#ULIMIT 2097152
-
-#
-# Max number of login retries if password is bad
-#
-LOGIN_RETRIES 5
-
-#
-# Max time in seconds for login
-#
-LOGIN_TIMEOUT 60
-
-#
-# Which fields may be changed by regular users using chfn - use
-# any combination of letters "frwh" (full name, room number, work
-# phone, home phone). If not defined, no changes are allowed.
-# For backward compatibility, "yes" = "rwh" and "no" = "frwh".
-#
-CHFN_RESTRICT rwh
-
-#
-# Should login be allowed if we can't cd to the home directory?
-# Default in no.
-#
-DEFAULT_HOME yes
-
-#
-# Enable setting of the umask group bits to be the same as owner bits
-# (examples: 022 -> 002, 077 -> 007) for non-root users, if the uid is
-# the same as gid, and username is the same as the primary group name.
-#
-# This also enables userdel to remove user groups if no members exist.
-#
-USERGROUPS_ENAB yes
-
diff --git a/deployment/shadow/manifests/init.pp b/deployment/shadow/manifests/init.pp
index 083f86ba..c24c36bf 100644
--- a/deployment/shadow/manifests/init.pp
+++ b/deployment/shadow/manifests/init.pp
@@ -1,8 +1,23 @@
class shadow {
- file { '/etc/login.defs':
+ include stdlib
+
+ $login_defs = '/etc/login.defs'
+
+ file { $login_defs:
owner => 'root',
group => 'shadow',
mode => '0640',
- source => 'puppet:///modules/shadow/login.defs',
+ }
+
+ file_line { 'uid_max':
+ path => $login_defs,
+ line => 'UID_MAX 2000',
+ match => '^UID_MAX\s+',
+ }
+
+ file_line { 'gid_max':
+ path => $login_defs,
+ line => 'GID_MAX 2000',
+ match => '^GID_MAX\s+',
}
}
diff --git a/deployment/softwarekey/manifests/init.pp b/deployment/softwarekey/manifests/init.pp
index 41102b4e..b2c4bcb2 100644
--- a/deployment/softwarekey/manifests/init.pp
+++ b/deployment/softwarekey/manifests/init.pp
@@ -1,24 +1,23 @@
class softwarekey {
$sign_login = 'softwarekey'
- $sign_home_dir = "/var/lib/$sign_login"
- $sign_keydir = "$sign_home_dir/keys"
+ $sign_home_dir = "/var/lib/${sign_login}"
+ $sign_keydir = "${sign_home_dir}/keys"
group { $sign_login: }
user { $sign_login:
- comment => 'System user to sign Mageia Software',
home => $sign_home_dir,
gid => $sign_login,
require => Group[$sign_login],
}
gnupg::keys{ 'software':
- email => "software@$::domain",
+ email => "software@${::domain}",
#FIXME there should be a variable somewhere to change the
# name of the distribution
key_name => 'Mageia Software',
login => $sign_login,
- batchdir => "$sign_home_dir/batches",
+ batchdir => "${sign_home_dir}/batches",
keydir => $sign_keydir,
require => User[$sign_login],
}
diff --git a/deployment/tld_redirections/manifests/init.pp b/deployment/tld_redirections/manifests/init.pp
index 92215e86..18db541c 100644
--- a/deployment/tld_redirections/manifests/init.pp
+++ b/deployment/tld_redirections/manifests/init.pp
@@ -1,24 +1,24 @@
class tld_redirections {
define domain {
- dns::zone { "mageia.$name": }
+ dns::zone { "mageia.${name}": }
}
define redirection($managed_dns = false) {
if ($managed_dns) {
- @@tld_redirections::domain { $name: }
+ @@tld_redirections::domain { $name: }
}
- apache::vhost_redirect { "mageia.$name":
- url => "http://www.mageia.org/?fromtld=$name"
+ apache::vhost_redirect { "mageia.${name}":
+ url => "https://www.${::domain}/?fromtld=${name}"
}
- apache::vhost_redirect { "www.mageia.$name":
- url => "http://www.mageia.org/?fromtld=$name"
+ apache::vhost_redirect { "www.mageia.${name}":
+ url => "https://www.${::domain}/?fromtld=${name}"
}
}
- # domaine owned by Florin Catalin Russen
+ # domain owned by Florin Catalin Russen
redirection { "ro": }
# domain owned by the association
diff --git a/deployment/websites/manifests/archives.pp b/deployment/websites/manifests/archives.pp
new file mode 100644
index 00000000..825e082b
--- /dev/null
+++ b/deployment/websites/manifests/archives.pp
@@ -0,0 +1,20 @@
+class websites::archives {
+ include websites::base
+ $vhost = "archives.${::domain}"
+ $vhostdir = "${websites::base::webdatadir}/${vhost}"
+ $git_location = "git://git.${::domain}/web/archives"
+
+ apache::vhost::base { $vhost:
+ location => $vhostdir,
+ }
+
+ apache::vhost::base { "ssl_${vhost}":
+ vhost => $vhost,
+ use_ssl => true,
+ location => $vhostdir,
+ }
+
+ git::snapshot { $vhostdir:
+ source => $git_location,
+ }
+}
diff --git a/deployment/websites/manifests/base.pp b/deployment/websites/manifests/base.pp
index c45ca431..1c2dbc64 100644
--- a/deployment/websites/manifests/base.pp
+++ b/deployment/websites/manifests/base.pp
@@ -1,3 +1,9 @@
class websites::base {
$webdatadir = '/var/www/vhosts'
+ file { $webdatadir:
+ ensure => directory,
+ mode => '0755',
+ owner => root,
+ group => root
+ }
}
diff --git a/deployment/websites/manifests/doc.pp b/deployment/websites/manifests/doc.pp
index 63ba9d45..01474af2 100644
--- a/deployment/websites/manifests/doc.pp
+++ b/deployment/websites/manifests/doc.pp
@@ -1,20 +1,20 @@
class websites::doc {
include websites::base
- $vhost = "doc.$::domain"
- $vhostdir = "$websites::base::webdatadir/$vhost"
- $svn_location = "svn://svn.$::domain/svn/web/doc/"
+ $vhost = "doc.${::domain}"
+ $vhostdir = "${websites::base::webdatadir}/${vhost}"
+ $git_location = "git://git.${::domain}/web/doc"
apache::vhost::base { $vhost:
location => $vhostdir,
}
- apache::vhost::base { "ssl_$vhost":
+ apache::vhost::base { "ssl_${vhost}":
vhost => $vhost,
use_ssl => true,
location => $vhostdir,
}
- subversion::snapshot { $vhostdir:
- source => $svn_location,
+ git::snapshot { $vhostdir:
+ source => $git_location,
}
}
diff --git a/deployment/websites/manifests/forum_proxy.pp b/deployment/websites/manifests/forum_proxy.pp
index 06e9f433..bd8f1fc1 100644
--- a/deployment/websites/manifests/forum_proxy.pp
+++ b/deployment/websites/manifests/forum_proxy.pp
@@ -1,13 +1,13 @@
class websites::forum_proxy {
- $web_domain = "forums.$::domain"
+ $web_domain = "forums.${::domain}"
apache::vhost::reverse_proxy { $web_domain:
- url => "http://$web_domain/",
+ url => "http://${web_domain}/",
}
- apache::vhost::reverse_proxy { "ssl_$web_domain":
+ apache::vhost::reverse_proxy { "ssl_${web_domain}":
vhost => $web_domain,
use_ssl => true,
- url => "http://$web_domain/",
+ url => "http://${web_domain}/",
}
}
diff --git a/deployment/websites/manifests/git.pp b/deployment/websites/manifests/git.pp
new file mode 100644
index 00000000..e357dfb2
--- /dev/null
+++ b/deployment/websites/manifests/git.pp
@@ -0,0 +1,10 @@
+class websites::git {
+ apache::vhost_redirect { "git.${::domain}":
+ url => "https://gitweb.${::domain}/",
+ }
+ apache::vhost_redirect { "ssl_git.${::domain}":
+ use_ssl => true,
+ vhost => "git.${::domain}",
+ url => "https://gitweb.${::domain}/",
+ }
+}
diff --git a/deployment/websites/manifests/hugs.pp b/deployment/websites/manifests/hugs.pp
index 49fe6ac7..95246464 100644
--- a/deployment/websites/manifests/hugs.pp
+++ b/deployment/websites/manifests/hugs.pp
@@ -1,15 +1,15 @@
class websites::hugs {
include websites::base
- $vhostdir = "$websites::base::webdatadir/hugs.$::domain"
- $svn_location = "svn://svn.$::domain/svn/web/hugs/public/"
+ $vhostdir = "${websites::base::webdatadir}/hugs.${::domain}"
+ $git_location = "git://git.${::domain}/web/hugs"
- apache::vhost::base { "hugs.$::domain":
+ apache::vhost::base { "hugs.${::domain}":
location => $vhostdir,
}
- subversion::snapshot { $vhostdir:
- source => $svn_location
+ git::snapshot { $vhostdir:
+ source => $git_location
}
package { 'php-exif': }
diff --git a/deployment/websites/manifests/meetbot.pp b/deployment/websites/manifests/meetbot.pp
new file mode 100644
index 00000000..04bbcf70
--- /dev/null
+++ b/deployment/websites/manifests/meetbot.pp
@@ -0,0 +1,14 @@
+# We should rather have a meetbot module used to deploy
+# it, setup backups and this website
+class websites::meetbot {
+ $vhost = "meetbot.${::domain}"
+ $vhostdir = "/home/irc_bots/meetings/"
+
+ apache::vhost::other_app { "meetbot.${::domain}":
+ vhost_file => 'websites/vhost_meetbot.conf',
+ }
+
+ file { $vhostdir:
+ ensure => directory,
+ }
+}
diff --git a/deployment/websites/manifests/nav.pp b/deployment/websites/manifests/nav.pp
index 587abe0d..84323c26 100644
--- a/deployment/websites/manifests/nav.pp
+++ b/deployment/websites/manifests/nav.pp
@@ -1,20 +1,27 @@
class websites::nav {
include websites::base
- $vhost = "nav.$::domain"
- $vhostdir = "$websites::base::webdatadir/$vhost"
- $svn_location = "svn://svn.$::domain/svn/web/nav/"
+ $vhost = "nav.${::domain}"
+ $vhostdir = "${websites::base::webdatadir}/${vhost}"
+ $git_location = "git://git.${::domain}/web/nav"
apache::vhost::base { $vhost:
location => $vhostdir,
}
- apache::vhost::base { "ssl_$vhost":
+ apache::vhost::base { "ssl_${vhost}":
vhost => $vhost,
use_ssl => true,
location => $vhostdir,
}
- subversion::snapshot { $vhostdir:
- source => $svn_location,
+ git::snapshot { $vhostdir:
+ source => $git_location,
+ }
+
+ file { "${vhostdir}/var/tmp/cache":
+ ensure => directory,
+ mode => '0660',
+ group => $apache::var::apache_group,
+ require => Git::Snapshot[$vhostdir],
}
}
diff --git a/deployment/websites/manifests/perl.pp b/deployment/websites/manifests/perl.pp
index 041bde63..2b4849fb 100644
--- a/deployment/websites/manifests/perl.pp
+++ b/deployment/websites/manifests/perl.pp
@@ -1,10 +1,10 @@
class websites::perl {
include websites::base
- $vhost = "perl.$::domain"
- $vhostdir = "$websites::base::webdatadir/$vhost"
+ $vhost = "perl.${::domain}"
+ $vhostdir = "${websites::base::webdatadir}/${vhost}"
$statsdir = "${vhostdir}/stats"
- $login = 'pkgcpan'
- $homedir = "/var/lib/$login"
+ $login = 'pkgcpan'
+ $homedir = "/var/lib/${login}"
user { $login:
managehome => true,
@@ -26,29 +26,29 @@ class websites::perl {
cron { 'update cpanpkg':
hour => 23,
- minute => 0,
+ minute => 0,
require => Package['perl-Module-Packaged-Generator'],
- command => "pkgcpan -q -f $vhostdir/cpan_Mageia.db -d Mageia && chmod 644 $vhostdir/cpan_Mageia.db",
+ command => "pkgcpan -q -f ${vhostdir}/cpan_Mageia.db -d Mageia && chmod 644 ${vhostdir}/cpan_Mageia.db",
user => $login,
}
- file { "$vhostdir/cpan_Mageia.db":
+ file { "${vhostdir}/cpan_Mageia.db":
owner => $login,
group => $login,
}
file { $statsdir:
- ensure => directory,
- owner => $login,
- group => $login,
+ ensure => directory,
+ owner => $login,
+ group => $login,
}
- # http://www.mageia.org/pipermail/mageia-sysadm/2012-March/004337.html
+ # https://www.mageia.org/pipermail/mageia-sysadm/2012-March/004337.html
cron { 'update pkgcpan stats':
- hour => 23,
- minute => 30,
- require => [ Package['magpie'], File[$statsdir] ],
- command => "magpie webstatic -qq -d $statsdir",
- user => $login,
+ hour => 23,
+ minute => 30,
+ require => [ Package['magpie'], File[$statsdir] ],
+ command => "magpie webstatic -qq -d ${statsdir}",
+ user => $login,
}
}
diff --git a/deployment/websites/manifests/releases.pp b/deployment/websites/manifests/releases.pp
index 1d52201c..2b25c8ec 100644
--- a/deployment/websites/manifests/releases.pp
+++ b/deployment/websites/manifests/releases.pp
@@ -1,22 +1,22 @@
class websites::releases {
include websites::base
- $vhost = "releases.$::domain"
- $vhostdir = "$websites::base::webdatadir/$vhost"
- $svn_location = "svn://svn.$::domain/svn/web/releases/"
+ $vhost = "releases.${::domain}"
+ $vhostdir = "${websites::base::webdatadir}/${vhost}"
+ $git_location = "git://git.${::domain}/web/releases"
apache::vhost::base { $vhost:
location => $vhostdir,
options => [ 'FollowSymLinks' ],
}
- apache::vhost::base { "ssl_$vhost":
+ apache::vhost::base { "ssl_${vhost}":
vhost => $vhost,
use_ssl => true,
location => $vhostdir,
options => [ 'FollowSymLinks' ],
}
- subversion::snapshot { $vhostdir:
- source => $svn_location,
+ git::snapshot { $vhostdir:
+ source => $git_location,
}
}
diff --git a/deployment/websites/manifests/start.pp b/deployment/websites/manifests/start.pp
index e4c383ce..9d5b77e5 100644
--- a/deployment/websites/manifests/start.pp
+++ b/deployment/websites/manifests/start.pp
@@ -1,6 +1,11 @@
class websites::start {
include websites::base
- apache::vhost_redirect { "start.$::domain":
- url => "http://www.mageia.org/community/",
+ apache::vhost_redirect { "start.${::domain}":
+ url => "https://www.${::domain}/community/",
+ }
+ apache::vhost_redirect { "ssl_start.${::domain}":
+ use_ssl => true,
+ vhost => "start.${::domain}",
+ url => "https://www.${::domain}/community/",
}
}
diff --git a/deployment/websites/manifests/static.pp b/deployment/websites/manifests/static.pp
index 749f72b0..66711329 100644
--- a/deployment/websites/manifests/static.pp
+++ b/deployment/websites/manifests/static.pp
@@ -1,8 +1,8 @@
class websites::static {
include websites::base
- $vhostdir = "$websites::base::webdatadir/static.$::domain"
+ $vhostdir = "${websites::base::webdatadir}/static.${::domain}"
- apache::vhost::other_app { "static.$::domain":
+ apache::vhost::other_app { "static.${::domain}":
vhost_file => 'websites/vhost_static.conf',
}
@@ -10,7 +10,7 @@ class websites::static {
ensure => directory,
}
- subversion::snapshot { "$vhostdir/g":
- source => "svn://svn.$::domain/svn/web/www/trunk/g/",
+ git::snapshot { "${vhostdir}":
+ source => "git://git.${::domain}/web/www",
}
}
diff --git a/deployment/websites/manifests/svn.pp b/deployment/websites/manifests/svn.pp
index 650442dc..973c012d 100644
--- a/deployment/websites/manifests/svn.pp
+++ b/deployment/websites/manifests/svn.pp
@@ -1,5 +1,10 @@
class websites::svn {
- apache::vhost_redirect { "svn.$::domain":
- url => "http://svnweb.$::domain/",
+ apache::vhost_redirect { "svn.${::domain}":
+ url => "https://svnweb.${::domain}/",
+ }
+ apache::vhost_redirect { "ssl_svn.${::domain}":
+ use_ssl => true,
+ vhost => "svn.${::domain}",
+ url => "https://svnweb.${::domain}/",
}
}
diff --git a/deployment/websites/manifests/www.pp b/deployment/websites/manifests/www.pp
index 03498084..08c232f2 100644
--- a/deployment/websites/manifests/www.pp
+++ b/deployment/websites/manifests/www.pp
@@ -1,51 +1,64 @@
class websites::www {
include websites::base
- $vhost = "www.$::domain"
- $vhostdir = "$websites::base::webdatadir/$vhost"
- $svn_location = "svn://svn.$::domain/svn/web/www/trunk"
+ $vhost = "www.${::domain}"
+ $vhostdir = "${websites::base::webdatadir}/${vhost}"
+ $git_location = "git://git.${::domain}/web/www"
+ include apache::var
include apache::mod::php
- include apache::mod::geoip
# for mailman reverse proxy, on ssl
include apache::mod::proxy
include apache::mod::ssl
- subversion::snapshot { $vhostdir:
- source => $svn_location,
+ git::snapshot { $vhostdir:
+ source => $git_location,
}
- file { "$vhostdir/var/tmp/cache":
- ensure => directory,
- group => $apache::base::apache_group,
- mode => '0660',
+ file { [ "${vhostdir}/var",
+ "${vhostdir}/var/tmp",
+ "${vhostdir}/var/tmp/cache" ] :
+ ensure => directory,
+ group => $apache::var::apache_group,
+ mode => '0660',
+ require => Git::Snapshot[$vhostdir],
+ }
+
+ file { [ "${vhostdir}/_nav",
+ "${vhostdir}/_nav/var",
+ "${vhostdir}/_nav/var/tmp",
+ "${vhostdir}/_nav/var/tmp/cache" ] :
+ ensure => directory,
+ group => $apache::var::apache_group,
+ mode => '0660',
+ require => Git::Snapshot[$vhostdir],
}
apache::vhost::base { $vhost:
content => template('websites/vhost_www.conf',
- 'websites/vhost_proxy_mailman.conf'),
+ 'websites/vhost_www_rewrite.conf'),
location => $vhostdir,
options => ['FollowSymLinks'],
}
- apache::vhost::base { "ssl_$vhost":
+ apache::vhost::base { "ssl_${vhost}":
use_ssl => true,
vhost => $vhost,
content => template('websites/vhost_www.conf',
- 'websites/vhost_proxy_mailman_ssl.conf'),
+ 'websites/vhost_www_rewrite.conf'),
location => $vhostdir,
options => ['FollowSymLinks'],
}
- apache::vhost_redirect { $::domain:
- url => "http://www.$::domain/",
+ apache::vhost_redirect { "${::domain}":
+ url => "https://www.${::domain}/",
}
- apache::vhost_redirect { "ssl_$::domain":
+ apache::vhost_redirect { "ssl_${::domain}":
use_ssl => true,
- vhost => $::domain,
- url => "https://www.$::domain/",
+ vhost => "${::domain}",
+ url => "https://www.${::domain}/",
}
- package { ['php-mbstring', 'php-mcrypt', 'php-gettext', 'php-geoip']: }
+ package { ['php-mbstring', 'php-mcrypt', 'php-gettext']: }
}
diff --git a/deployment/websites/templates/vhost_meetbot.conf b/deployment/websites/templates/vhost_meetbot.conf
new file mode 100644
index 00000000..40a0f92a
--- /dev/null
+++ b/deployment/websites/templates/vhost_meetbot.conf
@@ -0,0 +1,36 @@
+<VirtualHost *:80>
+ ServerAdmin sysadm@mageia.org
+ ServerName meetbot.<%= @domain %>
+ DocumentRoot <%= scope.lookupvar("websites::meetbot::vhostdir") %>
+
+ CustomLog /var/log/httpd/access_meetbot_log combined
+ ErrorLog /var/log/httpd/error_meetbot_log
+ <Directory <%= scope.lookupvar("websites::meetbot::vhostdir") %>>
+ Allow from all
+ <IfModule mod_authz_core.c>
+ Require all granted
+ </IfModule>
+ Options +Indexes
+ IndexIgnore .htaccess *.bak *~ *.txt *.log.html
+ </Directory>
+</VirtualHost>
+
+<VirtualHost *:443>
+ ServerAdmin sysadm@mageia.org
+ ServerName meetbot.<%= @domain %>
+ DocumentRoot <%= scope.lookupvar("websites::meetbot::vhostdir") %>
+
+ CustomLog /var/log/httpd/access_meetbot_log combined
+ ErrorLog /var/log/httpd/error_meetbot_log
+
+<%= scope.function_template(["apache/vhost_ssl.conf"]) %>
+
+ <Directory <%= scope.lookupvar("websites::meetbot::vhostdir") %>>
+ Allow from all
+ <IfModule mod_authz_core.c>
+ Require all granted
+ </IfModule>
+ Options +Indexes
+ IndexIgnore .htaccess *.bak *~ *.txt *.log.html
+ </Directory>
+</VirtualHost>
diff --git a/deployment/websites/templates/vhost_proxy_mailman.conf b/deployment/websites/templates/vhost_proxy_mailman.conf
deleted file mode 100644
index ef447f9c..00000000
--- a/deployment/websites/templates/vhost_proxy_mailman.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-
-ProxyRequests Off
-ProxyPreserveHost On
-
-<Proxy *>
- Order deny,allow
- Allow from all
-</Proxy>
-
-<% for u in ['/mailman/','/pipermail/'] %>
-ProxyPass <%= u %> http://ryu.zarb.org<%= u %>
-ProxyPassReverse <%= u %> http://ryu.zarb.org<%= u %>
-
-<% end %>
diff --git a/deployment/websites/templates/vhost_proxy_mailman_ssl.conf b/deployment/websites/templates/vhost_proxy_mailman_ssl.conf
deleted file mode 100644
index e5fcfbe1..00000000
--- a/deployment/websites/templates/vhost_proxy_mailman_ssl.conf
+++ /dev/null
@@ -1,16 +0,0 @@
-
-ProxyRequests Off
-ProxyPreserveHost On
-
-<Proxy *>
- Order deny,allow
- Allow from all
-</Proxy>
-
-SSLProxyEngine On
-
-<% for u in ['/mailman/','/pipermail/'] %>
-ProxyPass <%= u %> https://ryu.zarb.org<%= u %>
-ProxyPassReverse <%= u %> https://ryu.zarb.org<%= u %>
-
-<% end %>
diff --git a/deployment/websites/templates/vhost_static.conf b/deployment/websites/templates/vhost_static.conf
index 6521b469..fcadc425 100644
--- a/deployment/websites/templates/vhost_static.conf
+++ b/deployment/websites/templates/vhost_static.conf
@@ -1,7 +1,7 @@
<VirtualHost *:80>
- ServerName static.<%= domain %>
+ ServerName static.<%= @domain %>
- DocumentRoot <%= vhostdir %>
+ DocumentRoot <%= scope.lookupvar("websites::static::vhostdir") %>
CustomLog /var/log/httpd/static_log combined
ErrorLog /var/log/httpd/error_static_log
@@ -19,10 +19,63 @@
AddOutputFilterByType DEFLATE application/json text/javascript application/javascript application/x-javascript
<Location />
+ Deny from all
+ </Location>
+
+ <Location /g/>
+ Allow from all
+ </Location>
+
+ <Directory <%= scope.lookupvar("websites::static::vhostdir") %>>
+ Order deny,allow
+ Deny from All
+ AllowOverride None
+ </Directory>
+
+ <Directory <%= scope.lookupvar("websites::static::vhostdir") %>/g>
+ Order deny,allow
+ Allow from All
+ AllowOverride None
+ </Directory>
+</VirtualHost>
+
+<VirtualHost *:443>
+ ServerName static.<%= @domain %>
+
+ DocumentRoot <%= scope.lookupvar("websites::static::vhostdir") %>
+ CustomLog /var/log/httpd/static_log combined
+ ErrorLog /var/log/httpd/error_static_log
+
+<%= scope.function_template(["apache/vhost_ssl.conf"]) %>
+
+ FileETag none
+ Header unset ETag
+ ExpiresActive On
+ ExpiresByType text/css "access plus 1 month"
+ ExpiresByType image/gif "access plus 2 months"
+ ExpiresByType image/png "access plus 2 months"
+ ExpiresByType image/jpeg "access plus 2 months"
+ ExpiresByType image/x-icon "access plus 2 months"
+ ExpiresByType application/x-javascript "access plus 1 month"
+ ExpiresByType text/javascript "access plus 1 month"
+ AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
+ AddOutputFilterByType DEFLATE application/json text/javascript application/javascript application/x-javascript
+
+ <Location />
+ Deny from all
+ </Location>
+
+ <Location /g/>
Allow from all
</Location>
- <Directory <%= vhostdir %>>
+ <Directory <%= scope.lookupvar("websites::static::vhostdir") %>>
+ Order deny,allow
+ Deny from All
+ AllowOverride None
+ </Directory>
+
+ <Directory <%= scope.lookupvar("websites::static::vhostdir") %>/g>
Order deny,allow
Allow from All
AllowOverride None
diff --git a/deployment/websites/templates/vhost_www.conf b/deployment/websites/templates/vhost_www.conf
index 9d00828d..399681be 100644
--- a/deployment/websites/templates/vhost_www.conf
+++ b/deployment/websites/templates/vhost_www.conf
@@ -1,13 +1,13 @@
Redirect /wiki https://wiki.mageia.org/#
-# Everything under /g/ is static content to be served by a seconday host
+# Everything under /g/ is static content to be served by a secondary host
RewriteEngine On
-RewriteRule ^g/(.+)$ http://static.mageia.org/g/$1 [R,L,QSA]
-
-# ProxyPreserveHost On is in another file
-ProxyPass /old-wiki/ http://ryu.zarb.org/wiki
-ProxyPassReverse /old-wiki/ http://ryu.zarb.org/wiki
+RewriteRule ^g/(.+)$ https://static.mageia.org/g/$1 [R,L,QSA]
ErrorDocument 404 /404.php
php_value short_open_tag false
+
+# switch all to https
+RewriteCond %{HTTPS} !=on
+RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
diff --git a/deployment/websites/templates/vhost_www_rewrite.conf b/deployment/websites/templates/vhost_www_rewrite.conf
new file mode 100644
index 00000000..c7bb2fd5
--- /dev/null
+++ b/deployment/websites/templates/vhost_www_rewrite.conf
@@ -0,0 +1,22 @@
+RewriteEngine On
+RewriteRule ^/mailman/listinfo/mageia-annnounce https://ml.mageia.org/l/info/announce [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-artwork https://ml.mageia.org/l/info/atelier-discuss [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-bugsquad https://ml.mageia.org/l/info/bugsquad-discuss [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-dev https://ml.mageia.org/l/info/dev [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-discuss https://ml.mageia.org/l/info/discuss [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-i18n https://ml.mageia.org/l/info/i18n-discuss [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-marketing https://ml.mageia.org/l/info/atelier-discuss [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-sysadm https://ml.mageia.org/l/info/sysadmin-discuss [R=301,L]
+RewriteRule ^/mailman/listinfo/mageia-webteam https://ml.mageia.org/l/info/atelier-discuss [R=301,L]
+RewriteRule ^/mailman https://ml.mageia.org/ [R=301,L]
+
+RewriteRule ^/pipermail/mageia-announce/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-announce/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-artwork/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-artwork/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-bugsquad/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-bugsquad/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-dev/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-dev/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-discuss/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-discuss/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-i18n/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-i18n/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-marketing/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-marketing/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-sysadm/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-sysadm/$1 [R=301,L]
+RewriteRule ^/pipermail/mageia-webteam/?(.*)$ https://archives.mageia.org/zarb-ml/mageia-webteam/$1 [R=301,L]
+RewriteRule ^/pipermail https://archives.mageia.org/zarb-ml/ [R=301,L]
diff --git a/deployment/wikis/manifests/init.pp b/deployment/wikis/manifests/init.pp
index 901c5953..c34b06d5 100644
--- a/deployment/wikis/manifests/init.pp
+++ b/deployment/wikis/manifests/init.pp
@@ -9,22 +9,22 @@ class wikis {
vhost => false,
}
- subversion::snapshot { $wikis_templates:
- source => "svn://svn.$::domain/svn/web/templates/mediawiki"
+ git::snapshot { $wikis_templates:
+ source => "git://git.${::domain}/web/templates/mediawiki"
}
- $wiki_languages = [ 'en','de' ]
+ $wiki_languages = [ 'en','de', 'fr' ]
mediawiki::instance { $wiki_languages:
title => 'Mageia wiki',
wiki_settings => template('wikis/wiki_settings'),
- skinsdir => "$wikis_templates/skins",
+ skinsdir => "${wikis_templates}/skins",
}
- apache::vhost::redirect_ssl { "wiki.$::domain": }
+ apache::vhost::redirect_ssl { "wiki.${::domain}": }
- apache::vhost::base { "ssl_wiki.$::domain":
+ apache::vhost::base { "ssl_wiki.${::domain}":
use_ssl => true,
- vhost => "wiki.$::domain",
+ vhost => "wiki.${::domain}",
content => template('wikis/wiki_vhost.conf'),
}
}
diff --git a/deployment/wikis/templates/wiki_settings b/deployment/wikis/templates/wiki_settings
index d2c1b199..ec6e647d 100644
--- a/deployment/wikis/templates/wiki_settings
+++ b/deployment/wikis/templates/wiki_settings
@@ -7,12 +7,13 @@ $wgGroupPermissions['*']['createpage'] = false;
$wgGroupPermissions['*']['writeapi'] = false;
$wgGroupPermissions['*']['createaccount'] = false;
$wgGroupPermissions['user']['edit'] = true;
+$wgGroupPermissions['*']['autocreateaccount'] = true;
$wgScriptPath = "/mw-$wgLanguageCode";
$wgArticlePath = "/$wgLanguageCode/$1";
$wgUsePathInfo = true;
$wgStylePath = "$wgScriptPath/skins";
-$wgStyleDirectory = '<%= wikis_templates %>/skins';
+$wgStyleDirectory = '<%= @wikis_templates %>/skins';
$wgLogo = "";
$wgDefaultSkin = 'vector';
$wgFavicon = '/mw-en/skins/cavendish/favicon.png';
@@ -31,3 +32,15 @@ $wgExtraNamespaces[NS_QA_PROCEDURE] = 'QA_procedure';
$wgExtraNamespaces[NS_QA_PROCEDURE_TALK] = 'QA_procedure_Talk';
$wgContentNamespaces[] = NS_QA_PROCEDURE;
$wgNamespacesToBeSearchedDefault[NS_QA_PROCEDURE] = true;
+
+wfLoadExtension('Nuke');
+wfLoadExtension('SpamBlacklist');
+wfLoadExtension('TitleBlacklist');
+$wgTitleBlacklistSources = array(
+ array(
+ 'type' => 'localpage',
+ 'src' => 'MediaWiki:Titleblacklist'
+ )
+);
+
+# $wgReadOnly = 'This wiki is currently read-only';
diff --git a/deployment/wikis/templates/wiki_vhost.conf b/deployment/wikis/templates/wiki_vhost.conf
index e8e4fd6d..4e1355bc 100644
--- a/deployment/wikis/templates/wiki_vhost.conf
+++ b/deployment/wikis/templates/wiki_vhost.conf
@@ -1,18 +1,19 @@
-<Directory <%= wikis_root %>>
+<Directory <%= @wikis_root %>>
Options +FollowSymLinks
</Directory>
RewriteEngine On
RewriteRule ^/?$ /en/ [R]
+Alias /robots.txt <%= @wikis_root %>/robots.txt
+
<%- for lang in wiki_languages -%>
-<Directory <%= wikis_root %>/<%= lang %>/images>
+<Directory <%= @wikis_root %>/<%= lang %>/images>
SetHandler default-handler
</Directory>
-Alias /<%= lang %> <%= wikis_root %>/<%= lang %>/index.php
-Alias /mw-<%= lang %> <%= wikis_root %>/<%= lang %>
+Alias /<%= lang %> <%= @wikis_root %>/<%= lang %>/index.php
+Alias /mw-<%= lang %> <%= @wikis_root %>/<%= lang %>
<%- end -%>
-