Last active
June 21, 2022 10:33
-
-
Save deskoh/b520a8743d99ed370715c363263cc477 to your computer and use it in GitHub Desktop.
Docker Registry V2 API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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