Skip to content

Commit 0d05927

Browse files
committed
Merge pull request #1651 from mboersma/1552-validate-hosts
fix(controller): validate Cluster.hosts field as comma-separated
2 parents e8373ad + a1c1432 commit 0d05927

3 files changed

Lines changed: 30 additions & 3 deletions

File tree

controller/api/models.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
import importlib
1010
import logging
1111
import os
12+
import re
1213
import subprocess
1314

1415
from celery.canvas import group
1516
from django.conf import settings
1617
from django.contrib.auth.models import User
18+
from django.core.exceptions import ValidationError
1719
from django.db import models, connections
1820
from django.db.models import Max
1921
from django.db.models.signals import post_delete
@@ -50,6 +52,15 @@ def _inner(*args, **kwargs):
5052
return _inner
5153

5254

55+
def validate_comma_separated(value):
56+
"""Error if the value doesn't look like a list of hostnames or IP addresses
57+
separated by commas.
58+
"""
59+
if not re.search(r'^[a-zA-Z0-9-,\.]+$', value):
60+
raise ValidationError(
61+
"{} should be a comma-separated list".format(value))
62+
63+
5364
class AuditedModel(models.Model):
5465
"""Add created and updated fields to a model."""
5566

@@ -86,7 +97,7 @@ class Cluster(UuidAuditedModel):
8697
type = models.CharField(max_length=16, choices=CLUSTER_TYPES, default='coreos')
8798

8899
domain = models.CharField(max_length=128)
89-
hosts = models.CharField(max_length=256)
100+
hosts = models.CharField(max_length=256, validators=[validate_comma_separated])
90101
auth = models.TextField()
91102
options = JSONField(default={}, blank=True)
92103

controller/api/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ class Meta:
6868
model = models.Cluster
6969
read_only_fields = ('created', 'updated')
7070

71+
def validate_hosts(self, attrs, source):
72+
value = attrs[source]
73+
models.validate_comma_separated(value)
74+
return attrs
75+
7176

7277
class PushSerializer(serializers.ModelSerializer):
7378
"""Serialize a :class:`~api.models.Push` model."""

controller/api/tests/test_cluster.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ def test_cluster(self):
3030
url = '/api/clusters'
3131
options = {'key': 'val'}
3232
body = {'id': 'autotest', 'domain': 'autotest.local', 'type': 'mock',
33-
'hosts': 'host1,host2', 'auth': 'base64string', 'options': options}
33+
'hosts': 'host1;host2', 'auth': 'base64string', 'options': options}
34+
response = self.client.post(url, json.dumps(body), content_type='application/json')
35+
self.assertEqual(response.status_code, 400)
36+
body['hosts'] = 'host1,host2'
3437
response = self.client.post(url, json.dumps(body), content_type='application/json')
3538
self.assertEqual(response.status_code, 201)
3639
cluster_id = response.data['id'] # noqa
@@ -54,7 +57,15 @@ def test_cluster(self):
5457
url = '/api/clusters/{cluster_id}'.format(**locals())
5558
response = self.client.get(url)
5659
self.assertEqual(response.status_code, 200)
57-
new_hosts, new_options = 'host2,host3', {'key': 'val2'}
60+
# regression test for https://github.com/deis/deis/issues/1552
61+
body = {'hosts': 'host2 host3'}
62+
response = self.client.patch(url, json.dumps(body), content_type='application/json')
63+
self.assertEqual(response.status_code, 400)
64+
body = {'hosts': 'host2;host3'}
65+
response = self.client.patch(url, json.dumps(body), content_type='application/json')
66+
self.assertEqual(response.status_code, 400)
67+
# update cluster hosts
68+
new_hosts, new_options = 'host2.domain,host3.sub.domain,127.0.0.1', {'key': 'val2'}
5869
body = {'hosts': new_hosts, 'options': new_options}
5970
response = self.client.patch(url, json.dumps(body), content_type='application/json')
6071
self.assertEqual(response.status_code, 200)

0 commit comments

Comments
 (0)