Skip to content

Instantly share code, notes, and snippets.

@deskoh
Last active June 21, 2022 10:33
Show Gist options
  • Save deskoh/b520a8743d99ed370715c363263cc477 to your computer and use it in GitHub Desktop.
Save deskoh/b520a8743d99ed370715c363263cc477 to your computer and use it in GitHub Desktop.
Docker Registry V2 API
const axios = require('axios')
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
if (process.argv.length < 3) {
console.info(`Usage: node delete-image <image_name>`)
process.exit(1)
}
const imageUrl = new URL(`http://${process.argv[2]}`)
const [IMAGE_NAME, IMAGE_TAG = 'latest'] = imageUrl.pathname.slice(1).split(':')
const getAuthenticateInfo = async (origin) => {
try {
await axios({ method: 'GET', url: `${origin}/v2/` });
} catch (error) {
const { headers: { 'www-authenticate': auth } } = error.response;
const [realm, service] = auth.substring(7).split(',').map(s => s.split('=')[1].replace(/"/g, ''));
return [realm, service];
}
}
const getToken = async(username, password, image) => {
const [tokenEndpoint, service] = await getAuthenticateInfo(imageUrl.origin);
const url = `${tokenEndpoint}?service=${service}&client_id=cli&scope=repository:${image}:*`
console.debug(url)
const token = Buffer.from(`${username}:${password}`).toString('base64')
const resp = await axios({
method: 'GET',
url,
headers: username && {
Authorization: `Basic ${token}`,
},
})
return resp.data.token
}
const getImageManifest = async (token, image, tag) => {
const url = `${imageUrl.origin}/v2/${image}/manifests/${tag}`
const resp = await axios({
method: 'GET',
url,
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.docker.distribution.manifest.v2+json',
},
})
const { headers, data } = resp
return {
digest: headers['docker-content-digest'],
blobs: data.layers.map(l => l.digest),
}
}
const deleteImage = async (token, image, digest) => {
const url = `${imageUrl.origin}/v2/${image}/manifests/${digest}`
console.debug('DELETE: ', url)
try {
const resp = await axios({
method: 'DELETE',
url,
headers: {
Authorization: `Bearer ${token}`,
},
})
return resp.status === 202
} catch (error) {
console.error(`Image ${image}:${digest} cannot be deleted: ${error.message}`)
return false
}
}
const deleteBlob = async (token, image, digest) => {
const url = `${imageUrl.origin}/v2/${image}/blobs/${digest}`
console.debug('DELETE: ', url)
try {
const resp = await axios({
method: 'DELETE',
url,
headers: {
Authorization: `Bearer ${token}`,
},
})
return resp.status === 202
} catch (error) {
console.error(`Blob ${image}:${digest} cannot be deleted: ${error.message}`)
return false
}
}
const main = async () => {
console.log('Getting token')
const token = await getToken(process.env.USER, process.env.PASSWORD, IMAGE_NAME)
console.log(`Getting image manifest: ${IMAGE_NAME}:${IMAGE_TAG}`)
const manifest = await getImageManifest(token, IMAGE_NAME, IMAGE_TAG)
console.log(`Deleting image: ${IMAGE_NAME}:${IMAGE_TAG} (${manifest.digest})`)
if (!await deleteImage(token, IMAGE_NAME, manifest.digest)) {
process.exit(1)
}
console.log(`Deleting blobs for: ${IMAGE_NAME}:${IMAGE_TAG}`)
for (let blob of manifest.blobs) {
if (await deleteBlob(token, IMAGE_NAME, blob)){
console.log(`Deleted blob ${blob}`)
}
}
}
main().catch(e => console.log(e.message))
const { getManifest, deleteImage } = require('@snyk/docker-registry-v2-client')
// For insecure registry replace `https` with `http` in:
// node_modules\@snyk\docker-registry-v2-client\dist\registry-call.js
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
if (process.argv.length < 3) {
console.info(`Usage: node delete-image <image_name>`)
process.exit(1)
}
const USERNAME = process.env.USERNAME
const PASSWORD = process.env.PASSWORD
const url = new URL(`http://${process.argv[2]}`)
const CLUSTER_BASE_URL = url.hostname
const [IMAGE_NAME, IMAGE_TAG = 'latest'] = url.pathname.slice(1).split(':')
const getToken = async(username, password, image) => {
const url = `${CLUSTER_BASE_URL}:5000/image-manager/api/v1/auth/token?service=token-service&client_id=cli&scope=repository:${image}:*`
const token = Buffer.from(`${username}:${password}`).toString('base64')
const resp = await axios({
method: 'GET',
url,
headers: {
Authorization: `Basic ${token}`,
},
})
return resp.data.token
}
const getImageManifest = async (token, image, tag) => {
const url = `${CLUSTER_BASE_URL}:5000/v2/${image}/manifests/${tag}`
const resp = await axios({
method: 'GET',
url,
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.docker.distribution.manifest.v2+json',
},
})
const { headers, data } = resp
return {
digest: headers['docker-content-digest'],
blobs: data.layers.map(l => l.digest),
}
}
const deleteImage = async (token, image, digest) => {
const url = `${CLUSTER_BASE_URL}:5000/v2/${image}/manifests/${digest}`
try {
const resp = await axios({
method: 'DELETE',
url,
headers: {
Authorization: `Bearer ${token}`,
},
})
return resp.status === 202
} catch (error) {
console.error(`Image ${image}:${digest} cannot be deleted: ${error.message}`)
return false
}
}
const deleteBlob = async (token, image, digest) => {
const url = `${CLUSTER_BASE_URL}:5000/v2/${image}/blobs/${digest}`
try {
const resp = await axios({
method: 'DELETE',
url,
headers: {
Authorization: `Bearer ${token}`,
},
})
return resp.status === 202
} catch (error) {
console.error(`Blob ${image}:${digest} cannot be deleted: ${error.message}`)
return false
}
}
const main = async () => {
// console.log('Getting token')
// const token = await getToken('admin', 'P@ssw0rd!', IMAGE_NAME)
console.log(CLUSTER_BASE_URL, IMAGE_NAME, IMAGE_TAG, USERNAME, PASSWORD)
console.log(`Getting image manifest: ${IMAGE_NAME}:${IMAGE_TAG}`)
const { config, layers } = await getManifest(CLUSTER_BASE_URL, IMAGE_NAME, IMAGE_TAG, USERNAME, PASSWORD, {
})
console.log(manifest)
console.log(`Deleting image: ${IMAGE_NAME}:${IMAGE_TAG} (${config.digest})`)
if (!await deleteImage(token, IMAGE_NAME, manifest.digest)) {
process.exit(1)
}
console.log(`Deleting blobs for: ${IMAGE_NAME}:${IMAGE_TAG}`)
for (let blob of manifest.blobs) {
if (await deleteBlob(token, IMAGE_NAME, blob)){
console.log(`Deleted blob ${blob}`)
}
}
}
main().catch(e => console.log(e.message))
const {
getTags,
getManifest,
getImageConfig,
getAuthTokenForEndpoint,
} = require('@snyk/docker-registry-v2-client');
const USER = process.env.USER;
const PASSWORD = process.env.PASSWORD;
const REGISTRY = 'index.docker.io';
const image = [REGISTRY, 'redhat/ubi8-minimal', USER, PASSWORD];
(async () => {
const token = await getAuthTokenForEndpoint(REGISTRY, '/redhat/ubi8-minimal/manifests/latest');
console.log(token);
const tags = await getTags(...image);
console.log(tags);
// Image digest is in headers['docker-content-digest']
const manifest = await getManifest(REGISTRY, 'redhat/ubi8-minimal', tags[0]);
console.log(manifest.config.digest)
const imageConfig = await getImageConfig(...image, manifest.config.digest);
console.log(imageConfig);
})().catch(e => console.error(e));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment