Skip to content
This repository was archived by the owner on Sep 12, 2018. It is now read-only.

Commit a0bdc2c

Browse files
committed
Adds existing arch/os information to searches and index operations
This code is a first step in making the current registry aware of images which are non-Intel architecture. While os==linux today, if we are adding architecture we might as well add the pair of arch and os for future use by Microsoft where os=windows but arch=amd64. Signed-off-by: Pradipta Kr. Banerjee <[email protected]> Signed-off-by: Phil Estes <[email protected]>
1 parent c519df0 commit a0bdc2c

File tree

4 files changed

+85
-19
lines changed

4 files changed

+85
-19
lines changed

docker_registry/index.py

+45-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# -*- coding: utf-8 -*-
22

3+
import re
4+
35
import flask
46

57
from docker_registry.core import compat
@@ -13,6 +15,7 @@
1315

1416
from .app import app # noqa
1517

18+
RE_USER_AGENT = re.compile('([^\s/]+)/([^\s/]+)')
1619

1720
store = storage.load()
1821

@@ -51,7 +54,7 @@ def put_username(username):
5154
return toolkit.response('', 204)
5255

5356

54-
def update_index_images(namespace, repository, data_arg):
57+
def update_index_images(namespace, repository, data_arg, arch, os):
5558
path = store.index_images_path(namespace, repository)
5659
sender = flask.current_app._get_current_object()
5760
try:
@@ -70,13 +73,20 @@ def update_index_images(namespace, repository, data_arg):
7073
data = images.values()
7174
# Note(dmp): unicode patch
7275
store.put_json(path, data)
76+
77+
# Get image arch and os from the json property
78+
img_data = store.get_content(store.image_json_path(data[0]['id']))
79+
arch = json.loads(img_data)['architecture']
80+
os = json.loads(img_data)['os']
81+
7382
signals.repository_updated.send(
74-
sender, namespace=namespace, repository=repository, value=data)
83+
sender, namespace=namespace, repository=repository,
84+
value=data, arch=arch, os=os)
7585
except exceptions.FileNotFoundError:
7686
signals.repository_created.send(
7787
sender, namespace=namespace, repository=repository,
7888
# Note(dmp): unicode patch
79-
value=json.loads(data_arg.decode('utf8')))
89+
value=json.loads(data_arg.decode('utf8')), arch=arch, os=os)
8090
store.put_content(path, data_arg)
8191

8292

@@ -88,14 +98,25 @@ def update_index_images(namespace, repository, data_arg):
8898
@toolkit.requires_auth
8999
def put_repository(namespace, repository, images=False):
90100
data = None
101+
# Default arch/os are amd64/linux
102+
arch = 'amd64'
103+
os = 'linux'
104+
# If caller is docker host, retrieve arch/os from user agent
105+
user_agent = flask.request.headers.get('user-agent', '')
106+
ua = dict(RE_USER_AGENT.findall(user_agent))
107+
if 'arch' in ua:
108+
arch = ua['arch']
109+
if 'os' in ua:
110+
os = ua['os']
111+
91112
try:
92113
# Note(dmp): unicode patch
93114
data = json.loads(flask.request.data.decode('utf8'))
94115
except ValueError:
95116
return toolkit.api_error('Error Decoding JSON', 400)
96117
if not isinstance(data, list):
97118
return toolkit.api_error('Invalid data')
98-
update_index_images(namespace, repository, flask.request.data)
119+
update_index_images(namespace, repository, flask.request.data, arch, os)
99120
headers = generate_headers(namespace, repository, 'write')
100121
code = 204 if images is True else 200
101122
return toolkit.response('', code, headers)
@@ -107,9 +128,28 @@ def put_repository(namespace, repository, images=False):
107128
@mirroring.source_lookup(index_route=True)
108129
def get_repository_images(namespace, repository):
109130
data = None
131+
# Default arch/os are amd64/linux
132+
arch = 'amd64'
133+
os = 'linux'
134+
# If caller is docker host, retrieve arch/os from user agent
135+
user_agent = flask.request.headers.get('user-agent', '')
136+
ua = dict(RE_USER_AGENT.findall(user_agent))
137+
if 'arch' in ua:
138+
arch = ua['arch']
139+
if 'os' in ua:
140+
os = ua['os']
141+
110142
try:
111143
path = store.index_images_path(namespace, repository)
112-
data = store.get_content(path)
144+
json_data = store.get_json(path)
145+
img_data = store.get_content(store.image_json_path(json_data[0]['id']))
146+
# Get image arch and os from the json property
147+
img_arch = json.loads(img_data)['architecture']
148+
img_os = json.loads(img_data)['os']
149+
if arch != img_arch or os != img_os:
150+
return toolkit.api_error('images not found for arch/os pair', 404)
151+
else:
152+
data = store.get_content(path)
113153
except exceptions.FileNotFoundError:
114154
return toolkit.api_error('images not found', 404)
115155
headers = generate_headers(namespace, repository, 'read')

docker_registry/lib/index/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,17 @@ def _walk_storage(self, store):
5252
yield({'name': name, 'description': description})
5353

5454
def _handle_repository_created(
55-
self, sender, namespace, repository, value):
55+
self, sender, namespace, repository, value, arch, os):
5656
pass
5757

5858
def _handle_repository_updated(
59-
self, sender, namespace, repository, value):
59+
self, sender, namespace, repository, value, arch, os):
6060
pass
6161

6262
def _handle_repository_deleted(self, sender, namespace, repository):
6363
pass
6464

65-
def results(self, search_term=None):
65+
def results(self, search_term=None, arch=None, os=None):
6666
"""Return a list of results matching search_term
6767
6868
The list elements should be dictionaries:

docker_registry/lib/index/db.py

+21-8
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,16 @@ class Repository (Base):
4242
nullable=False, unique=True)
4343
description = sqlalchemy.Column(
4444
sqlalchemy.String(length=100))
45+
arch = sqlalchemy.Column(
46+
sqlalchemy.String(length=10))
47+
os = sqlalchemy.Column(
48+
sqlalchemy.String(length=10))
4549

4650
def __repr__(self):
47-
return "<{0}(name='{1}', description='{2}')>".format(
48-
type(self).__name__, self.name, self.description)
51+
rep_str = ("<{0}(name='{1}', description='{2}', arch='{3}', "
52+
"os='{4}')>")
53+
return rep_str.format(type(self).__name__, self.name,
54+
self.description, self.arch, self.os)
4955

5056

5157
def retry(f):
@@ -117,24 +123,25 @@ def _generate_index(self, session):
117123

118124
@retry
119125
def _handle_repository_created(
120-
self, sender, namespace, repository, value):
126+
self, sender, namespace, repository, value, arch, os):
121127
name = '{0}/{1}'.format(namespace, repository)
122128
description = '' # TODO(wking): store descriptions
123129
session = self._session()
124-
session.add(Repository(name=name, description=description))
130+
session.add(Repository(name=name, description=description, arch=arch,
131+
os=os))
125132
session.commit()
126133
session.close()
127134

128135
@retry
129136
def _handle_repository_updated(
130-
self, sender, namespace, repository, value):
137+
self, sender, namespace, repository, value, arch, os):
131138
name = '{0}/{1}'.format(namespace, repository)
132139
description = '' # TODO(wking): store descriptions
133140
session = self._session()
134141
session.query(Repository).filter(
135142
Repository.name == name
136143
).update(
137-
values={'description': description},
144+
values={'description': description, 'arch': arch, 'os': os},
138145
synchronize_session=False
139146
)
140147
session.commit()
@@ -149,19 +156,25 @@ def _handle_repository_deleted(self, sender, namespace, repository):
149156
session.close()
150157

151158
@retry
152-
def results(self, search_term=None):
159+
def results(self, search_term=None, arch=None, os=None):
153160
session = self._session()
154161
repositories = session.query(Repository)
155162
if search_term:
156163
like_term = '%%%s%%' % search_term
157164
repositories = repositories.filter(
158165
sqlalchemy.sql.or_(
159166
Repository.name.like(like_term),
160-
Repository.description.like(like_term)))
167+
Repository.description.like(like_term)),
168+
sqlalchemy.sql.and_(
169+
Repository.arch.like(arch)),
170+
sqlalchemy.sql.and_(
171+
Repository.os.like(os)))
161172
results = [
162173
{
163174
'name': repo.name,
164175
'description': repo.description,
176+
'arch': repo.arch,
177+
'os': repo.os,
165178
}
166179
for repo in repositories]
167180
session.close()

docker_registry/search.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
# -*- coding: utf-8 -*-
2+
import re
23

4+
import flask
35
from . import toolkit
46
from .app import app
57
from .lib import config
68
from .lib import index
79
from .lib import mirroring
8-
import flask
9-
1010

1111
cfg = config.load()
1212

1313
# Enable the search index
1414
INDEX = index.load(cfg.search_backend.lower())
1515

16+
RE_USER_AGENT = re.compile('([^\s/]+)/([^\s/]+)')
17+
1618

1719
@app.route('/v1/search', methods=['GET'])
1820
@mirroring.source_lookup(index_route=True, merge_results=True)
1921
def get_search():
22+
# default to standard 64-bit linux, then check UA for
23+
# specific arch/os (if coming from a docker host)
24+
arch = 'amd64'
25+
os = 'linux'
26+
user_agent = flask.request.headers.get('user-agent', '')
27+
ua = dict(RE_USER_AGENT.findall(user_agent))
28+
if 'arch' in ua:
29+
arch = ua['arch']
30+
if 'os' in ua:
31+
os = ua['os']
32+
2033
search_term = flask.request.args.get('q', '')
21-
results = INDEX.results(search_term=search_term)
34+
results = INDEX.results(search_term=search_term, arch=arch, os=os)
2235
return toolkit.response({
2336
'query': search_term,
2437
'num_results': len(results),

0 commit comments

Comments
 (0)