Skip to content

Commit b690e01

Browse files
committed
feat(registry): move registry information to its own top level Config resource
ref #639
1 parent fa13d46 commit b690e01

5 files changed

Lines changed: 139 additions & 9 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.9.5 on 2016-04-21 19:06
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations
6+
import jsonfield.fields
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('api', '0007_auto_20160226_2335'),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name='config',
18+
name='registry',
19+
field=jsonfield.fields.JSONField(blank=True, default={}),
20+
),
21+
]

rootfs/api/models/config.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Config(UuidAuditedModel):
1717
memory = JSONField(default={}, blank=True)
1818
cpu = JSONField(default={}, blank=True)
1919
tags = JSONField(default={}, blank=True)
20+
registry = JSONField(default={}, blank=True)
2021

2122
class Meta:
2223
get_latest_by = 'created'
@@ -80,11 +81,15 @@ def set_healthchecks(self):
8081
# having succeeded.
8182
self.values['HEALTHCHECK_FAILURE_THRESHOLD'] = health['failure_threshold']
8283

84+
def set_registry(self):
85+
# lower case all registry options for consistency
86+
self.registry = {key.lower(): value for key, value in self.registry.copy().items()}
87+
8388
def save(self, **kwargs):
8489
"""merge the old config with the new"""
8590
try:
8691
previous_config = self.app.config_set.latest()
87-
for attr in ['cpu', 'memory', 'tags', 'values']:
92+
for attr in ['cpu', 'memory', 'tags', 'registry', 'values']:
8893
# Guard against migrations from older apps without fixes to
8994
# JSONField encoding.
9095
try:
@@ -104,8 +109,8 @@ def save(self, **kwargs):
104109
except Config.DoesNotExist:
105110
pass
106111

107-
# set any missing HEALTHCHECK_* elements
108112
self.set_healthchecks()
113+
self.set_registry()
109114

110115
# verify the tags exist on any nodes as labels
111116
if self.tags:

rootfs/api/models/release.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ def publish(self, source_version='latest'):
107107
if not self.build.dockerfile and not self.build.sha:
108108
# gather custom login information for registry if needed
109109
auth = None
110-
if self.config.values.get('IMAGE_AUTH_USER', None):
110+
if self.config.registry.get('username', None):
111111
auth = {
112-
'username': self.config.values.get('IMAGE_AUTH_USER', None),
113-
'password': self.config.values.get('IMAGE_AUTH_PASSWORD', None),
112+
'username': self.config.registry.get('username', None),
113+
'password': self.config.registry.get('password', None),
114114
'email': self.owner.email
115115
}
116116

@@ -248,8 +248,10 @@ def save(self, *args, **kwargs): # noqa
248248
self.summary += "{} deployed {}".format(self.build.owner, self.build.sha[:7])
249249
else:
250250
self.summary += "{} deployed {}".format(self.build.owner, self.build.image)
251+
251252
# if the config data changed, log the dict diff
252253
if self.config != old_config:
254+
# if env vars change, log the dict diff
253255
dict1 = self.config.values
254256
dict2 = old_config.values if old_config else {}
255257
diff = dict_diff(dict1, dict2)
@@ -265,6 +267,7 @@ def save(self, *args, **kwargs): # noqa
265267
if self.summary:
266268
self.summary += ' and '
267269
self.summary += "{} {}".format(self.config.owner, changes)
270+
268271
# if the limits changed (memory or cpu), log the dict diff
269272
changes = []
270273
old_mem = old_config.memory if old_config else {}
@@ -278,6 +281,7 @@ def save(self, *args, **kwargs): # noqa
278281
if changes:
279282
changes = 'changed limits for '+', '.join(changes)
280283
self.summary += "{} {}".format(self.config.owner, changes)
284+
281285
# if the tags changed, log the dict diff
282286
changes = []
283287
old_tags = old_config.tags if old_config else {}
@@ -294,6 +298,24 @@ def save(self, *args, **kwargs): # noqa
294298
if self.summary:
295299
self.summary += ' and '
296300
self.summary += "{} {}".format(self.config.owner, changes)
301+
302+
# if the registry information changed, log the dict diff
303+
changes = []
304+
old_registry = old_config.registry if old_config else {}
305+
diff = dict_diff(self.config.registry, old_registry)
306+
# try to be as succinct as possible
307+
added = ', '.join(k for k in diff.get('added', {}))
308+
added = 'added registry info ' + added if added else ''
309+
changed = ', '.join(k for k in diff.get('changed', {}))
310+
changed = 'changed registry info ' + changed if changed else ''
311+
deleted = ', '.join(k for k in diff.get('deleted', {}))
312+
deleted = 'deleted registry info ' + deleted if deleted else ''
313+
changes = ', '.join(i for i in (added, changed, deleted) if i)
314+
if changes:
315+
if self.summary:
316+
self.summary += ' and '
317+
self.summary += "{} {}".format(self.config.owner, changes)
318+
297319
if not self.summary:
298320
if self.version == 1:
299321
self.summary = "{} created the initial release".format(self.owner)

rootfs/api/serializers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class ConfigSerializer(serializers.ModelSerializer):
124124
memory = JSONFieldSerializer(required=False, binary=True)
125125
cpu = JSONFieldSerializer(required=False, binary=True)
126126
tags = JSONFieldSerializer(required=False, binary=True)
127+
registry = JSONFieldSerializer(required=False, binary=True)
127128

128129
class Meta:
129130
"""Metadata options for a :class:`ConfigSerializer`."""
@@ -208,6 +209,15 @@ def validate_tags(self, data):
208209

209210
return data
210211

212+
def validate_registry(self, data):
213+
for key, value in data.items():
214+
if not re.match(CONFIGKEY_MATCH, key):
215+
raise serializers.ValidationError(
216+
"Config keys must start with a letter or underscore and "
217+
"only contain [A-z0-9_]")
218+
219+
return data
220+
211221

212222
class ReleaseSerializer(serializers.ModelSerializer):
213223
"""Serialize a :class:`~api.models.Release` model."""

rootfs/api/tests/test_config.py

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,15 @@ def test_response_data(self, mock_requests):
126126
response = self.client.post(url, body)
127127
for key in response.data:
128128
self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
129-
'cpu', 'tags'])
129+
'cpu', 'tags', 'registry'])
130130
expected = {
131131
'owner': self.user.username,
132132
'app': 'test',
133133
'values': {'PORT': '5000'},
134134
'memory': {},
135135
'cpu': {},
136-
'tags': {}
136+
'tags': {},
137+
'registry': {}
137138
}
138139
self.assertDictContainsSubset(expected, response.data)
139140

@@ -148,14 +149,15 @@ def test_response_data_types_converted(self, mock_requests):
148149
self.assertEqual(response.status_code, 201)
149150
for key in response.data:
150151
self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
151-
'cpu', 'tags'])
152+
'cpu', 'tags', 'registry'])
152153
expected = {
153154
'owner': self.user.username,
154155
'app': 'test',
155156
'values': {'PORT': '5000'},
156157
'memory': {},
157158
'cpu': {'web': "1024"},
158-
'tags': {}
159+
'tags': {},
160+
'registry': {}
159161
}
160162
self.assertDictContainsSubset(expected, response.data)
161163

@@ -535,6 +537,76 @@ def test_tags(self, mock_requests):
535537
response = self.client.delete(url)
536538
self.assertEqual(response.status_code, 405)
537539

540+
def test_registry(self, mock_requests):
541+
"""
542+
Test that registry information can be set on an application
543+
"""
544+
url = '/v2/apps'
545+
response = self.client.post(url)
546+
self.assertEqual(response.status_code, 201)
547+
app_id = response.data['id']
548+
549+
# check default
550+
url = '/v2/apps/{app_id}/config'.format(**locals())
551+
response = self.client.get(url)
552+
self.assertEqual(response.status_code, 200)
553+
self.assertIn('registry', response.data)
554+
self.assertEqual(response.data['registry'], {})
555+
556+
# set some registry information
557+
body = {'registry': json.dumps({'username': 'bob'})}
558+
response = self.client.post(url, body)
559+
self.assertEqual(response.status_code, 201)
560+
registry1 = response.data
561+
562+
# check registry information again
563+
response = self.client.get(url)
564+
self.assertEqual(response.status_code, 200)
565+
self.assertIn('registry', response.data)
566+
registry = response.data['registry']
567+
self.assertIn('username', registry)
568+
self.assertEqual(registry['username'], 'bob')
569+
570+
# set an additional value
571+
# set them upper case, internally it should translate to lower
572+
body = {'registry': json.dumps({'PASSWORD': 's3cur3pw1'})}
573+
response = self.client.post(url, body)
574+
self.assertEqual(response.status_code, 201)
575+
registry2 = response.data
576+
self.assertNotEqual(registry1['uuid'], registry2['uuid'])
577+
registry = response.data['registry']
578+
self.assertIn('password', registry)
579+
self.assertEqual(registry['password'], 's3cur3pw1')
580+
self.assertIn('username', registry)
581+
self.assertEqual(registry['username'], 'bob')
582+
583+
# read the registry information again
584+
response = self.client.get(url)
585+
self.assertEqual(response.status_code, 200)
586+
registry3 = response.data
587+
self.assertEqual(registry2, registry3)
588+
registry = response.data['registry']
589+
self.assertIn('password', registry)
590+
self.assertEqual(registry['password'], 's3cur3pw1')
591+
self.assertIn('username', registry)
592+
self.assertEqual(registry['username'], 'bob')
593+
594+
# unset a value
595+
body = {'registry': json.dumps({'password': None})}
596+
response = self.client.post(url, body)
597+
self.assertEqual(response.status_code, 201)
598+
registry4 = response.data
599+
self.assertNotEqual(registry3['uuid'], registry4['uuid'])
600+
self.assertNotIn('password', json.dumps(response.data['registry']))
601+
602+
# disallow put/patch/delete
603+
response = self.client.put(url)
604+
self.assertEqual(response.status_code, 405)
605+
response = self.client.patch(url)
606+
self.assertEqual(response.status_code, 405)
607+
response = self.client.delete(url)
608+
self.assertEqual(response.status_code, 405)
609+
538610
def test_config_owner_is_requesting_user(self, mock_requests):
539611
"""
540612
Ensure that setting the config value is owned by the requesting user

0 commit comments

Comments
 (0)