From 8d4c98a481d924ca99d6b7383e662481291ed3de Mon Sep 17 00:00:00 2001 From: James Brumond Date: Fri, 18 Aug 2023 20:10:28 -0700 Subject: [PATCH] first commit --- action.yaml | 18 ++++++++ main.js | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 25 +++++++++++ 3 files changed, 159 insertions(+) create mode 100644 action.yaml create mode 100644 main.js create mode 100644 readme.md diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..f33540c --- /dev/null +++ b/action.yaml @@ -0,0 +1,18 @@ +name: Docker Tag +description: Tags an existing container manifest in a remote registry without pulling/pushing any images +runs: + using: node16 + main: main.js +inputs: + registry: + description: URL to the container registry + required: true + image: + description: The name of the image in the registry + required: true + old-tag: + description: The tag of the existing image to be re-tagged + required: true + new-tags: + description: Newline delimited list of new tags to apply to the image + required: true diff --git a/main.js b/main.js new file mode 100644 index 0000000..87025cc --- /dev/null +++ b/main.js @@ -0,0 +1,116 @@ + +const core = require('@actions/core'); +const http = require('http'); +const https = require('https'); + +main(); + +const manifest_media_type = 'application/vnd.docker.distribution.manifest.v2+json'; + +async function main() { + try { + const input = { + registry: core.getInput('registry'), + image: core.getInput('image'), + old_tag: core.getInput('old-tag'), + new_tags: core.getInput('new-tags').trim().split('\n'), + }; + + const manifest = await get_manifest(`${input.registry}/v2/${input.image}/manifests/${input.old_tag}`); + const promises = input.new_tags.map((new_tag) => { + return put_manifest(`${input.registry}/v2/${input.image}/manifests/${new_tag}`, manifest); + }); + + await Promise.all(promises); + } + + catch (error) { + core.setFailed(error.message); + } +} + +async function get_manifest(url_str) { + const { status, body: existing_manifest } = await http_req('GET', url_str, { + accept: manifest_media_type, + }); + + if (status !== 200) { + throw new Error('failed to fetch existing manifest'); + } + + return existing_manifest; +} + +async function put_manifest(url_str, manifest) { + const { status } = await http_req('PUT', url_str, { + 'content-type': manifest_media_type, + }, manifest); + + if (status >= 400) { + throw new Error('failed to put manifest'); + } +} + +async function http_req(method, url_str, headers, body) { + const url = new URL(url_str); + const make_request + = url.protocol === 'https:' ? https.request + : url.protocol === 'http:' ? http.request + : null; + + if (! make_request) { + throw new Error('registry URL protocol not http(s)'); + } + + if (url.username || url.password) { + throw new Error('urls containing user credentials not allowed'); + } + + const result = { + url, + status: null, + body: null, + headers: null, + }; + + const path = url.pathname + (url.search || ''); + const port = url.port ? parseInt(url.port, 10) : (url.protocol === 'https:' ? 443 : 80); + + return new Promise((resolve, reject) => { + const req = make_request({ + method: method, + protocol: url.protocol, + hostname: url.hostname, + port: port, + path: path, + headers: headers + }, (res) => { + result.status = res.statusCode; + result.body = ''; + + res.on('data', (chunk) => { + result.body += chunk; + }); + + res.on('end', () => { + result.headers = res.headers; + resolve(result); + }); + }); + + req.setTimeout(timeout, () => { + req.destroy(); + reject(new Error('request timeout')); + }); + + req.on('error', (error) => { + reject(error); + }); + + if (body) { + req.write(body); + } + + req.end(); + }); +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..6642e4e --- /dev/null +++ b/readme.md @@ -0,0 +1,25 @@ + +Action for re-tagging an existing docker multi-arch manifest + +## Inputs + + + +## Example usage + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Tag the "gitea.example.com/owner/package:latest" image with some new tags + uses: https://gitea.jbrumond.me/actions/docker-tag@v0.1 + with: + registry: https://gitea.example.com + image: owner/package + old-tag: latest + new-tags: | + tag1 + tag2 + tag3 +```