github.com/jiasir/deis@v1.12.2/controller/api/serializers.py (about) 1 """ 2 Classes to serialize the RESTful representation of Deis API models. 3 """ 4 5 from __future__ import unicode_literals 6 7 import json 8 import re 9 10 from django.conf import settings 11 from django.contrib.auth.models import User 12 from django.utils import timezone 13 from rest_framework import serializers 14 from rest_framework.validators import UniqueTogetherValidator 15 16 from api import models 17 18 19 PROCTYPE_MATCH = re.compile(r'^(?P<type>[a-z]+)') 20 MEMLIMIT_MATCH = re.compile(r'^(?P<mem>[0-9]+(MB|KB|GB|[BKMG]))$', re.IGNORECASE) 21 CPUSHARE_MATCH = re.compile(r'^(?P<cpu>[0-9]+)$') 22 TAGKEY_MATCH = re.compile(r'^[a-z]+$') 23 TAGVAL_MATCH = re.compile(r'^\w+$') 24 CONFIGKEY_MATCH = re.compile(r'^[a-z_]+[a-z0-9_]*$', re.IGNORECASE) 25 26 27 class JSONFieldSerializer(serializers.Field): 28 """ 29 A Django REST framework serializer for JSON data. 30 """ 31 32 def to_representation(self, obj): 33 """Serialize the field's JSON data, for read operations.""" 34 return obj 35 36 def to_internal_value(self, data): 37 """Deserialize the field's JSON data, for write operations.""" 38 try: 39 val = json.loads(data) 40 except TypeError: 41 val = data 42 return val 43 44 45 class JSONIntFieldSerializer(JSONFieldSerializer): 46 """ 47 A JSON serializer that coerces its data to integers. 48 """ 49 50 def to_internal_value(self, data): 51 """Deserialize the field's JSON integer data.""" 52 field = super(JSONIntFieldSerializer, self).to_internal_value(data) 53 54 for k, v in field.viewitems(): 55 if v is not None: # NoneType is used to unset a value 56 try: 57 field[k] = int(v) 58 except ValueError: 59 field[k] = v 60 # Do nothing, the validator will catch this later 61 return field 62 63 64 class JSONStringFieldSerializer(JSONFieldSerializer): 65 """ 66 A JSON serializer that coerces its data to strings. 67 """ 68 69 def to_internal_value(self, data): 70 """Deserialize the field's JSON string data.""" 71 field = super(JSONStringFieldSerializer, self).to_internal_value(data) 72 73 for k, v in field.viewitems(): 74 if v is not None: # NoneType is used to unset a value 75 field[k] = unicode(v) 76 77 return field 78 79 80 class ModelSerializer(serializers.ModelSerializer): 81 82 uuid = serializers.ReadOnlyField() 83 84 def get_validators(self): 85 """ 86 Hack to remove DRF's UniqueTogetherValidator when it concerns the UUID. 87 88 See https://github.com/deis/deis/pull/2898#discussion_r23105147 89 """ 90 validators = super(ModelSerializer, self).get_validators() 91 for v in validators: 92 if isinstance(v, UniqueTogetherValidator) and 'uuid' in v.fields: 93 validators.remove(v) 94 return validators 95 96 97 class UserSerializer(serializers.ModelSerializer): 98 class Meta: 99 model = User 100 fields = ['email', 'username', 'password', 'first_name', 'last_name', 'is_superuser', 101 'is_staff', 'groups', 'user_permissions', 'last_login', 'date_joined', 102 'is_active'] 103 read_only_fields = ['is_superuser', 'is_staff', 'groups', 104 'user_permissions', 'last_login', 'date_joined', 'is_active'] 105 extra_kwargs = {'password': {'write_only': True}} 106 107 def create(self, validated_data): 108 now = timezone.now() 109 user = User( 110 email=validated_data.get('email'), 111 username=validated_data.get('username'), 112 last_login=now, 113 date_joined=now, 114 is_active=True 115 ) 116 if validated_data.get('first_name'): 117 user.first_name = validated_data['first_name'] 118 if validated_data.get('last_name'): 119 user.last_name = validated_data['last_name'] 120 user.set_password(validated_data['password']) 121 # Make the first signup an admin / superuser 122 if not User.objects.filter(is_superuser=True).exists(): 123 user.is_superuser = user.is_staff = True 124 user.save() 125 return user 126 127 128 class AdminUserSerializer(serializers.ModelSerializer): 129 """Serialize admin status for a User model.""" 130 131 class Meta: 132 model = User 133 fields = ['username', 'is_superuser'] 134 read_only_fields = ['username'] 135 136 137 class AppSerializer(ModelSerializer): 138 """Serialize a :class:`~api.models.App` model.""" 139 140 owner = serializers.ReadOnlyField(source='owner.username') 141 structure = JSONFieldSerializer(required=False) 142 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 143 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 144 145 class Meta: 146 """Metadata options for a :class:`AppSerializer`.""" 147 model = models.App 148 fields = ['uuid', 'id', 'owner', 'url', 'structure', 'created', 'updated'] 149 read_only_fields = ['uuid'] 150 151 152 class BuildSerializer(ModelSerializer): 153 """Serialize a :class:`~api.models.Build` model.""" 154 155 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 156 owner = serializers.ReadOnlyField(source='owner.username') 157 procfile = JSONFieldSerializer(required=False) 158 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 159 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 160 161 class Meta: 162 """Metadata options for a :class:`BuildSerializer`.""" 163 model = models.Build 164 fields = ['owner', 'app', 'image', 'sha', 'procfile', 'dockerfile', 'created', 165 'updated', 'uuid'] 166 read_only_fields = ['uuid'] 167 168 169 class ConfigSerializer(ModelSerializer): 170 """Serialize a :class:`~api.models.Config` model.""" 171 172 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 173 owner = serializers.ReadOnlyField(source='owner.username') 174 values = JSONStringFieldSerializer(required=False) 175 memory = JSONStringFieldSerializer(required=False) 176 cpu = JSONIntFieldSerializer(required=False) 177 tags = JSONStringFieldSerializer(required=False) 178 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 179 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 180 181 class Meta: 182 """Metadata options for a :class:`ConfigSerializer`.""" 183 model = models.Config 184 185 def validate_values(self, value): 186 for k, v in value.viewitems(): 187 if not re.match(CONFIGKEY_MATCH, k): 188 raise serializers.ValidationError( 189 "Config keys must start with a letter or underscore and " 190 "only contain [A-z0-9_]") 191 return value 192 193 def validate_memory(self, value): 194 for k, v in value.viewitems(): 195 if v is None: # use NoneType to unset a value 196 continue 197 if not re.match(PROCTYPE_MATCH, k): 198 raise serializers.ValidationError("Process types can only contain [a-z]") 199 if not re.match(MEMLIMIT_MATCH, str(v)): 200 raise serializers.ValidationError( 201 "Limit format: <number><unit>, where unit = B, K, M or G") 202 return value 203 204 def validate_cpu(self, value): 205 for k, v in value.viewitems(): 206 if v is None: # use NoneType to unset a value 207 continue 208 if not re.match(PROCTYPE_MATCH, k): 209 raise serializers.ValidationError("Process types can only contain [a-z]") 210 shares = re.match(CPUSHARE_MATCH, str(v)) 211 if not shares: 212 raise serializers.ValidationError("CPU shares must be an integer") 213 for v in shares.groupdict().viewvalues(): 214 try: 215 i = int(v) 216 except ValueError: 217 raise serializers.ValidationError("CPU shares must be an integer") 218 if i > 1024 or i < 0: 219 raise serializers.ValidationError("CPU shares must be between 0 and 1024") 220 return value 221 222 def validate_tags(self, value): 223 for k, v in value.viewitems(): 224 if v is None: # use NoneType to unset a value 225 continue 226 if not re.match(TAGKEY_MATCH, k): 227 raise serializers.ValidationError("Tag keys can only contain [a-z]") 228 if not re.match(TAGVAL_MATCH, str(v)): 229 raise serializers.ValidationError("Invalid tag value") 230 return value 231 232 233 class ReleaseSerializer(ModelSerializer): 234 """Serialize a :class:`~api.models.Release` model.""" 235 236 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 237 owner = serializers.ReadOnlyField(source='owner.username') 238 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 239 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 240 241 class Meta: 242 """Metadata options for a :class:`ReleaseSerializer`.""" 243 model = models.Release 244 245 246 class ContainerSerializer(ModelSerializer): 247 """Serialize a :class:`~api.models.Container` model.""" 248 249 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 250 owner = serializers.ReadOnlyField(source='owner.username') 251 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 252 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 253 release = serializers.SerializerMethodField() 254 255 class Meta: 256 """Metadata options for a :class:`ContainerSerializer`.""" 257 model = models.Container 258 fields = ['owner', 'app', 'release', 'type', 'num', 'state', 'created', 'updated', 'uuid'] 259 260 def get_release(self, obj): 261 return "v{}".format(obj.release.version) 262 263 264 class KeySerializer(ModelSerializer): 265 """Serialize a :class:`~api.models.Key` model.""" 266 267 owner = serializers.ReadOnlyField(source='owner.username') 268 fingerprint = serializers.CharField(read_only=True) 269 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 270 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 271 272 class Meta: 273 """Metadata options for a KeySerializer.""" 274 model = models.Key 275 276 277 class DomainSerializer(ModelSerializer): 278 """Serialize a :class:`~api.models.Domain` model.""" 279 280 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 281 owner = serializers.ReadOnlyField(source='owner.username') 282 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 283 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 284 285 class Meta: 286 """Metadata options for a :class:`DomainSerializer`.""" 287 model = models.Domain 288 fields = ['uuid', 'owner', 'created', 'updated', 'app', 'domain'] 289 290 def validate_domain(self, value): 291 """ 292 Check that the hostname is valid 293 """ 294 if len(value) > 255: 295 raise serializers.ValidationError('Hostname must be 255 characters or less.') 296 if value[-1:] == ".": 297 value = value[:-1] # strip exactly one dot from the right, if present 298 labels = value.split('.') 299 if 'xip.io' in value: 300 return value 301 if labels[0] == '*': 302 raise serializers.ValidationError( 303 'Adding a wildcard subdomain is currently not supported.') 304 allowed = re.compile("^(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE) 305 for label in labels: 306 match = allowed.match(label) 307 if not match or '--' in label or label.isdigit() or \ 308 len(labels) == 1 and any(char.isdigit() for char in label): 309 raise serializers.ValidationError('Hostname does not look valid.') 310 if models.Domain.objects.filter(domain=value).exists(): 311 raise serializers.ValidationError( 312 "The domain {} is already in use by another app".format(value)) 313 return value 314 315 316 class CertificateSerializer(ModelSerializer): 317 """Serialize a :class:`~api.models.Cert` model.""" 318 319 owner = serializers.ReadOnlyField(source='owner.username') 320 expires = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 321 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 322 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 323 324 class Meta: 325 """Metadata options for a DomainCertSerializer.""" 326 model = models.Certificate 327 extra_kwargs = {'certificate': {'write_only': True}, 328 'key': {'write_only': True}, 329 'common_name': {'required': False}} 330 read_only_fields = ['expires', 'created', 'updated'] 331 332 333 class PushSerializer(ModelSerializer): 334 """Serialize a :class:`~api.models.Push` model.""" 335 336 app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all()) 337 owner = serializers.ReadOnlyField(source='owner.username') 338 created = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 339 updated = serializers.DateTimeField(format=settings.DEIS_DATETIME_FORMAT, read_only=True) 340 341 class Meta: 342 """Metadata options for a :class:`PushSerializer`.""" 343 model = models.Push 344 fields = ['uuid', 'owner', 'app', 'sha', 'fingerprint', 'receive_user', 'receive_repo', 345 'ssh_connection', 'ssh_original_command', 'created', 'updated']