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 badge
pull/444/head
lhalbert 2020-10-27 22:41:13 -04:00 zatwierdzone przez GitHub
rodzic 2965d23f52
commit 4952a0f22f
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
2 zmienionych plików z 173 dodań i 2 usunięć

Wyświetl plik

@ -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'
}
}

89
libs/docker.ts 100644
Wyświetl plik

@ -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
}