feat(route): add dockerhub image new tag (#8965)

* feat: /dockerhub/tags/:owner/:image

fix #6916

* fix: update the doc link of /dockerhub/build

* docs(en): /dockerhub/tag/:owner/:image

* docs: fix typo

* fix(dockerhub/tags): use parseDate()

* docs: link docs to h2 instead of h3

* chore: move /dockerhub/build to v2

* docs: put warning inside <Route>

* fix(dockerhub): use docker.com/hub as the radar path

* fix(dockerhub/tag): add :limits?

* feat(dockerhub): polish the GUID

* fix(dockerhub/tag): stricter argument checks

* fix(dockerhub/tag): handle NaN per Deepscan CI
This commit is contained in:
Outvi V
2022-02-03 00:09:01 +08:00
committed by GitHub
parent 6311e02182
commit 41a06a22bb
11 changed files with 103 additions and 28 deletions

View File

@@ -312,17 +312,6 @@
},
'wineyun.com': { _name: '酒云网', www: [{ title: '最新商品', docs: 'https://docs.rsshub.app/other.html#jiu-yun-wang', source: ['/:category'], target: '/wineyun/:category' }] },
'epicgames.com': { _name: 'Epic Games', www: [{ title: '每周免费游戏', docs: 'https://docs.rsshub.app/game.html#epicgames-freegame', source: '/store/zh-CN/free-games', target: '/epicgames/freegames' }] },
'docker.com': {
_name: 'Docker',
hub: [
{
title: '镜像有新 Build',
docs: 'https://docs.rsshub.app/program-update.html#docker-hub',
source: ['/r/:owner/:image', '/r/:owner/:image/tags', '/_/:image'],
target: (params) => `/dockerhub/build/${params.owner ? params.owner : 'library'}/${params.image}`,
},
],
},
'nga.cn': {
_name: 'NGA',
bbs: [

View File

@@ -112,7 +112,7 @@ For example: `https://www.curseforge.com/sc2/assets/taylor-mouses-stuff/files` t
### Image New Build
<RouteEn author="HenryQW" example="/dockerhub/build/wangqiru/ttrss" path="/dockerhub/build/:owner/:image/:tag?" :paramsDesc="['Image owner', 'Image name', 'Image tagdefault to latest']"/>
<RouteEn author="HenryQW" example="/dockerhub/build/wangqiru/ttrss" path="/dockerhub/build/:owner/:image/:tag?" :paramsDesc="['Image owner', 'Image name', 'Image tagdefault to latest']">
::: warning
@@ -120,6 +120,20 @@ The owner of the official image fills in the library, for example: https://rsshu
:::
</RouteEn>
### Image New Tag
<RouteEn author="outloudvi" example="/dockerhub/tag/library/mariadb" path="/dockerhub/tag/:owner/:image/:limits?" :paramsDesc="['Image owner', 'Image name', 'Tag count, 10 by default']">
::: warning
Use `library` as the `owner` for official images, such as <https://rsshub.app/dockerhub/tag/library/mysql>
:::
</RouteEn>
## Eagle
### Changelog

View File

@@ -150,7 +150,7 @@ pageClass: routes
### 镜像有新 Build
<Route author="HenryQW" example="/dockerhub/build/wangqiru/ttrss" path="/dockerhub/build/:owner/:image/:tag?" :paramsDesc="['镜像作者', '镜像名称', '镜像标签,默认 latest']" radar="1" rssbud="1"/>
<Route author="HenryQW" example="/dockerhub/build/wangqiru/ttrss" path="/dockerhub/build/:owner/:image/:tag?" :paramsDesc="['镜像作者', '镜像名称', '镜像标签,默认 latest']" radar="1" rssbud="1">
::: warning 注意
@@ -158,6 +158,20 @@ pageClass: routes
:::
</Route>
### 镜像有新 Tag
<Route author="outloudvi" example="/dockerhub/tag/library/mariadb" path="/dockerhub/tag/:owner/:image/:limits?" :paramsDesc="['镜像作者', '镜像名称', 'tag 数量,默认为 10']" radar="1" rssbud="1">
::: warning 注意
官方镜像的 owner 填写 library, 如: <https://rsshub.app/dockerhub/tag/library/mysql>
:::
</Route>
## Eagle
### 更新日志

View File

@@ -786,17 +786,6 @@ module.exports = {
},
],
},
'docker.com': {
_name: 'Docker',
hub: [
{
title: '镜像有新 Build',
docs: 'https://docs.rsshub.app/program-update.html#docker-hub',
source: ['/r/:owner/:image', '/r/:owner/:image/tags', '/_/:image'],
target: (params) => `/dockerhub/build/${params.owner ? params.owner : 'library'}/${params.image}`,
},
],
},
'nga.cn': {
_name: 'NGA',
bbs: [

View File

@@ -1376,9 +1376,6 @@ router.get('/meipai/user/:uid', lazyloadRouteHandler('./routes/meipai/user'));
// 多知网
router.get('/duozhi', lazyloadRouteHandler('./routes/duozhi'));
// Docker Hub
router.get('/dockerhub/build/:owner/:image/:tag?', lazyloadRouteHandler('./routes/dockerhub/build'));
// 人人都是产品经理
router.get('/woshipm/popular', lazyloadRouteHandler('./routes/woshipm/popular'));
router.get('/woshipm/wen', lazyloadRouteHandler('./routes/woshipm/wen'));

View File

@@ -1,4 +1,5 @@
const got = require('@/utils/got');
const { hash } = require('./utils');
module.exports = async (ctx) => {
const { owner, image, tag = 'latest' } = ctx.params;
@@ -22,7 +23,8 @@ module.exports = async (ctx) => {
link: `https://hub.docker.com/layers/docker/${namespace}/${tag}/images/${item.images[0].digest.replace(':', '-')}`,
author: owner,
pubDate: new Date(item.last_updated).toUTCString(),
guid: item.last_updated,
// only check for different images hashes (considering varients of all arches), since the tag name is already fixed
guid: hash(item.images),
},
],
};

View File

@@ -0,0 +1,4 @@
module.exports = {
'/build/:owner/:image/:tag?': ['HenryQW'],
'/tag/:owner/:image': ['outloudvi'],
};

19
lib/v2/dockerhub/radar.js Normal file
View File

@@ -0,0 +1,19 @@
module.exports = {
'docker.com': {
_name: 'Docker Hub',
hub: [
{
title: '镜像有新 Build',
docs: 'https://docs.rsshub.app/program-update.html#docker-hub',
source: ['/r/:owner/:image', '/r/:owner/:image/tags', '/_/:image'],
target: (params) => `/dockerhub/build/${params.owner ? params.owner : 'library'}/${params.image}`,
},
{
title: '镜像有新 Tag',
docs: 'https://docs.rsshub.app/program-update.html#docker-hub',
source: ['/r/:owner/:image', '/r/:owner/:image/tags', '/_/:image'],
target: (params) => `/dockerhub/tag/${params.owner ? params.owner : 'library'}/${params.image}`,
},
],
},
};

View File

@@ -0,0 +1,4 @@
module.exports = function (router) {
router.get('/build/:owner/:image/:tag?', require('./build'));
router.get('/tag/:owner/:image/:limits?', require('./tag'));
};

33
lib/v2/dockerhub/tag.js Normal file
View File

@@ -0,0 +1,33 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { hash } = require('./utils');
module.exports = async (ctx) => {
const { owner, image, limits } = ctx.params;
const namespace = `${owner}/${image}`;
const link = `https://hub.docker.com/r/${namespace}`;
const pageSize = !isNaN(parseInt(limits)) ? parseInt(limits) : 10;
const data = await got.get(`https://hub.docker.com/v2/repositories/${namespace}/tags/?page_size=${pageSize}`);
const metadata = await got.get(`https://hub.docker.com/v2/repositories/${namespace}/`);
const tags = data.data.results;
ctx.state.data = {
title: `${namespace} tags`,
description: metadata.data.description,
link,
language: 'en',
item: tags.map((item) => ({
title: `${namespace}:${item.name} was updated`,
description: `${namespace}:${item.name} was updated, supporting the architectures of ${item.images.map((img) => `${img.os}/${img.architecture}`).join(', ')}`,
link: `https://hub.docker.com/layers/${owner === 'library' ? `${image}/` : ''}${namespace}/${item.name}/images/${item.images[0].digest.replace(':', '-')}`,
author: owner,
pubDate: parseDate(item.tag_last_pushed),
// check for (1) different tag names and (2) different image hashes, considering varients of all arches
guid: `${namespace}:${item.name}@${hash(item.images)}`,
})),
};
};

10
lib/v2/dockerhub/utils.js Normal file
View File

@@ -0,0 +1,10 @@
const md5 = require('@/utils/md5');
function hash(images) {
const entries = Object.entries(images)
.map((x) => [`${x.os}/${x.architecture}`, x.digest])
.sort((a, b) => a[0] - b[0]);
return md5(entries.map((x) => x.join(',')).join('|'));
}
module.exports = { hash };