mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-26 14:13:26 +00:00
test-oci-registry.sh: Tests for talking to an OCI registry
Add a new test case to test the OCI remote functionality. The tests talk to a server that implements good-enough index generation and bits of the docker registry protocol. Adding and remove remotes, summary and appstream generation, and image installation are all tested. Closes: #1910 Approved by: alexlarsson
This commit is contained in:
parent
4dfa7721bb
commit
6838206e2a
@ -100,6 +100,8 @@ dist_test_scripts = \
|
||||
tests/test-bundle.sh \
|
||||
tests/test-bundle-system.sh \
|
||||
tests/test-oci.sh \
|
||||
tests/test-oci-registry.sh \
|
||||
tests/test-oci-registry-system.sh \
|
||||
tests/test-unsigned-summaries.sh \
|
||||
tests/test-update-remote-configuration.sh \
|
||||
$(NULL)
|
||||
|
||||
38
tests/oci-registry-client.py
Normal file
38
tests/oci-registry-client.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/python2
|
||||
|
||||
import httplib
|
||||
import urllib
|
||||
import sys
|
||||
|
||||
if sys.argv[2] == 'add':
|
||||
detach_icons = '--detach-icons' in sys.argv
|
||||
if detach_icons:
|
||||
sys.argv.remove('--detach-icons')
|
||||
params = {'d': sys.argv[5]}
|
||||
if detach_icons:
|
||||
params['detach-icons'] = 1
|
||||
query = urllib.urlencode(params)
|
||||
conn = httplib.HTTPConnection(sys.argv[1])
|
||||
path = "/testing/{repo}/{tag}?{query}".format(repo=sys.argv[3],
|
||||
tag=sys.argv[4],
|
||||
query=query)
|
||||
conn.request("POST", path)
|
||||
response = conn.getresponse()
|
||||
if response.status != 200:
|
||||
print >>sys.stderr, response.read()
|
||||
print >>sys.stderr, "Failed: status={}".format(response.status)
|
||||
sys.exit(1)
|
||||
elif sys.argv[2] == 'delete':
|
||||
conn = httplib.HTTPConnection(sys.argv[1])
|
||||
path = "/testing/{repo}/{ref}".format(repo=sys.argv[3],
|
||||
ref=sys.argv[4])
|
||||
conn.request("DELETE", path)
|
||||
response = conn.getresponse()
|
||||
if response.status != 200:
|
||||
print >>sys.stderr, response.read()
|
||||
print >>sys.stderr, "Failed: status={}".format(response.status)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print >>sys.stderr, "Usage: oci-registry-client.py [add|remove] ARGS"
|
||||
sys.exit(1)
|
||||
|
||||
233
tests/oci-registry-server.py
Normal file
233
tests/oci-registry-server.py
Normal file
@ -0,0 +1,233 @@
|
||||
#!/usr/bin/python2
|
||||
|
||||
import BaseHTTPServer
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from urlparse import parse_qs
|
||||
import time
|
||||
|
||||
repositories = {}
|
||||
icons = {}
|
||||
|
||||
def get_index():
|
||||
results = []
|
||||
for repo_name in sorted(repositories.keys()):
|
||||
repo = repositories[repo_name]
|
||||
results.append({
|
||||
'Name': repo_name,
|
||||
'Images': repo['images'],
|
||||
'Lists': [],
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
'Registry': '/',
|
||||
'Results': results
|
||||
}, indent=4)
|
||||
|
||||
def cache_icon(data_uri):
|
||||
prefix = 'data:image/png;base64,'
|
||||
assert data_uri.startswith(prefix)
|
||||
data = base64.b64decode(data_uri[len(prefix):])
|
||||
h = hashlib.sha256()
|
||||
h.update(data)
|
||||
digest = h.hexdigest()
|
||||
filename = digest + '.png'
|
||||
icons[filename] = data
|
||||
|
||||
return '/icons/' + filename
|
||||
|
||||
serial = 0
|
||||
server_start_time = int(time.time())
|
||||
|
||||
def get_etag():
|
||||
return str(server_start_time) + '-' + str(serial)
|
||||
|
||||
def modified():
|
||||
global serial
|
||||
serial += 1
|
||||
|
||||
def parse_http_date(date):
|
||||
parsed = parsedate(date)
|
||||
if parsed is not None:
|
||||
return timegm(parsed)
|
||||
else:
|
||||
return None
|
||||
|
||||
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def check_route(self, route):
|
||||
parts = self.path.split('?', 1)
|
||||
path = parts[0].split('/')
|
||||
|
||||
result = []
|
||||
|
||||
route_path = route.split('/')
|
||||
print(route_path, path)
|
||||
if len(route_path) != len(path):
|
||||
return False
|
||||
|
||||
matches = {}
|
||||
for i in range(1, len(route_path)):
|
||||
if route_path[i][0] == '@':
|
||||
matches[route_path[i][1:]] = path[i]
|
||||
elif route_path[i] != path[i]:
|
||||
return False
|
||||
|
||||
self.matches = matches
|
||||
if len(parts) == 1:
|
||||
self.query = {}
|
||||
else:
|
||||
self.query = parse_qs(parts[1], keep_blank_values=True)
|
||||
|
||||
return True
|
||||
|
||||
def do_GET(self):
|
||||
response = 200
|
||||
response_string = None
|
||||
response_content_type = "application/octet-stream"
|
||||
response_file = None
|
||||
|
||||
add_headers = {}
|
||||
|
||||
if self.check_route('/v2/@repo_name/blobs/@digest'):
|
||||
repo_name = self.matches['repo_name']
|
||||
digest = self.matches['digest']
|
||||
response_file = repositories[repo_name]['blobs'][digest]
|
||||
elif self.check_route('/v2/@repo_name/manifests/@ref'):
|
||||
repo_name = self.matches['repo_name']
|
||||
ref = self.matches['ref']
|
||||
response_file = repositories[repo_name]['manifests'][ref]
|
||||
elif self.check_route('/index/static') or self.check_route('/index/dynamic'):
|
||||
etag = get_etag()
|
||||
if self.headers.get("If-None-Match") == etag:
|
||||
response = 304
|
||||
else:
|
||||
response_string = get_index()
|
||||
add_headers['Etag'] = etag
|
||||
elif self.check_route('/icons/@filename') :
|
||||
response_string = icons[self.matches['filename']]
|
||||
response_content_type = 'image/png'
|
||||
else:
|
||||
response = 404
|
||||
|
||||
self.send_response(response)
|
||||
for k, v in add_headers.items():
|
||||
self.send_header(k, v)
|
||||
|
||||
if response == 200:
|
||||
self.send_header("Content-Type", response_content_type)
|
||||
|
||||
if response == 200 or response == 304:
|
||||
self.send_header('Cache-Control', 'no-cache')
|
||||
|
||||
self.end_headers()
|
||||
|
||||
if response == 200:
|
||||
if response_file:
|
||||
with open(response_file) as f:
|
||||
response_string = f.read()
|
||||
self.wfile.write(response_string)
|
||||
|
||||
def do_POST(self):
|
||||
if self.check_route('/testing/@repo_name/@tag'):
|
||||
repo_name = self.matches['repo_name']
|
||||
tag = self.matches['tag']
|
||||
d = self.query['d'][0]
|
||||
detach_icons = 'detach-icons' in self.query
|
||||
|
||||
repo = repositories.setdefault(repo_name, {})
|
||||
blobs = repo.setdefault('blobs', {})
|
||||
manifests = repo.setdefault('manifests', {})
|
||||
images = repo.setdefault('images', [])
|
||||
|
||||
with open(os.path.join(d, 'index.json')) as f:
|
||||
index = json.load(f)
|
||||
|
||||
manifest_digest = index['manifests'][0]['digest']
|
||||
manifest_path = os.path.join(d, 'blobs', *manifest_digest.split(':'))
|
||||
manifests[manifest_digest] = manifest_path
|
||||
manifests[tag] = manifest_path
|
||||
|
||||
with open(manifest_path) as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
config_digest = manifest['config']['digest']
|
||||
config_path = os.path.join(d, 'blobs', *config_digest.split(':'))
|
||||
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
|
||||
for dig in os.listdir(os.path.join(d, 'blobs', 'sha256')):
|
||||
digest = 'sha256:' + dig
|
||||
path = os.path.join(d, 'blobs', 'sha256', dig)
|
||||
if digest != manifest_digest:
|
||||
blobs[digest] = path
|
||||
|
||||
if detach_icons:
|
||||
for size in (64, 128):
|
||||
annotation = 'org.freedesktop.appstream.icon-{}'.format(size)
|
||||
icon = manifest['annotations'].get(annotation)
|
||||
if icon:
|
||||
path = cache_icon(icon)
|
||||
manifest['annotations'][annotation] = path
|
||||
|
||||
image = {
|
||||
"Tags": [tag],
|
||||
"Digest": manifest_digest,
|
||||
"MediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"OS": config['os'],
|
||||
"Architecture": config['architecture'],
|
||||
"Annotations": manifest['annotations'],
|
||||
"Labels": {},
|
||||
}
|
||||
|
||||
images.append(image)
|
||||
|
||||
modified()
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
def do_DELETE(self):
|
||||
if self.check_route('/testing/@repo_name/@ref'):
|
||||
repo_name = self.matches['repo_name']
|
||||
ref = self.matches['ref']
|
||||
|
||||
repo = repositories.setdefault(repo_name, {})
|
||||
blobs = repo.setdefault('blobs', {})
|
||||
manifests = repo.setdefault('manifests', {})
|
||||
images = repo.setdefault('images', [])
|
||||
|
||||
image = None
|
||||
for i in images:
|
||||
if i['Digest'] == ref or ref in i['Tags']:
|
||||
image = i
|
||||
break
|
||||
|
||||
assert image
|
||||
|
||||
images.remove(image)
|
||||
del manifests[image['Digest']]
|
||||
for t in image['Tags']:
|
||||
del manifests[t]
|
||||
|
||||
modified()
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
def test():
|
||||
BaseHTTPServer.test(RequestHandler)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
22
tests/test-oci-registry-system.sh
Executable file
22
tests/test-oci-registry-system.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2018 Red Hat, Inc.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
export USE_SYSTEMDIR=yes
|
||||
|
||||
. $(dirname $0)/test-oci-registry.sh
|
||||
134
tests/test-oci-registry.sh
Executable file
134
tests/test-oci-registry.sh
Executable file
@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2018 Red Hat, Inc.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
. $(dirname $0)/libtest.sh
|
||||
|
||||
export FLATPAK_ENABLE_EXPERIMENTAL_OCI=1
|
||||
|
||||
skip_without_bwrap
|
||||
|
||||
echo "1..7"
|
||||
|
||||
# Start the fake registry server
|
||||
|
||||
$(dirname $0)/test-webserver.sh "" "python2 $test_srcdir/oci-registry-server.py 0"
|
||||
FLATPAK_HTTP_PID=$(cat httpd-pid)
|
||||
mv httpd-port httpd-port-main
|
||||
port=$(cat httpd-port-main)
|
||||
client="python2 $test_srcdir/oci-registry-client.py 127.0.0.1:$port"
|
||||
|
||||
setup_repo_no_add oci
|
||||
|
||||
# Add OCI bundles to it
|
||||
|
||||
${FLATPAK} build-bundle --runtime --oci $FL_GPGARGS repos/oci oci/platform-image org.test.Platform
|
||||
$client add platform latest $(pwd)/oci/platform-image
|
||||
|
||||
${FLATPAK} build-bundle --oci $FL_GPGARGS repos/oci oci/app-image org.test.Hello
|
||||
$client add hello latest $(pwd)/oci/app-image
|
||||
|
||||
# Add an OCI remote
|
||||
|
||||
flatpak remote-add ${U} --oci oci-registry "http://127.0.0.1:${port}"
|
||||
|
||||
# Check that the images we expect are listed
|
||||
|
||||
images=$(flatpak remote-ls ${U} oci-registry | sort | tr '\n' ' ' | sed 's/ $//')
|
||||
assert_streq "$images" "org.test.Hello org.test.Platform"
|
||||
echo "ok list remote"
|
||||
|
||||
# Pull appstream data
|
||||
|
||||
flatpak update ${U} --appstream oci-registry
|
||||
|
||||
# Check that the appstream and icons exist
|
||||
|
||||
if [ x${USE_SYSTEMDIR-} == xyes ] ; then
|
||||
appstream=$SYSTEMDIR/appstream/oci-registry/$ARCH/appstream.xml.gz
|
||||
icondir=$SYSTEMDIR/appstream/oci-registry/$ARCH/icons
|
||||
else
|
||||
appstream=$USERDIR/appstream/oci-registry/$ARCH/appstream.xml.gz
|
||||
icondir=$USERDIR/appstream/oci-registry/$ARCH/icons
|
||||
fi
|
||||
|
||||
gunzip -c $appstream > appstream-uncompressed
|
||||
assert_file_has_content appstream-uncompressed '<id>org.test.Hello.desktop</id>'
|
||||
assert_has_file $icondir/64x64/org.test.Hello.png
|
||||
|
||||
echo "ok appstream"
|
||||
|
||||
# Test that 'flatpak search' works
|
||||
flatpak search org.test.Hello > search-results
|
||||
assert_file_has_content search-results "Print a greeting"
|
||||
|
||||
echo "ok search"
|
||||
|
||||
# Replace with the app image with detached icons, check that the icons work
|
||||
|
||||
old_icon_hash=(md5sum $icondir/64x64/org.test.Hello.png)
|
||||
rm $icondir/64x64/org.test.Hello.png
|
||||
$client delete hello latest
|
||||
$client add --detach-icons hello latest $(pwd)/oci/app-image
|
||||
flatpak update ${U} --appstream oci-registry
|
||||
assert_has_file $icondir/64x64/org.test.Hello.png
|
||||
new_icon_hash=(md5sum $icondir/64x64/org.test.Hello.png)
|
||||
assert_streq $old_icon_hash $new_icon_hash
|
||||
|
||||
echo "ok detached icons"
|
||||
|
||||
# Try installing from the remote
|
||||
|
||||
${FLATPAK} ${U} install -y oci-registry org.test.Platform
|
||||
echo "ok install"
|
||||
|
||||
# Remove the app from the registry, check that things were removed properly
|
||||
|
||||
$client delete hello latest
|
||||
|
||||
images=$(flatpak remote-ls ${U} oci-registry | sort | tr '\n' ' ' | sed 's/ $//')
|
||||
assert_streq "$images" "org.test.Platform"
|
||||
|
||||
flatpak update ${U} --appstream oci-registry
|
||||
|
||||
assert_not_file_has_content $appstream '<id>org.test.Hello.desktop</id>'
|
||||
assert_not_has_file $icondir/64x64/org.test.Hello.png
|
||||
assert_not_has_file $icondir/64x64
|
||||
|
||||
echo "ok appstream change"
|
||||
|
||||
# Delete the remote, check that everything was removed
|
||||
|
||||
if [ x${USE_SYSTEMDIR-} == xyes ] ; then
|
||||
base=$SYSTEMDIR
|
||||
else
|
||||
base=$USERDIR
|
||||
fi
|
||||
|
||||
assert_has_file $base/oci/oci-registry.index.gz
|
||||
assert_has_file $base/oci/oci-registry.summary
|
||||
assert_has_dir $base/appstream/oci-registry
|
||||
flatpak ${U} -y uninstall org.test.Platform
|
||||
flatpak ${U} remote-delete oci-registry
|
||||
assert_not_has_file $base/oci/oci-registry.index.gz
|
||||
assert_not_has_file $base/oci/oci-registry.summary
|
||||
assert_not_has_dir $base/appstream/oci-registry
|
||||
|
||||
echo "ok delete remote"
|
||||
@ -27,12 +27,10 @@ skip_without_bwrap
|
||||
|
||||
echo "1..2"
|
||||
|
||||
setup_repo
|
||||
|
||||
${FLATPAK} ${U} install test-repo org.test.Platform master
|
||||
setup_repo_no_add oci
|
||||
|
||||
mkdir -p oci
|
||||
${FLATPAK} build-bundle --oci $FL_GPGARGS repos/test oci/image org.test.Hello
|
||||
${FLATPAK} build-bundle --oci $FL_GPGARGS repos/oci oci/image org.test.Hello
|
||||
|
||||
assert_has_file oci/image/oci-layout
|
||||
assert_has_dir oci/image/blobs/sha256
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user