mirror of
https://github.com/lilydjwg/nvchecker
synced 2025-01-12 01:09:25 +00:00
container source plugin supports watching update of a specified tag (#243)
container source plugin supports watching update of a specified tag. Resolve #241
This commit is contained in:
parent
0ba8cd41de
commit
4833135f87
@ -839,7 +839,9 @@ Check container registry
|
||||
This enables you to check tags of images on a container registry like Docker.
|
||||
|
||||
container
|
||||
The path for the container image. For official Docker images, use namespace ``library/`` (e.g. ``library/python``).
|
||||
The path (and tag) for the container image. For official Docker images, use namespace ``library/`` (e.g. ``library/python``).
|
||||
|
||||
If no tag is given, it checks latest available tag (sort by tag name), otherwise, it checks the tag's update time.
|
||||
|
||||
registry
|
||||
The container registry host. Default: ``docker.io``
|
||||
@ -850,17 +852,23 @@ container name while this plugin requires the full name. If the host part is
|
||||
omitted, use ``docker.io``, and if there is no slash in the path, prepend
|
||||
``library/`` to the path. Here are some examples:
|
||||
|
||||
+----------------------------------------------+-----------+--------------------------+
|
||||
| Pull command | registry | container |
|
||||
+==============================================+===========+==========================+
|
||||
| docker pull quay.io/prometheus/node-exporter | quay.io | prometheus/node-exporter |
|
||||
+----------------------------------------------+-----------+--------------------------+
|
||||
| docker pull nvidia/cuda | docker.io | nvidia/cuda |
|
||||
+----------------------------------------------+-----------+--------------------------+
|
||||
| docker pull python | docker.io | library/python |
|
||||
+----------------------------------------------+-----------+--------------------------+
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
| Pull command | registry | container |
|
||||
+=====================================================+===========+=================================+
|
||||
| docker pull quay.io/prometheus/node-exporter | quay.io | prometheus/node-exporter |
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
| docker pull quay.io/prometheus/node-exporter:master | quay.io | prometheus/node-exporter:master |
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
| docker pull openeuler/openeuler | docker.io | openeuler/openeuler |
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
| docker pull openeuler/openeuler:20.03-lts | docker.io | openeuler/openeuler:20.03-lts |
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
| docker pull python | docker.io | library/python |
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
| docker pull python:3.11 | docker.io | library/python:3.11 |
|
||||
+-----------------------------------------------------+-----------+---------------------------------+
|
||||
|
||||
This source returns tags and supports :ref:`list options`.
|
||||
If no tag is given, this source returns tags and supports :ref:`list options`.
|
||||
|
||||
Check ALPM database
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -4,6 +4,7 @@
|
||||
from typing import Dict, List, NamedTuple, Optional, Tuple
|
||||
from urllib.request import parse_http_list
|
||||
from urllib.parse import urljoin
|
||||
import json
|
||||
|
||||
from nvchecker.api import session, HTTPError
|
||||
|
||||
@ -57,15 +58,7 @@ async def get_registry_auth_info(registry_host: str) -> AuthInfo:
|
||||
|
||||
async def get_container_tags(info: Tuple[str, str, AuthInfo]) -> List[str]:
|
||||
image_path, registry_host, auth_info = info
|
||||
|
||||
auth_params = {
|
||||
'scope': f'repository:{image_path}:pull',
|
||||
}
|
||||
if auth_info.service:
|
||||
auth_params['service'] = auth_info.service
|
||||
res = await session.get(auth_info.realm, params=auth_params)
|
||||
token = res.json()['token']
|
||||
|
||||
token = await get_auth_token(auth_info, image_path)
|
||||
tags = []
|
||||
url = f'https://{registry_host}/v2/{image_path}/tags/list'
|
||||
|
||||
@ -83,6 +76,18 @@ async def get_container_tags(info: Tuple[str, str, AuthInfo]) -> List[str]:
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
async def get_auth_token(auth_info, image_path):
|
||||
auth_params = {
|
||||
'scope': f'repository:{image_path}:pull',
|
||||
}
|
||||
if auth_info.service:
|
||||
auth_params['service'] = auth_info.service
|
||||
res = await session.get(auth_info.realm, params=auth_params)
|
||||
token = res.json()['token']
|
||||
return token
|
||||
|
||||
|
||||
def parse_next_link(value: str) -> str:
|
||||
ending = '>; rel="next"'
|
||||
if value.endswith(ending):
|
||||
@ -90,13 +95,54 @@ def parse_next_link(value: str) -> str:
|
||||
else:
|
||||
raise ValueError(value)
|
||||
|
||||
|
||||
async def get_container_tag_update_time(info: Tuple[str, str, str, AuthInfo]):
|
||||
'''
|
||||
Find the update time of a container tag.
|
||||
|
||||
In fact, it's the creation time of the image ID referred by the tag. Tag itself does not have any update time.
|
||||
'''
|
||||
image_path, image_tag, registry_host, auth_info = info
|
||||
token = await get_auth_token(auth_info, image_path)
|
||||
|
||||
# HTTP headers
|
||||
headers = {
|
||||
'Authorization': f'Bearer {token}',
|
||||
# Prefer Image Manifest Version 2, Schema 2: https://distribution.github.io/distribution/spec/manifest-v2-2/
|
||||
'Accept': 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.container.image.v1+json, application/json',
|
||||
}
|
||||
|
||||
# Get tag manifest
|
||||
url = f'https://{registry_host}/v2/{image_path}/manifests/{image_tag}'
|
||||
res = await session.get(url, headers=headers)
|
||||
data = res.json()
|
||||
# Schema 1 returns the creation time in the response
|
||||
if data['schemaVersion'] == 1:
|
||||
return json.loads(data['history'][0]['v1Compatibility'])['created']
|
||||
|
||||
# For schema 2, we have to fetch the config's blob
|
||||
digest = data['config']['digest']
|
||||
url = f'https://{registry_host}/v2/{image_path}/blobs/{digest}'
|
||||
res = await session.get(url, headers=headers)
|
||||
data = res.json()
|
||||
return data['created']
|
||||
|
||||
|
||||
async def get_version(name, conf, *, cache, **kwargs):
|
||||
image_path = conf.get('container', name)
|
||||
image_tag = None
|
||||
# image tag is optional
|
||||
if ':' in image_path:
|
||||
image_path, image_tag = image_path.split(':', 1)
|
||||
registry_host = conf.get('registry', 'docker.io')
|
||||
if registry_host == 'docker.io':
|
||||
registry_host = 'registry-1.docker.io'
|
||||
|
||||
auth_info = await cache.get(registry_host, get_registry_auth_info)
|
||||
|
||||
# if a tag is given, return the tag's update time, otherwise return the image's tag list
|
||||
if image_tag:
|
||||
key = image_path, image_tag, registry_host, auth_info
|
||||
return await cache.get(key, get_container_tag_update_time)
|
||||
key = image_path, registry_host, auth_info
|
||||
return await cache.get(key, get_container_tags)
|
||||
|
@ -2,6 +2,7 @@
|
||||
# Copyright (c) 2020 Chih-Hsuan Yen <yan12125 at gmail dot com>
|
||||
|
||||
import pytest
|
||||
import datetime
|
||||
pytestmark = [pytest.mark.asyncio, pytest.mark.needs_net]
|
||||
|
||||
async def test_container(get_version):
|
||||
@ -11,6 +12,23 @@ async def test_container(get_version):
|
||||
"include_regex": "linux",
|
||||
}) == "linux"
|
||||
|
||||
async def test_container_with_tag(get_version):
|
||||
update_time = await get_version("hello-world:linux", {
|
||||
"source": "container",
|
||||
"container": "library/hello-world:linux",
|
||||
})
|
||||
# the update time is changing occasionally, so we can not compare the exact time, otherwise the test will be failed in the future
|
||||
assert datetime.date.fromisoformat(update_time.split('T')[0]) > datetime.date(2023, 1, 1)
|
||||
|
||||
async def test_container_with_tag_and_registry(get_version):
|
||||
update_time = await get_version("hello-world-nginx:v1.0", {
|
||||
"source": "container",
|
||||
"registry": "quay.io",
|
||||
"container": "redhattraining/hello-world-nginx:v1.0",
|
||||
})
|
||||
# the update time probably won't be changed
|
||||
assert datetime.date.fromisoformat(update_time.split('T')[0]) == datetime.date(2019, 6, 26)
|
||||
|
||||
async def test_container_paging(get_version):
|
||||
assert await get_version("prometheus-operator", {
|
||||
"source": "container",
|
||||
|
Loading…
Reference in New Issue
Block a user