2525from django .utils .encoding import python_2_unicode_compatible
2626from django_fsm import FSMField , transition
2727from django_fsm .signals import post_transition
28- from docker .utils import utils
28+ from docker .utils import utils as dockerutils
2929from json_field .fields import JSONField
3030import requests
3131from rest_framework .authtoken .models import Token
3232
33- from api import fields
33+ from api import fields , utils
3434from registry import publish_release
3535from utils import dict_diff , fingerprint
3636
@@ -44,6 +44,15 @@ def log_event(app, msg, level=logging.INFO):
4444 app .log (msg ) # local filesystem
4545
4646
47+ def validate_id_is_docker_compatible (value ):
48+ """
49+ Check that the ID follows docker's image name constraints
50+ """
51+ match = re .match (r'^[a-z0-9-]+$' , value )
52+ if not match :
53+ raise ValidationError ("App IDs can only contain [a-z0-9-]." )
54+
55+
4756def validate_app_structure (value ):
4857 """Error if the dict values aren't ints >= 0."""
4958 try :
@@ -54,6 +63,12 @@ def validate_app_structure(value):
5463 raise ValidationError (err )
5564
5665
66+ def validate_reserved_names (value ):
67+ """A value cannot use some reserved names."""
68+ if value in ['deis' ]:
69+ raise ValidationError ('{} is a reserved name.' .format (value ))
70+
71+
5772def validate_comma_separated (value ):
5873 """Error if the value doesn't look like a list of hostnames or IP addresses
5974 separated by commas.
@@ -97,7 +112,9 @@ class App(UuidAuditedModel):
97112 """
98113
99114 owner = models .ForeignKey (settings .AUTH_USER_MODEL )
100- id = models .SlugField (max_length = 64 , unique = True )
115+ id = models .SlugField (max_length = 64 , unique = True , default = utils .generate_app_name ,
116+ validators = [validate_id_is_docker_compatible ,
117+ validate_reserved_names ])
101118 structure = JSONField (default = {}, blank = True , validators = [validate_app_structure ])
102119
103120 class Meta :
@@ -334,7 +351,7 @@ def run(self, user, command):
334351
335352 # check for backwards compatibility
336353 def _has_hostname (image ):
337- repo , tag = utils .parse_repository_tag (image )
354+ repo , tag = dockerutils .parse_repository_tag (image )
338355 return True if '/' in repo and '.' in repo .split ('/' )[0 ] else False
339356
340357 if not _has_hostname (image ):
@@ -588,6 +605,29 @@ class Meta:
588605 def __str__ (self ):
589606 return "{}-{}" .format (self .app .id , self .uuid [:7 ])
590607
608+ def save (self , ** kwargs ):
609+ """merge the old config with the new"""
610+ try :
611+ previous_config = self .app .config_set .latest ()
612+ for attr in ['cpu' , 'memory' , 'tags' , 'values' ]:
613+ # Guard against migrations from older apps without fixes to
614+ # JSONField encoding.
615+ try :
616+ data = getattr (previous_config , attr ).copy ()
617+ except AttributeError :
618+ data = {}
619+ try :
620+ new_data = getattr (self , attr ).copy ()
621+ except AttributeError :
622+ new_data = {}
623+ data .update (new_data )
624+ # remove config keys if we provided a null value
625+ [data .pop (k ) for k , v in new_data .items () if v is None ]
626+ setattr (self , attr , data )
627+ except Config .DoesNotExist :
628+ pass
629+ return super (Config , self ).save (** kwargs )
630+
591631
592632@python_2_unicode_compatible
593633class Release (UuidAuditedModel ):
0 commit comments