diff options
Diffstat (limited to 'deployment')
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 -%> - |
