Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate mirror submission #448

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local_settings.py
archweb.db
archweb.db-*
database.db
/*.tar.gz
Torxed marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop this, this shouldn't be required anymore. It's also unrelated to the other changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure it's "unrelated", but the fact remains that whenever you work from the project folder and follow some instructions about downloading a database or something similar you run the risk of pushing it globally.

If the project does not need .tar.gz files to be built or executed, there's really no risk of adding it to the ignore list. And I thought while I'm at it, I'll add it.

tags
collected_static/
testing/
Expand Down
1 change: 1 addition & 0 deletions mirrors/urls_mirrorlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

urlpatterns = [
path('', views.generate_mirrorlist, name='mirrorlist'),
path('submit/', views.submit_mirror, name='mirrorsubmit'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not linked anywhere on archlinux.org, maybe we should add a link with some text on https://archlinux.org/mirrors.

re_path(r'^all/$', views.find_mirrors, {'countries': ['all']}),
re_path(r'^all/(?P<protocol>[A-z]+)/$', views.find_mirrors_simple, name='mirrorlist_simple')
]
Expand Down
207 changes: 205 additions & 2 deletions mirrors/views/mirrorlist.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,132 @@
from operator import attrgetter, itemgetter
from urllib.parse import urlparse, urlunsplit
from functools import partial

from django import forms
from django.db import DatabaseError, transaction
from django.db.models import Q
from django.forms.widgets import SelectMultiple, CheckboxSelectMultiple
from django.core.mail import send_mail
from django.template import loader
from django.forms.widgets import (
Select,
SelectMultiple,
CheckboxSelectMultiple,
TextInput,
EmailInput
)
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.csrf import csrf_exempt
from django_countries import countries

from ..models import MirrorUrl, MirrorProtocol
from ..models import Mirror, MirrorUrl, MirrorProtocol, MirrorRsync
from ..utils import get_mirror_statuses

import random

# This is populated later, and re-populated every refresh
# This was the only way to get 3 different examples without
# changing the models.py
url_examples = []


class MirrorRequestForm(forms.ModelForm):
upstream = forms.ModelChoiceField(
queryset=Mirror.objects.filter(
tier__gte=0,
tier__lte=1
),
required=False
)

class Meta:
model = Mirror
fields = ('name', 'tier', 'upstream', 'admin_email', 'alternate_email',
'isos', 'active', 'public', 'rsync_user', 'rsync_password', 'notes')

def __init__(self, *args, **kwargs):
super(MirrorRequestForm, self).__init__(*args, **kwargs)
fields = self.fields
fields['name'].widget.attrs.update({'placeholder': 'Ex: mirror.argentina.co'})
fields['rsync_user'].widget.attrs.update({'placeholder': 'Optional'})
fields['rsync_password'].widget.attrs.update({'placeholder': 'Optional'})
fields['notes'].widget.attrs.update({'placeholder': 'Ex: Hosted by ISP GreatISO.bg'})

def as_div(self):
"Returns this form rendered as HTML <divs>s."
return self._html_output(
normal_row=u'<div%(html_class_attr)s>%(label)s %(field)s%(help_text)s</div>',
error_row=u'%s',
row_ender='</div>',
help_text_html=u' <span class="helptext">%s</span>',
errors_on_separate_row=True)


class MirrorUrlForm(forms.ModelForm):
class Meta:
model = MirrorUrl
fields = ('url', 'country', 'bandwidth', 'active')

def __init__(self, *args, **kwargs):
global url_examples

super(MirrorUrlForm, self).__init__(*args, **kwargs)
fields = self.fields

if len(url_examples) == 0:
url_examples = [
'Ex: http://mirror.argentina.co/archlinux',
'Ex: https://mirror.argentina.co/archlinux',
'Ex: rsync://mirror.argentina.co/archlinux'
]

fields['url'].widget.attrs.update({'placeholder': url_examples.pop()})

def clean_url(self):
# is this a valid-looking URL?
url_parts = urlparse(self.cleaned_data["url"])
if not url_parts.scheme:
raise forms.ValidationError("No URL scheme (protocol) provided.")
if not url_parts.netloc:
raise forms.ValidationError("No URL host provided.")
if url_parts.params or url_parts.query or url_parts.fragment:
raise forms.ValidationError(
"URL parameters, query, and fragment elements are not supported.")
# ensure we always save the URL with a trailing slash
path = url_parts.path
if not path.endswith('/'):
path += '/'
url = urlunsplit((url_parts.scheme, url_parts.netloc, path, '', ''))
return url

def as_div(self):
"Returns this form rendered as HTML <divs>s."
return self._html_output(
normal_row=u'<div%(html_class_attr)s>%(label)s %(field)s%(help_text)s</div>',
error_row=u'%s',
row_ender='</div>',
help_text_html=u' <span class="helptext">%s</span>',
errors_on_separate_row=True)


class MirrorRsyncForm(forms.ModelForm):
class Meta:
model = MirrorRsync
fields = ('ip',)

def __init__(self, *args, **kwargs):
super(MirrorRsyncForm, self).__init__(*args, **kwargs)
fields = self.fields
fields['ip'].widget.attrs.update({'placeholder': 'Ex: 1.2.4.5'})

def as_div(self):
"Returns this form rendered as HTML <divs>s."
return self._html_output(
normal_row=u'<div%(html_class_attr)s>%(label)s %(field)s%(help_text)s</div>',
error_row=u'%s',
row_ender='</div>',
help_text_html=u' <span class="helptext">%s</span>',
errors_on_separate_row=True)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded change

class MirrorlistForm(forms.Form):
country = forms.MultipleChoiceField(required=False, widget=SelectMultiple(attrs={'size': '12'}))
Expand Down Expand Up @@ -127,4 +242,92 @@ def find_mirrors_simple(request, protocol):
proto = get_object_or_404(MirrorProtocol, protocol=protocol)
return find_mirrors(request, protocols=[proto])


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, but PEP8#Blank-Lines dictates two blank lines between function definitions. The rest of the code is also spaced out this way.

For reference:

return urls
def find_mirrors(request, countries=None, protocols=None, use_status=False,

def mail_mirror_admins(data):
template = loader.get_template('mirrors/new_mirror_mail_template.txt')
for mirror_maintainer in ['[email protected]', '[email protected]']:
Torxed marked this conversation as resolved.
Show resolved Hide resolved
send_mail('A mirror entry was submitted: \'%s\'' % data.get('name'),
template.render(data),
'Arch Mirror Notification <[email protected]>',
[mirror_maintainer],
fail_silently=True)


def submit_mirror(request):
if request.method == 'POST' or len(request.GET) > 0:
data = request.POST if request.method == 'POST' else request.GET

# data is immutable, need to be copied and modified to enforce
# active and public is False.
tmp = data.copy()
tmp['active'] = False
tmp['public'] = False
data = tmp

mirror_form = MirrorRequestForm(data=data)
mirror_url1_form = MirrorUrlForm(data=data, prefix="url1")
if data.get('url2-url') != '':
mirror_url2_form = MirrorUrlForm(data=data, prefix="url2")
else:
mirror_url2_form = MirrorUrlForm(prefix="url2")
if data.get('url3-url') != '':
mirror_url3_form = MirrorUrlForm(data=data, prefix="url3")
else:
mirror_url3_form = MirrorUrlForm(prefix="url3")
rsync_form = MirrorRsyncForm(data=data)

mirror_url2_form.fields['url'].required = False
mirror_url3_form.fields['url'].required = False
rsync_form.fields['ip'].required = False

if mirror_form.is_valid() and mirror_url1_form.is_valid():
try:
with transaction.atomic():
transaction.on_commit(partial(mail_mirror_admins, data))

mirror = mirror_form.save()
mirror_url1 = mirror_url1_form.save(commit=False)
mirror_url1.mirror = mirror
mirror_url1.save()

if data.get('url2-url') != '' and mirror_url2_form.is_valid():
mirror_url2 = mirror_url2_form.save(commit=False)
mirror_url2.mirror = mirror
mirror_url2.save()
if data.get('url3-url') != '' and mirror_url3_form.is_valid():
mirror_url3 = mirror_url3_form.save(commit=False)
mirror_url3.mirror = mirror
mirror_url3.save()

if data.get('ip') != '' and rsync.is_valid():
rsync = rsync_form.save(commit=False)
rsync.mirror = mirror
rsync.save()

except DatabaseError as error:
print(error)

else:
mirror_form = MirrorRequestForm()
mirror_url1_form = MirrorUrlForm(prefix="url1")
mirror_url2_form = MirrorUrlForm(prefix="url2")
mirror_url3_form = MirrorUrlForm(prefix="url3")
rsync_form = MirrorRsyncForm()

mirror_url2_form.fields['url'].required = False
mirror_url3_form.fields['url'].required = False
rsync_form.fields['ip'].required = False

return render(
request,
'mirrors/mirror_submit.html',
{
'mirror_form': mirror_form,
'mirror_url1_form': mirror_url1_form,
'mirror_url2_form': mirror_url2_form,
'mirror_url3_form': mirror_url3_form,
'rsync_form': rsync_form
}
)

# vim: set ts=4 sw=4 et:
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
cssmin==0.2.0
Torxed marked this conversation as resolved.
Show resolved Hide resolved
Django==4.1.6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be in here? Please rebase and drop it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but saw an opportunity to update old stuff seeing as they have a few vulns in 4.0 that should be fixed in 4.1. But I'll revert it and someone can update later.

IPy==1.1
Markdown==3.3.7
Expand Down
42 changes: 42 additions & 0 deletions templates/mirrors/mirror_submit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% load package_extras %}
{% block title %}Arch Linux - Pacman Mirrorlist Generator{% endblock %}

{% block content %}
<div id="mirrorlist-gen" class="box">

<h2>Mirror Request</h2>

<p>This page is meant as a replacement of the old way of registring as a Tier 1 or Tier 2 mirror. Previously this was done through <a href="https://bugs.archlinux.org/index.php?string=&project=1&search_name=&type%5B%5D=&sev%5B%5D=&pri%5B%5D=&due%5B%5D=&reported%5B%5D=&cat%5B%5D=43&status%5B%5D=open&percent%5B%5D=&opened=&dev=&closed=&duedatefrom=&duedateto=&changedfrom=&changedto=&openedfrom=&openedto=&closedfrom=&closedto=&do=index">https://bugs.archlinux.org</a> and would require manual intervention from the mirror maintainer(s). This process is now semi-automated and various checks against your mirror will be performed when submitting via the below form.</p>
Torxed marked this conversation as resolved.
Show resolved Hide resolved

<h3>Available mirrors</h3>

<p>Below are direct links to the two different tiers you will need to be acquainted with.<p>

<ul>
<li><a href="/mirrors/tier/1/">Tier 1 mirrors</a></li>
<li><a href="/mirrors/tier/2/">Tier 2 mirrors</a></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally this uses the url template tag

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll learn how those work and see if I can get it working.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to work around the fact that the tier url's use re_path(r'^tier/(?P<tier>\d+)/$', mirrors, name='mirror-list-tier'), with one common name (I assume that's how those links work after reading up).

</ul>

<h3>Mirror information</h3>

{% if mirror_form.is_valid or mirror_url1_form.is_valid %}
<code>
Your request have successfully been submitted and should be visible within 5 minutes in the mirrorlist.
</code>
{% else %}
<p>Before you can submit a <b>Tier 1</b> request the mirror in question must first be a registered <b>Tier 2</b> for a certain amount of time with proven reliablity. Once the submitted information is verified the mirror will be visible under the appropriate tier list above. This process usually takes 5 minutes.</p>
Torxed marked this conversation as resolved.
Show resolved Hide resolved

<form id="list-generator" method="get">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Form should never be a GET, but always a POST and should have:

    <form method="post">{% csrf_token %}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And just for reference, I took the method from this form:

{{ mirror_form.as_div }}
{{ mirror_url1_form.as_div }}
{{ mirror_url2_form.as_div }}
{{ mirror_url3_form.as_div }}
<p>If you are registring a <b>Tier 1</b> mirror, you need to supply the static IP to the machine that will be using rsync towards the Tier 0 mirror. This is so we can allow it to sync.
{{ rsync_form.as_div }}
<p><label></label> <input type="submit" value="Submit Request" /></p>
</form>
{% endif %}
</div>
{% endblock %}

9 changes: 9 additions & 0 deletions templates/mirrors/new_mirror_mail_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% autoescape off %}A new mirror has been requested called "{{ name }}". As the mirror administrator, check in on https://archlinux.org/mirrors/{{ name }}/ after a few minutes and check:

* Is the Completion % more than 98% for Tier 2 and 100% for Tier 1?
* Does it have HTTP or HTTPS protocols?

If so, go to https://archlinux.org/admin/mirrors/, find the new mirror and mark it as Active and Public!
If the mirror hasn't synced in a couple of days, email the mirror admin asking if we can assist in any way.

{% endautoescape %}