Skip to content

Commit 5184dd6

Browse files
author
Matthew Fisher
committed
move deis controller to separate project
1 parent b8a540b commit 5184dd6

143 files changed

Lines changed: 10964 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn deis.wsgi -b 0.0.0.0:8000 -w 8 -n deis --log-level debug

api/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
The **api** Django app presents a RESTful web API for interacting with the **deis** system.
3+
"""

api/admin.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
Django admin app configuration for Deis API models.
5+
"""
6+
7+
from __future__ import unicode_literals
8+
9+
from django.contrib import admin
10+
from guardian.admin import GuardedModelAdmin
11+
12+
from .models import App
13+
from .models import Build
14+
from .models import Config
15+
from .models import Container
16+
from .models import Flavor
17+
from .models import Formation
18+
from .models import Key
19+
from .models import Layer
20+
from .models import Node
21+
from .models import Provider
22+
from .models import Release
23+
24+
25+
class AppAdmin(GuardedModelAdmin):
26+
"""Set presentation options for :class:`~api.models.App` models
27+
in the Django admin.
28+
"""
29+
date_hierarchy = 'created'
30+
list_display = ('id', 'owner', 'formation')
31+
list_filter = ('owner', 'formation')
32+
admin.site.register(App, AppAdmin)
33+
34+
35+
class BuildAdmin(admin.ModelAdmin):
36+
"""Set presentation options for :class:`~api.models.Build` models
37+
in the Django admin.
38+
"""
39+
date_hierarchy = 'created'
40+
list_display = ('sha', 'owner', 'app')
41+
list_filter = ('owner', 'app')
42+
admin.site.register(Build, BuildAdmin)
43+
44+
45+
class ConfigAdmin(admin.ModelAdmin):
46+
"""Set presentation options for :class:`~api.models.Config` models
47+
in the Django admin.
48+
"""
49+
date_hierarchy = 'created'
50+
list_display = ('version', 'owner', 'app')
51+
list_filter = ('owner', 'app')
52+
admin.site.register(Config, ConfigAdmin)
53+
54+
55+
class ContainerAdmin(admin.ModelAdmin):
56+
"""Set presentation options for :class:`~api.models.Container` models
57+
in the Django admin.
58+
"""
59+
date_hierarchy = 'created'
60+
list_display = ('short_name', 'owner', 'formation', 'app', 'status')
61+
list_filter = ('owner', 'formation', 'app', 'status')
62+
admin.site.register(Container, ContainerAdmin)
63+
64+
65+
class FlavorAdmin(admin.ModelAdmin):
66+
"""Set presentation options for :class:`~api.models.Flavor` models
67+
in the Django admin.
68+
"""
69+
date_hierarchy = 'created'
70+
list_display = ('id', 'owner', 'provider')
71+
list_filter = ('owner', 'provider')
72+
admin.site.register(Flavor, FlavorAdmin)
73+
74+
75+
class FormationAdmin(admin.ModelAdmin):
76+
"""Set presentation options for :class:`~api.models.Formation` models
77+
in the Django admin.
78+
"""
79+
date_hierarchy = 'created'
80+
list_display = ('id', 'owner')
81+
list_filter = ('owner',)
82+
admin.site.register(Formation, FormationAdmin)
83+
84+
85+
class KeyAdmin(admin.ModelAdmin):
86+
"""Set presentation options for :class:`~api.models.Key` models
87+
in the Django admin.
88+
"""
89+
date_hierarchy = 'created'
90+
list_display = ('id', 'owner', '__str__')
91+
list_filter = ('owner',)
92+
admin.site.register(Key, KeyAdmin)
93+
94+
95+
class LayerAdmin(admin.ModelAdmin):
96+
"""Set presentation options for :class:`~api.models.Layer` models
97+
in the Django admin.
98+
"""
99+
date_hierarchy = 'created'
100+
list_display = ('id', 'owner', 'formation', 'flavor', 'proxy', 'runtime', 'config')
101+
list_filter = ('owner', 'formation', 'flavor')
102+
admin.site.register(Layer, LayerAdmin)
103+
104+
105+
class NodeAdmin(admin.ModelAdmin):
106+
"""Set presentation options for :class:`~api.models.Node` models
107+
in the Django admin.
108+
"""
109+
date_hierarchy = 'created'
110+
list_display = ('id', 'owner', 'formation', 'fqdn')
111+
list_filter = ('owner', 'formation')
112+
admin.site.register(Node, NodeAdmin)
113+
114+
115+
class ProviderAdmin(admin.ModelAdmin):
116+
"""Set presentation options for :class:`~api.models.Provider` models
117+
in the Django admin.
118+
"""
119+
date_hierarchy = 'created'
120+
list_display = ('id', 'owner', 'type')
121+
list_filter = ('owner', 'type')
122+
admin.site.register(Provider, ProviderAdmin)
123+
124+
125+
class ReleaseAdmin(admin.ModelAdmin):
126+
"""Set presentation options for :class:`~api.models.Release` models
127+
in the Django admin.
128+
"""
129+
date_hierarchy = 'created'
130+
list_display = ('owner', 'app', 'version')
131+
list_filter = ('owner', 'app')
132+
admin.site.register(Release, ReleaseAdmin)

api/docker.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import cStringIO
2+
import hashlib
3+
import json
4+
import requests
5+
import tarfile
6+
import urlparse
7+
import uuid
8+
9+
from deis import settings
10+
11+
12+
def publish_release(repository_path, config, tag):
13+
"""
14+
Publish a new release as a Docker image
15+
16+
Given a source repository path, a dictionary of environment variables
17+
and a target tag, create a new lightweight Docker image on the registry.
18+
19+
For example, publish_release('gabrtv/myapp', {'ENVVAR': 'values'}, 'v23')
20+
results in a new Docker image at: <registry_url>/gabrtv/myapp:v23
21+
which contains the new configuration as ENV entries.
22+
"""
23+
try:
24+
image_id = _get_tag(repository_path, 'latest')
25+
except RuntimeError:
26+
# no image exists yet, so let's build one!
27+
_put_first_image(repository_path)
28+
image_id = _get_tag(repository_path, 'latest')
29+
image = _get_image(image_id)
30+
# construct the new image
31+
image['parent'] = image['id']
32+
image['id'] = _new_id()
33+
image['config']['Env'] = _construct_env(image['config']['Env'], config)
34+
# update and tag the new image
35+
_commit(repository_path, image, _empty_tar_archive(), tag)
36+
37+
38+
# registry access
39+
40+
41+
def _commit(repository_path, image, layer, tag):
42+
_put_image(image)
43+
cookies = _put_layer(image['id'], layer)
44+
_put_checksum(image, cookies)
45+
_put_tag(image['id'], repository_path, tag)
46+
# point latest to the new tag
47+
_put_tag(image['id'], repository_path, 'latest')
48+
49+
50+
def _put_first_image(repository_path):
51+
image = {
52+
'id': _new_id(),
53+
'parent': '',
54+
'config': {
55+
'Env': []
56+
}
57+
}
58+
# tag as v0 in the registry
59+
_commit(repository_path, image, _empty_tar_archive(), 'v0')
60+
61+
62+
def _get_tag(repository, tag):
63+
path = "/v1/repositories/{repository}/tags/{tag}".format(**locals())
64+
url = urlparse.urljoin(settings.REGISTRY_URL, path)
65+
r = requests.get(url)
66+
if not r.status_code == 200:
67+
raise RuntimeError("GET Image Error ({}: {})".format(r.status_code, r.text))
68+
print r.text
69+
return r.json()
70+
71+
72+
def _get_image(image_id):
73+
path = "/v1/images/{image_id}/json".format(**locals())
74+
url = urlparse.urljoin(settings.REGISTRY_URL, path)
75+
r = requests.get(url)
76+
if not r.status_code == 200:
77+
raise RuntimeError("GET Image Error ({}: {})".format(r.status_code, r.text))
78+
return r.json()
79+
80+
81+
def _put_image(image):
82+
path = "/v1/images/{id}/json".format(**image)
83+
url = urlparse.urljoin(settings.REGISTRY_URL, path)
84+
r = requests.put(url, data=json.dumps(image))
85+
if not r.status_code == 200:
86+
raise RuntimeError("PUT Image Error ({}: {})".format(r.status_code, r.text))
87+
return r.json()
88+
89+
90+
def _put_layer(image_id, layer_fileobj):
91+
path = "/v1/images/{image_id}/layer".format(**locals())
92+
url = urlparse.urljoin(settings.REGISTRY_URL, path)
93+
r = requests.put(url, data=layer_fileobj.read())
94+
if not r.status_code == 200:
95+
raise RuntimeError("PUT Layer Error ({}: {})".format(r.status_code, r.text))
96+
return r.cookies
97+
98+
99+
def _put_checksum(image, cookies):
100+
path = "/v1/images/{id}/checksum".format(**image)
101+
url = urlparse.urljoin(settings.REGISTRY_URL, path)
102+
tarsum = TarSum(json.dumps(image)).compute()
103+
headers = {'X-Docker-Checksum': tarsum}
104+
r = requests.put(url, headers=headers, cookies=cookies)
105+
if not r.status_code == 200:
106+
raise RuntimeError("PUT Checksum Error ({}: {})".format(r.status_code, r.text))
107+
print r.json()
108+
109+
110+
def _put_tag(image_id, repository_path, tag):
111+
path = "/v1/repositories/{repository_path}/tags/{tag}".format(**locals())
112+
url = urlparse.urljoin(settings.REGISTRY_URL, path)
113+
r = requests.put(url, data=json.dumps(image_id))
114+
if not r.status_code == 200:
115+
raise RuntimeError("PUT Tag Error ({}: {})".format(r.status_code, r.text))
116+
print r.json()
117+
118+
119+
# utility functions
120+
121+
122+
def _construct_env(env, config):
123+
"Update current environment with latest config"
124+
new_env = []
125+
# see if we need to update existing ENV vars
126+
for e in env:
127+
k, v = e.split('=', 1)
128+
if k in config:
129+
# update values defined by config
130+
v = config.pop(k)
131+
new_env.append("{}={}".format(k, v))
132+
# add other config ENV items
133+
for k, v in config.items():
134+
new_env.append("{}={}".format(k, v))
135+
return new_env
136+
137+
138+
def _new_id():
139+
"Return 64-char UUID for use as Image ID"
140+
return ''.join(uuid.uuid4().hex * 2)
141+
142+
143+
def _empty_tar_archive():
144+
"Return an empty tar archive (in memory)"
145+
data = cStringIO.StringIO()
146+
tar = tarfile.open(mode="w", fileobj=data)
147+
tar.close()
148+
data.seek(0)
149+
return data
150+
151+
152+
#
153+
# Below adapted from https://github.com/dotcloud/docker-registry/blob/master/lib/checksums.py
154+
#
155+
156+
def sha256_file(fp, data=None):
157+
h = hashlib.sha256(data or '')
158+
if not fp:
159+
return h.hexdigest()
160+
while True:
161+
buf = fp.read(4096)
162+
if not buf:
163+
break
164+
h.update(buf)
165+
return h.hexdigest()
166+
167+
168+
def sha256_string(s):
169+
return hashlib.sha256(s).hexdigest()
170+
171+
172+
class TarSum(object):
173+
174+
def __init__(self, json_data):
175+
self.json_data = json_data
176+
self.hashes = []
177+
self.header_fields = ('name', 'mode', 'uid', 'gid', 'size', 'mtime',
178+
'type', 'linkname', 'uname', 'gname', 'devmajor',
179+
'devminor')
180+
181+
def append(self, member, tarobj):
182+
header = ''
183+
for field in self.header_fields:
184+
value = getattr(member, field)
185+
if field == 'type':
186+
field = 'typeflag'
187+
elif field == 'name':
188+
if member.isdir() and not value.endswith('/'):
189+
value += '/'
190+
header += '{0}{1}'.format(field, value)
191+
h = None
192+
try:
193+
if member.size > 0:
194+
f = tarobj.extractfile(member)
195+
h = sha256_file(f, header)
196+
else:
197+
h = sha256_string(header)
198+
except KeyError:
199+
h = sha256_string(header)
200+
self.hashes.append(h)
201+
202+
def compute(self):
203+
self.hashes.sort()
204+
data = self.json_data + ''.join(self.hashes)
205+
tarsum = 'tarsum+sha256:{0}'.format(sha256_string(data))
206+
return tarsum

api/exceptions.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""
2+
Deis API exception classes.
3+
"""
4+
5+
from __future__ import unicode_literals
6+
7+
from rest_framework.exceptions import APIException
8+
from rest_framework import status
9+
10+
11+
class AbstractDeisException(APIException):
12+
"""
13+
Abstract class in which all Deis Exceptions and Errors should extend.
14+
15+
This exception is subclassed from rest_framework's APIException so that
16+
subclasses can change the status code to something different than
17+
"500 SERVER ERROR."
18+
"""
19+
20+
def __init__(self, detail=None):
21+
self.detail = detail
22+
23+
class Meta:
24+
abstract = True
25+
26+
27+
class BuildNodeError(AbstractDeisException):
28+
"""
29+
Indicates a problem in building or bootstrapping a node.
30+
"""
31+
status_code = status.HTTP_401_UNAUTHORIZED
32+
33+
34+
class BuildFormationError(AbstractDeisException):
35+
"""
36+
Indicates a problem in creating a formation.
37+
"""
38+
status_code = status.HTTP_400_BAD_REQUEST
39+
40+
41+
class UserRegistrationException(AbstractDeisException):
42+
"""
43+
Indicates that there was a problem registering the user.
44+
"""
45+
status_code = status.HTTP_400_BAD_REQUEST

0 commit comments

Comments
 (0)