Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ The strategies in these specifications are inspired by some previous implementat
* [App Container Image Discovery](https://github.com/appc/spec/blob/v0.8.10/spec/discovery.md)
* [parcel](https://github.com/cyphar/parcel)

## Python dependencies

The [OCI Index Template Protocol](index-template.md) [implementation](oci_discovery/ref_engine/oci_index_template) depends on the [uritemplate][] package.
You can install the dependencies with [pip][]:

```
$ pip install -r requirements.txt
```

When uritemplate is not installed, a local implementation is used instead.
But the local stub supports only the most basic [URI Templates][rfc6570].

## Using the Python 3 ref-engine discovery tool

The individual components are usable as libraries, but the ref-engine discovery implementation can also be used from the command line:
Expand Down Expand Up @@ -258,5 +270,8 @@ location ~ ^/oci-image/.*/index.json$ {
[layout]: https://github.com/opencontainers/image-spec/blob/v1.0.0/image-layout.md
[location]: http://nginx.org/en/docs/http/ngx_http_core_module.html#location
[Nginx]: https://nginx.org/
[pip]: https://pip.pypa.io/en/stable/
[python3]: https://docs.python.org/3/
[rfc6570]: https://tools.ietf.org/html/rfc6570
[signed-name-assertions]: https://github.com/opencontainers/image-spec/issues/176
[uritemplate]: https://pypi.python.org/pypi/uritemplate
14 changes: 7 additions & 7 deletions oci_discovery/ref_engine/oci_index_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
import logging as _logging
import pprint as _pprint

try:
import uritemplate as _uritemplate
except ImportError as error:
from .. import uri_template as _uritemplate

from .. import fetch_json as _fetch_json
from .. import host_based_image_names as _host_based_image_names

Expand All @@ -30,16 +35,11 @@ def __str__(self):
self.uri_template)

def __init__(self, uri):
self.uri_template = uri
self.uri_template = _uritemplate.URITemplate(uri=uri)

def resolve(self, name):
name_parts = _host_based_image_names.parse(name=name)
try:
uri = self.uri_template.format(**name_parts)
except KeyError as error:
raise ValueError(
'failed to format {}'.format(self.uri_template)
) from error
uri = self.uri_template.expand(**name_parts)
_LOGGER.debug('fetching an OCI index for {} from {}'.format(name, uri))
index = _fetch_json.fetch(
uri=uri,
Expand Down
34 changes: 34 additions & 0 deletions oci_discovery/uri_template/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2017 oci-discovery contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

class URITemplate(object):
"""Stub implementation of uritemplate's URITemplate.

https://pypi.python.org/pypi/uritemplate
"""
def __init__(self, uri):
self.uri = uri

def __str__(self):
return self.uri

def expand(self, **kwargs):
# Basic URI Templates match Python's str.format() syntax, just
# try that.
try:
return self.uri.format(**kwargs)
except KeyError as error:
raise ValueError(
'failed to format {}'.format(self.uri)
) from error
294 changes: 294 additions & 0 deletions oci_discovery/uri_template/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
# Copyright 2017 oci-discovery contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import unittest

try:
import uritemplate
except ImportError:
uritemplate = None


from . import URITemplate


class TestURITemplate(unittest.TestCase):
def _run(self, cls, exceptions=(), wrong_values=()):
"""Test against examples from RFC 6570.

https://tools.ietf.org/html/rfc6570
"""
# Defined in https://tools.ietf.org/html/rfc6570#section-3.2
# Other sections have their own definitions, but they're
# subsets of this set.
variables = {
'count': ('one', 'two', 'three'),
'dom': ('example', 'com'),
'dub': 'me/too',
'hello': 'Hello World!',
'half': '50%',
'var': 'value',
'who': 'fred',
'base': 'http://example.com/home/',
'path': '/foo/bar',
'list': ('red', 'green', 'blue'),
'keys': collections.OrderedDict((
('semi', ';'),
('dot', '.'),
('comma', ','),
)),
'v': '6',
'x': '1024',
'y': '768',
'empty': '',
'empty_keys': {},
#'undef': None.
}
for name, checks in (
(
'section 1.2, level 1',
(
('{var}', 'value'),
('{hello}', 'Hello%20World%21'),
),
),
(
'section 1.2, level 2',
(
('{+var}', 'value'),
('{+hello}', 'Hello%20World!'),
('{+path}/here', '/foo/bar/here'),
('here?ref={+path}', 'here?ref=/foo/bar'),
('X#{var}', 'X#value'),
('X#{hello}', 'X#Hello%20World!'),
),
),
(
'section 1.2, level 3',
(
('map?{x,y}', 'map?1024,768'),
('{x,hello,y}', '1024,Hello%20World%21,768'),
('{+x,hello,y}', '1024,Hello%20World!,768'),
('{+path,x}/here', '/foo/bar,1024/here'),
('{#x,hello,y}', '#1024,Hello%20World!,768'),
('{#path,x}/here', '#/foo/bar,1024/here'),
('X{.var}', 'X.value'),
('X{.x,y}', 'X.1024.768'),
('{/var}', '/value'),
('{/var,x}/here', '/value/1024/here'),
('{;x,y}', ';x=1024;y=768'),
('{;x,y,empty}', ';x=1024;y=768;empty'),
('{?x,y}', '?x=1024&y=768'),
('{?x,y,empty}', '?x=1024&y=768&empty='),
('?fixed=yes{&x}', '?fixed=yes&x=1024'),
('{&x,y,empty}', '&x=1024&y=768&empty='),
),
),
(
'section 1.2, level 4',
(
('{var:3}', 'val'),
('{var:30}', 'value'),
('{list}', 'red,green,blue'),
('{list*}', 'red,green,blue'),
('{keys}', 'semi,%3B,dot,.,comma,%2C'),
('{keys*}', 'semi=%3B,dot=.,comma=%2C'),
('{+path:6}/here', '/foo/b/here'),
('{+list}', 'red,green,blue'),
('{+list*}', 'red,green,blue'),
('{+keys}', 'semi,;,dot,.,comma,,'),
('{+keys*}', 'semi=;,dot=.,comma=,'),
('{#path:6}/here', '#/foo/b/here'),
('{#list}', '#red,green,blue'),
('{#list*}', '#red,green,blue'),
('{#keys}', '#semi,;,dot,.,comma,,'),
('{#keys*}', '#semi=;,dot=.,comma=,'),
('X{.var:3}', 'X.val'),
('X{.list}', 'X.red,green,blue'),
('X{.list*}', 'X.red.green.blue'),
('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'),
('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'),
('{/var:1,var}', '/v/value'),
('{/list}', '/red,green,blue'),
('{/list*}', '/red/green/blue'),
('{/list*,path:4}', '/red/green/blue/%2Ffoo'),
('{/keys}', '/semi,%3B,dot,.,comma,%2C'),
('{/keys*}', '/semi=%3B/dot=./comma=%2C'),
('{;hello:5}', ';hello=Hello'),
('{;list}', ';list=red,green,blue'),
('{;list*}', ';list=red;list=green;list=blue'),
('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'),
('{;keys*}', ';semi=%3B;dot=.;comma=%2C'),
('{?var:3}', '?var=val'),
('{?list}', '?list=red,green,blue'),
('{?list*}', '?list=red&list=green&list=blue'),
('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'),
('{?keys*}', '?semi=%3B&dot=.&comma=%2C'),
('{&var:3}', '&var=val'),
('{&list}', '&list=red,green,blue'),
('{&list*}', '&list=red&list=green&list=blue'),
('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'),
('{&keys*}', '&semi=%3B&dot=.&comma=%2C'),
),
),
(
'section 3.2.2',
(
('{var}', 'value'),
('{hello}', 'Hello%20World%21'),
('{half}', '50%25'),
('O{empty}X', 'OX'),
('O{undef}X', 'OX'),
('{x,y}', '1024,768'),
('{x,hello,y}', '1024,Hello%20World%21,768'),
('?{x,empty}', '?1024,'),
('?{x,undef}', '?1024'),
('?{undef,y}', '?768'),
('{var:3}', 'val'),
('{var:30}', 'value'),
('{list}', 'red,green,blue'),
('{list*}', 'red,green,blue'),
('{keys}', 'semi,%3B,dot,.,comma,%2C'),
('{keys*}', 'semi=%3B,dot=.,comma=%2C'),
),
),
):
with self.subTest(name=name):
for template, expected in checks:
with self.subTest(template=template):
if template in exceptions and template in wrong_values:
self.fail(
msg="entries in both 'exceptions' and 'wrong_values'. Pick one.")
obj = cls(uri=template)
try:
expanded = obj.expand(**variables)
except Exception as error:
if template in exceptions:
self.skipTest(
reason='expected failure: raised {}'
.format(error))
raise
if template in exceptions:
self.fail(
msg='expected a failure, but this no longer raises an exception')
if template in wrong_values:
self.assertNotEqual(expanded, expected)
else:
self.assertEqual(expanded, expected)

def test_stub(self):
self._run(
cls=URITemplate,
exceptions={
'?fixed=yes{&x}',
'?{undef,y}',
'?{x,empty}',
'?{x,undef}',
'O{undef}X',
'X{.keys*}',
'X{.keys}',
'X{.list*}',
'X{.list}',
'X{.var:3}',
'X{.var}',
'X{.x,y}',
'here?ref={+path}',
'map?{x,y}',
'{#keys*}',
'{#keys}',
'{#list*}',
'{#list}',
'{#path,x}/here',
'{#path:6}/here',
'{#x,hello,y}',
'{&keys*}',
'{&keys}',
'{&list*}',
'{&list}',
'{&var:3}',
'{&x,y,empty}',
'{+hello}',
'{+keys*}',
'{+keys}',
'{+list*}',
'{+list}',
'{+path,x}/here',
'{+path:6}/here',
'{+path}/here',
'{+var}',
'{+x,hello,y}',
'{/keys*}',
'{/keys}',
'{/list*,path:4}',
'{/list*}',
'{/list}',
'{/var,x}/here',
'{/var:1,var}',
'{/var}',
'{;hello:5}',
'{;keys*}',
'{;keys}',
'{;list*}',
'{;list}',
'{;x,y,empty}',
'{;x,y}',
'{?keys*}',
'{?keys}',
'{?list*}',
'{?list}',
'{?var:3}',
'{?x,y,empty}',
'{?x,y}',
'{keys*}',
'{list*}',
'{undef,y}',
'{x,hello,y}',
'{x,y}',
},
wrong_values={
'X#{hello}',
'{half}',
'{hello}',
'{keys}',
'{list}',
'{var:30}',
'{var:3}',
},
)

@unittest.skipIf(uritemplate is None, 'failed to import uritemplate')
def test_external(self):
self._run(
cls=uritemplate.URITemplate,
wrong_values={
'X#{hello}',
'X{.keys*}',
'X{.keys}',
'{#keys*}',
'{#keys}',
'{&keys*}',
'{&keys}',
'{+keys*}',
'{+keys}',
'{/keys*}',
'{/keys}',
'{;keys*}',
'{;keys}',
'{?keys*}',
'{?keys}',
'{keys*}',
'{keys}',
},
)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uritemplate>=3.0