kopia lustrzana https://github.com/badgen/badgen.net
feat(docker): Add image layers handler (#442)
* feat(docker): Add image layers handler * Remove unnecessary BadgenError import * Add metadataHandler and refactor code * Separate the layers and metadata handlers Separate the layers and metadata handlers and remove 'metadata' from the layers path. * revert subject/label to static descriptive text * Move manifest error checking logic into libs\docker.ts Move manifest error checking logic into libs\docker.ts to clean up api\docker.ts * Replace 'throw new BadenError' with grey error badge Replace 'throw new BadenError' with grey error badgepull/444/head
rodzic
2965d23f52
commit
4952a0f22f
|
@ -1,8 +1,23 @@
|
|||
import millify from 'millify'
|
||||
import got from '../libs/got'
|
||||
import { getDockerAuthToken, getManifestList, getImageManifest, getImageConfig } from '../libs/docker'
|
||||
import { createBadgenHandler, PathArgs } from '../libs/create-badgen-handler'
|
||||
|
||||
const help = `## Usage
|
||||
|
||||
- \`/docker/stars/:scope/:name\` _stars_
|
||||
- \`/docker/pulls/:scope/:name\` _pulls_
|
||||
- \`/docker/size/:scope/:name/:tag?/:architecture?/:variant?\` _size (scoped/tag/architecture/variant)_
|
||||
- \`/docker/layers/:scope/:name/:tag?/:architecture?/:variant?\` _layers (scoped/tag/architecture/variant)_
|
||||
- \`/docker/metadata/:type/:scope/:name/:tag?/:architecture?/:variant?\` _metadata (type/scoped/tag/architecture/variant)_
|
||||
|
||||
### Querying [Label-Schema](http://label-schema.org/) Metadata
|
||||
|
||||
Metadata associated with an image in the [label-schema](http://label-schema.org/) format can be queried using the metadata handler. For example, a label of \`org.label-schema.build-date=2020-10-26T14:25:14Z\` on the image \`lucashalbert/curl\` can be queried using the badgen URL of [/docker/metadata/build-date/lucashalbert/curl/](/docker/metadata/build-date/lucashalbert/curl/).
|
||||
`
|
||||
|
||||
export default createBadgenHandler({
|
||||
help,
|
||||
title: 'Docker',
|
||||
examples: {
|
||||
'/docker/pulls/library/ubuntu': 'pulls (library)',
|
||||
|
@ -12,10 +27,19 @@ export default createBadgenHandler({
|
|||
'/docker/stars/library/mongo?icon=docker&label=stars': 'stars (icon & label)',
|
||||
'/docker/size/lukechilds/bitcoind/latest/amd64': 'size (scoped/tag/architecture)',
|
||||
'/docker/size/lucashalbert/curl/latest/arm/v6': 'size (scoped/tag/architecture/variant)',
|
||||
'/docker/layers/lucashalbert/curl/latest/arm/v7': 'layers (size)',
|
||||
'/docker/layers/lucashalbert/curl/latest/arm/v7?icon=docker&label=layers': 'layers (icon & label)',
|
||||
'/docker/layers/lucashalbert/curl/latest/arm/v7?label=docker%20layers': 'layers (label)',
|
||||
'/docker/metadata/version/lucashalbert/curl/latest/arm64/v8': 'metadata (version)',
|
||||
'/docker/metadata/architecture/lucashalbert/curl/latest/arm64/v8': 'metadata (architecture)',
|
||||
'/docker/metadata/build-date/lucashalbert/curl/latest/arm64/v8': 'metadata (build-date)',
|
||||
'/docker/metadata/maintainer/lucashalbert/curl/latest/arm64/v8': 'metadata (maintainer)',
|
||||
},
|
||||
handlers: {
|
||||
'/docker/:topic<stars|pulls>/:scope/:name': starPullHandler,
|
||||
'/docker/size/:scope/:name/:tag?/:architecture?/:variant?': sizeHandler
|
||||
'/docker/size/:scope/:name/:tag?/:architecture?/:variant?': sizeHandler,
|
||||
'/docker/layers/:scope/:name/:tag?/:architecture?/:variant?': layersHandler,
|
||||
'/docker/metadata/:type/:scope/:name/:tag?/:architecture?/:variant?': metadataHandler,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -97,8 +121,66 @@ async function sizeHandler ({ scope, name, tag, architecture, variant }: PathArg
|
|||
const sizeInMegabytes = (imageData.size / 1024 / 1024).toFixed(2)
|
||||
|
||||
return {
|
||||
subject: 'docker image size',
|
||||
subject: 'docker size',
|
||||
status: `${sizeInMegabytes} MB`,
|
||||
color: 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
async function layersHandler ({ scope, name, tag, architecture, variant }: PathArgs) {
|
||||
tag = tag ? tag : 'latest'
|
||||
architecture = architecture ? architecture : 'amd64'
|
||||
variant = variant ? variant : ''
|
||||
|
||||
const token = (await getDockerAuthToken(scope, name)).token
|
||||
|
||||
const manifest_list = await getManifestList(scope, name, tag, architecture, variant, token)
|
||||
|
||||
const image_manifest = await getImageManifest(scope, name, manifest_list.digest, token)
|
||||
|
||||
const image_config = await getImageConfig(scope, name, image_manifest.config.digest, token)
|
||||
|
||||
const layers = image_config.history
|
||||
if (! layers) {
|
||||
return {
|
||||
subject: 'docker layers',
|
||||
status: 'error getting layers',
|
||||
color: 'grey'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subject: 'docker layers',
|
||||
status: `${layers.length}`,
|
||||
color: 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
async function metadataHandler ({ type, scope, name, tag, architecture, variant }: PathArgs) {
|
||||
tag = tag ? tag : 'latest'
|
||||
architecture = architecture ? architecture : 'amd64'
|
||||
variant = variant ? variant : ''
|
||||
|
||||
const token = (await getDockerAuthToken(scope, name)).token
|
||||
|
||||
const manifest_list = await getManifestList(scope, name, tag, architecture, variant, token)
|
||||
|
||||
const image_manifest = await getImageManifest(scope, name, manifest_list.digest, token)
|
||||
|
||||
const image_config = await getImageConfig(scope, name, image_manifest.config.digest, token)
|
||||
|
||||
const metadata = image_config.container_config.Labels[`org.label-schema.${type}`]
|
||||
if (! metadata) {
|
||||
return {
|
||||
subject: 'docker metadata',
|
||||
status: `error getting ${type}`,
|
||||
color: 'grey'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subject: `${type}`,
|
||||
status: `${metadata}`,
|
||||
color: 'blue'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import got from './got'
|
||||
import { BadgenError } from './create-badgen-handler'
|
||||
|
||||
// request image specific DockerHub pull token
|
||||
export function getDockerAuthToken<T = any>(scope: string, name: string) {
|
||||
const prefixUrl = process.env.DOCKER_AUTHENTICATION_API || 'https://auth.docker.io/'
|
||||
const service = 'registry.docker.io'
|
||||
const searchParams = {
|
||||
service: service,
|
||||
scope: `repository:${scope}/${name}:pull`
|
||||
}
|
||||
|
||||
const resp = got.get("token", { prefixUrl, searchParams }).json<T>()
|
||||
|
||||
if (! resp) {
|
||||
throw new BadgenError({ status: 'unknown image' })
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// query the docker registry api
|
||||
function queryDockerRegistry<T = any>(path: string, headers) {
|
||||
const prefixUrl = process.env.DOCKER_REGISTRY_API || 'https://registry.hub.docker.com/'
|
||||
return got.get(path, { prefixUrl, headers }).json<T>()
|
||||
}
|
||||
|
||||
// get fat manifest list and return
|
||||
export async function getManifestList<T = any>(scope: string, name: string, tag: string, architecture: string, variant: string, token: string) {
|
||||
const headers = {
|
||||
authorization: `Bearer ${token}`,
|
||||
accept: `application/vnd.docker.distribution.manifest.list.v2+json`
|
||||
}
|
||||
const path = `v2/${scope}/${name}/manifests/${tag}`
|
||||
const manifest_list = (await queryDockerRegistry(path, headers)).manifests
|
||||
|
||||
if (! manifest_list) {
|
||||
throw new BadgenError({ status: 'unknown tag' })
|
||||
}
|
||||
|
||||
let manifest = manifest_list.find(manifest_list => manifest_list.platform.architecture === architecture)
|
||||
|
||||
if (! manifest) {
|
||||
throw new BadgenError({ status: 'unknown architecture' })
|
||||
}
|
||||
|
||||
if (variant) {
|
||||
manifest = manifest_list.filter(manifest_list => manifest_list.platform.architecture === architecture).find(manifest_list => manifest_list.platform.variant === variant)
|
||||
|
||||
if (! manifest) {
|
||||
throw new BadgenError({ status: 'unknown variant' })
|
||||
}
|
||||
}
|
||||
|
||||
if (! manifest.digest) {
|
||||
throw new BadgenError({ status: 'image digest error' })
|
||||
}
|
||||
|
||||
return manifest
|
||||
}
|
||||
|
||||
export async function getImageManifest<T = any>(scope: string, name: string, digest: string, token: string) {
|
||||
const headers = {
|
||||
authorization: `Bearer ${token}`,
|
||||
accept: `application/vnd.docker.distribution.manifest.list.v2+json`
|
||||
}
|
||||
const path = `v2/${scope}/${name}/manifests/${digest}`
|
||||
const image_manifest = await queryDockerRegistry(path, headers)
|
||||
|
||||
if (! image_manifest) {
|
||||
throw new BadgenError({ status: 'image manifest error' })
|
||||
}
|
||||
|
||||
return image_manifest
|
||||
}
|
||||
|
||||
export async function getImageConfig<T = any>(scope: string, name: string, digest: string, token: string) {
|
||||
const headers = {
|
||||
authorization: `Bearer ${token}`,
|
||||
accept: `application/vnd.docker.image.config+json`
|
||||
}
|
||||
const path = `v2/${scope}/${name}/blobs/${digest}`
|
||||
const image_config = await queryDockerRegistry(path, headers)
|
||||
|
||||
if (! image_config) {
|
||||
throw new BadgenError({ status: 'image config error' })
|
||||
}
|
||||
return image_config
|
||||
}
|
Ładowanie…
Reference in New Issue