Skip to content

Commit 54c4d72

Browse files
committed
feat(release): re-use image source if Build is called with an image that is already in the deis registry
Closes #614
1 parent 326f08c commit 54c4d72

3 files changed

Lines changed: 79 additions & 15 deletions

File tree

rootfs/api/models/release.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
from django.conf import settings
24
from django.db import models
35

@@ -6,6 +8,8 @@
68

79
from api.models import UuidAuditedModel, log_event
810

11+
logger = logging.getLogger(__name__)
12+
913

1014
class Release(UuidAuditedModel):
1115
"""
@@ -32,13 +36,22 @@ def __str__(self):
3236

3337
@property
3438
def image(self):
39+
# return image if it is already in the registry, test host and then host + port
40+
if (
41+
self.build.image.startswith(settings.REGISTRY_HOST) or
42+
self.build.image.startswith(settings.REGISTRY_URL)
43+
):
44+
# strip registry information off first
45+
image = self.build.image.replace('{}/'.format(settings.REGISTRY_URL), '')
46+
return image.replace('{}/'.format(settings.REGISTRY_HOST), '')
47+
3548
if not self.build.dockerfile:
3649
# Deis Pull
3750
if not self.build.sha:
3851
return '{}:v{}'.format(self.app.id, str(self.version))
39-
else:
40-
# Build Pack
41-
return self.build.image
52+
53+
# Build Pack
54+
return self.build.image
4255

4356
# DockerFile
4457
return '{}:git-{}'.format(self.app.id, str(self.build.sha))
@@ -72,6 +85,15 @@ def publish(self, source_version='latest'):
7285
raise EnvironmentError('No build associated with this release to publish')
7386

7487
source_image = self.build.image
88+
# return image if it is already in the registry, test host and then host + port
89+
if (
90+
source_image.startswith(settings.REGISTRY_HOST) or
91+
source_image.startswith(settings.REGISTRY_URL)
92+
):
93+
logger.debug('{} already exists in the target registry. Using this image for release {} of app {}'.format(source_image, self.version, self.app)) # noqa
94+
return
95+
96+
# add tag if it was not provided
7597
if ':' not in source_image:
7698
source_tag = 'git-{}'.format(self.build.sha) if self.build.sha else source_version
7799
source_image = "{}:{}".format(source_image, source_tag)
@@ -90,6 +112,7 @@ def previous(self):
90112
releases = self.app.release_set
91113
if self.pk:
92114
releases = releases.exclude(pk=self.pk)
115+
93116
try:
94117
# Get the Release previous to this one
95118
prev_release = releases.latest()

rootfs/api/tests/test_build.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from django.contrib.auth.models import User
1111
from django.core.cache import cache
12+
from django.conf import settings
1213
from rest_framework.test import APITransactionTestCase
1314
from unittest import mock
1415
from rest_framework.authtoken.models import Token
@@ -130,9 +131,11 @@ def test_build_default_containers(self, mock_requests):
130131
app_id = response.data['id']
131132
# post a new build with procfile
132133
url = "/v2/apps/{app_id}/builds".format(**locals())
133-
body = {'image': 'autotest/example',
134-
'sha': 'a'*40,
135-
'dockerfile': "FROM scratch"}
134+
body = {
135+
'image': 'autotest/example',
136+
'sha': 'a'*40,
137+
'dockerfile': "FROM scratch"
138+
}
136139
response = self.client.post(url, body)
137140
self.assertEqual(response.status_code, 201)
138141

@@ -154,10 +157,14 @@ def test_build_default_containers(self, mock_requests):
154157

155158
# post a new build with procfile
156159
url = "/v2/apps/{app_id}/builds".format(**locals())
157-
body = {'image': 'autotest/example',
158-
'sha': 'a'*40,
159-
'dockerfile': "FROM scratch",
160-
'procfile': {'worker': 'node worker.js'}}
160+
body = {
161+
'image': 'autotest/example',
162+
'sha': 'a'*40,
163+
'dockerfile': "FROM scratch",
164+
'procfile': {
165+
'worker': 'node worker.js'
166+
}
167+
}
161168
response = self.client.post(url, body)
162169
self.assertEqual(response.status_code, 201)
163170

@@ -179,10 +186,14 @@ def test_build_default_containers(self, mock_requests):
179186
# post a new build with procfile
180187

181188
url = "/v2/apps/{app_id}/builds".format(**locals())
182-
body = {'image': 'autotest/example',
183-
'sha': 'a'*40,
184-
'procfile': json.dumps({'web': 'node server.js',
185-
'worker': 'node worker.js'})}
189+
body = {
190+
'image': 'autotest/example',
191+
'sha': 'a'*40,
192+
'procfile': json.dumps({
193+
'web': 'node server.js',
194+
'worker': 'node worker.js'
195+
})
196+
}
186197
response = self.client.post(url, body)
187198
self.assertEqual(response.status_code, 201)
188199

@@ -306,3 +317,33 @@ def test_new_build_does_not_scale_up_automatically(self, mock_requests):
306317
response = self.client.get(url)
307318
self.assertEqual(response.status_code, 200)
308319
self.assertEqual(len(response.data['results']), 0)
320+
321+
def test_build_image_in_registry(self, mock_requests):
322+
"""When the image is already in the deis registry no pull/tag/push happens"""
323+
body = {'id': 'test'}
324+
url = '/v2/apps'
325+
response = self.client.post(url, body)
326+
327+
# post an image as a build using registry hostname
328+
url = "/v2/apps/test/builds".format(**locals())
329+
image = '{}/autotest/example'.format(settings.REGISTRY_HOST)
330+
body = {'image': image}
331+
response = self.client.post(url, body)
332+
self.assertEqual(response.status_code, 201)
333+
334+
build = Build.objects.get(uuid=response.data['uuid'])
335+
release = build.app.release_set.latest()
336+
# Registry host is internally stripped off
337+
self.assertEqual(release.image, 'autotest/example')
338+
339+
# post an image as a build using registry hostname + port
340+
url = "/v2/apps/test/builds".format(**locals())
341+
image = '{}/autotest/example'.format(settings.REGISTRY_URL)
342+
body = {'image': image}
343+
response = self.client.post(url, body)
344+
self.assertEqual(response.status_code, 201)
345+
346+
build = Build.objects.get(uuid=response.data['uuid'])
347+
release = build.app.release_set.latest()
348+
# Registry host + port is internally stripped off
349+
self.assertEqual(release.image, 'autotest/example')

rootfs/api/tests/test_release.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def test_release_rollback(self, mock_requests):
144144
body = {'values': json.dumps({'NEW_URL1': 'http://localhost:8080/'})}
145145
response = self.client.post(url, body)
146146
self.assertEqual(response.status_code, 201)
147-
# todo : edge case that fails becasue version 1 has no build object.
147+
# TODO: edge case that fails becasue version 1 has no build object.
148148
# update the build to roll a new release
149149
# url = '/v2/apps/{app_id}/builds'.format(**locals())
150150
# body = {'image': 'autotest/example'}

0 commit comments

Comments
 (0)