Compare commits
28 Commits
9d6cb8d126
...
v0.1
Author | SHA1 | Date | |
---|---|---|---|
144d8ae6a3
|
|||
176ce2b913
|
|||
3b98ba922b
|
|||
4ec5cfd721
|
|||
efaa2aa24d
|
|||
ac5141c79a
|
|||
09228f4f08
|
|||
a976260e5e
|
|||
46de33be5d
|
|||
fdcefbf5fa
|
|||
8ff608d867
|
|||
3697f3078d
|
|||
2b8c0ec844
|
|||
dc3b78f911
|
|||
acab3bc116
|
|||
06b2ca9b74
|
|||
f65905c9bd
|
|||
3c3c6ca421
|
|||
2a8da3e510
|
|||
9f2eb608c7
|
|||
db4149302a
|
|||
af98273b46
|
|||
c5b78bf494
|
|||
4d4170efa7
|
|||
24512ad125
|
|||
cd3d61058b
|
|||
93dd6aca70
|
|||
5a33737182
|
22
action.yaml
22
action.yaml
@@ -1,4 +1,4 @@
|
||||
name: Docker Tag
|
||||
name: Tag Manifest
|
||||
description: Tags an existing container manifest in a remote registry without pulling/pushing any images
|
||||
runs:
|
||||
using: node16
|
||||
@@ -7,12 +7,18 @@ inputs:
|
||||
registry:
|
||||
description: URL to the container registry
|
||||
required: true
|
||||
image:
|
||||
description: The name of the image in the registry
|
||||
insecure:
|
||||
description: If "true", will use HTTP rather than HTTPS
|
||||
required: false
|
||||
username:
|
||||
description: Username to use to authenticate to the container registry
|
||||
required: false
|
||||
password:
|
||||
description: Password to use to authenticate to the container registry
|
||||
required: false
|
||||
manifest:
|
||||
description: The name/tag of the existing manifest 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
|
||||
tags:
|
||||
description: Newline or comma delimited list of new tags to apply to the manifest
|
||||
required: true
|
||||
|
194
dist/index.js
vendored
194
dist/index.js
vendored
@@ -2855,8 +2855,11 @@ var __webpack_exports__ = {};
|
||||
const core = __nccwpck_require__(186);
|
||||
const http = __nccwpck_require__(685);
|
||||
const https = __nccwpck_require__(687);
|
||||
// const querystring = require('querystring');
|
||||
|
||||
const req_timeout_ms = 30_000;
|
||||
const manifest_media_type = 'application/vnd.docker.distribution.manifest.v2+json';
|
||||
const image_index_media_type = 'application/vnd.oci.image.index.v1+json';
|
||||
|
||||
main();
|
||||
|
||||
@@ -2864,16 +2867,22 @@ 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'),
|
||||
registry_url: null,
|
||||
insecure: core.getInput('insecure') === 'true',
|
||||
username: core.getInput('username'),
|
||||
password: core.getInput('password'),
|
||||
manifest: parse_manifest_tag(core.getInput('manifest')),
|
||||
tags: parse_tags(core.getInput('tags').trim()),
|
||||
token: null,
|
||||
};
|
||||
|
||||
console.log('Tagging %s:%s with new tags', input.image, input.old_tag, input);
|
||||
input.registry_url = `http${input.insecure ? '' : 's'}://${input.registry}`;
|
||||
console.log('Tagging %s/%s:%s with new tags', input.registry, input.manifest.image, input.manifest.tag, input.tags);
|
||||
|
||||
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);
|
||||
const manifest = await get_manifest(input);
|
||||
|
||||
const promises = input.tags.map((tag) => {
|
||||
return put_manifest(tag, manifest, input);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
@@ -2885,28 +2894,114 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
async function get_manifest(url_str) {
|
||||
const { status, body: existing_manifest } = await http_req('GET', url_str, {
|
||||
accept: manifest_media_type,
|
||||
});
|
||||
function parse_manifest_tag(full_tag) {
|
||||
const colon_index = full_tag.lastIndexOf(':');
|
||||
const image = full_tag.slice(0, colon_index);
|
||||
const tag = full_tag.slice(colon_index + 1);
|
||||
return { image, tag };
|
||||
}
|
||||
|
||||
function parse_tags(tags) {
|
||||
if (tags.indexOf('\n') < 0) {
|
||||
return tags.split(',').map((tag) => tag.trim());
|
||||
}
|
||||
|
||||
return tags.split('\n').map((tag) => tag.trim());
|
||||
}
|
||||
|
||||
async function get_manifest(input) {
|
||||
const path = `/v2/${input.manifest.image}/manifests/${input.manifest.tag}`;
|
||||
const req_headers = {
|
||||
accept: `${manifest_media_type}, ${image_index_media_type}`,
|
||||
};
|
||||
|
||||
http_basic_auth(req_headers, input);
|
||||
|
||||
let { status, headers, body } = await http_req('GET', input.registry_url + path, req_headers);
|
||||
|
||||
// if (status === 401 && headers['www-authenticate']) {
|
||||
// await get_token(headers['www-authenticate'], input);
|
||||
|
||||
// ({ status, headers, body } = await http_req('GET', input.registry_url + path, {
|
||||
// accept: manifest_media_type,
|
||||
// authorization: `Bearer ${input.token}`,
|
||||
// }));
|
||||
// }
|
||||
|
||||
if (status !== 200) {
|
||||
console.error('get manifest response', { status, headers, body });
|
||||
throw new Error('failed to fetch existing manifest');
|
||||
}
|
||||
|
||||
return existing_manifest;
|
||||
return {
|
||||
content: body,
|
||||
type: headers['content-type'],
|
||||
length: headers['content-length'],
|
||||
};
|
||||
}
|
||||
|
||||
async function put_manifest(url_str, manifest) {
|
||||
const { status } = await http_req('PUT', url_str, {
|
||||
'content-type': manifest_media_type,
|
||||
}, manifest);
|
||||
async function put_manifest(new_tag, manifest, input) {
|
||||
const path = `/v2/${input.manifest.image}/manifests/${new_tag}`;
|
||||
const req_headers = {
|
||||
'content-type': manifest.type,
|
||||
'content-length': manifest.length,
|
||||
};
|
||||
|
||||
http_basic_auth(req_headers, input);
|
||||
|
||||
// if (input.token) {
|
||||
// req_headers.authorization = `Bearer ${input.token}`;
|
||||
// }
|
||||
|
||||
const { status, headers, body } = await http_req('PUT', input.registry_url + path, req_headers, manifest.content);
|
||||
|
||||
if (status >= 400) {
|
||||
console.error('put manifest response', { status, headers, body });
|
||||
throw new Error('failed to put manifest');
|
||||
}
|
||||
}
|
||||
|
||||
function http_basic_auth(headers, input) {
|
||||
if (input.username) {
|
||||
const credentials = Buffer.from(`${input.username}:${input.password}`, 'utf8').toString('base64');
|
||||
headers.authorization = `Basic ${credentials}`;
|
||||
}
|
||||
}
|
||||
|
||||
// async function get_token(www_authenticate, input) {
|
||||
// const params = parse_www_authenticate(www_authenticate);
|
||||
|
||||
// if (! input.username || ! input.password) {
|
||||
// throw new Error('registry requested authentication, but not credentials were provided');
|
||||
// }
|
||||
|
||||
// const query_params = {
|
||||
// service: params.service,
|
||||
// scope: `repository:${input.manifest.image}:pull,push`,
|
||||
// grant_type: 'password',
|
||||
// username: input.username,
|
||||
// password: input.password,
|
||||
// };
|
||||
|
||||
// const { status, headers, body } = await http_req('GET', params.realm + '?' + querystring.stringify(query_params), {
|
||||
// 'content-type': 'application/x-www-form-urlencoded',
|
||||
// });
|
||||
|
||||
// if (status !== 200) {
|
||||
// console.error('get token response', { status, headers, body });
|
||||
// throw new Error('error response from get token request');
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const { token } = JSON.parse(body);
|
||||
// input.token = token;
|
||||
// }
|
||||
|
||||
// catch (error) {
|
||||
// throw new Error('invalid response from get token request');
|
||||
// }
|
||||
// }
|
||||
|
||||
async function http_req(method, url_str, headers, body) {
|
||||
const url = new URL(url_str);
|
||||
const make_request
|
||||
@@ -2932,7 +3027,7 @@ async function http_req(method, url_str, headers, body) {
|
||||
const path = url.pathname + (url.search || '');
|
||||
const port = url.port ? parseInt(url.port, 10) : (url.protocol === 'https:' ? 443 : 80);
|
||||
|
||||
return new Promise<HttpResult>((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = make_request({
|
||||
method: method,
|
||||
protocol: url.protocol,
|
||||
@@ -2954,7 +3049,7 @@ async function http_req(method, url_str, headers, body) {
|
||||
});
|
||||
});
|
||||
|
||||
req.setTimeout(timeout, () => {
|
||||
req.setTimeout(req_timeout_ms, () => {
|
||||
req.destroy();
|
||||
reject(new Error('request timeout'));
|
||||
});
|
||||
@@ -2971,6 +3066,69 @@ async function http_req(method, url_str, headers, body) {
|
||||
});
|
||||
}
|
||||
|
||||
// function parse_www_authenticate(www_authenticate) {
|
||||
// if (! www_authenticate.startsWith('Bearer ')) {
|
||||
// throw new Error('invalid www-authenticate header (no "Bearer " prefix)');
|
||||
// }
|
||||
|
||||
// let remaining = www_authenticate.slice(7);
|
||||
// const parameters = Object.create(null);
|
||||
|
||||
// while (remaining) {
|
||||
// const eq_index = remaining.indexOf('=');
|
||||
|
||||
// if (eq_index < 0) {
|
||||
// parameters[remaining] = '';
|
||||
// break;
|
||||
// }
|
||||
|
||||
// const key = remaining.slice(0, eq_index);
|
||||
// remaining = remaining.slice(eq_index + 1);
|
||||
|
||||
// if (! remaining) {
|
||||
// parameters[key] = '';
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if (remaining[0] === '"') {
|
||||
// const close_index = remaining.slice(1).indexOf('"');
|
||||
|
||||
// if (close_index < 0) {
|
||||
// throw new Error('invalid www-authenticate header (unclosed quotes)');
|
||||
// }
|
||||
|
||||
// parameters[key] = remaining.slice(1, close_index + 1);
|
||||
// remaining = remaining.slice(close_index + 2);
|
||||
|
||||
// if (remaining && remaining[0] !== ',') {
|
||||
// throw new Error('invalid www-authenticate header (expected comma after closing quote)');
|
||||
// }
|
||||
|
||||
// remaining = remaining.slice(1);
|
||||
// }
|
||||
|
||||
// else {
|
||||
// const comma_index = remaining.indexOf(',');
|
||||
|
||||
// if (comma_index < 0) {
|
||||
// parameters[key] = remaining;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// parameters[key] = remaining.slice(1, comma_index);
|
||||
// remaining = remaining.slice(comma_index + 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// const { realm, service, scope } = parameters;
|
||||
|
||||
// if (! realm || ! service || ! scope) {
|
||||
// throw new Error('invalid www-authenticate header (missing "realm", "service", or "scope")');
|
||||
// }
|
||||
|
||||
// return parameters;
|
||||
// }
|
||||
|
||||
})();
|
||||
|
||||
module.exports = __webpack_exports__;
|
||||
|
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
194
index.js
194
index.js
@@ -2,8 +2,11 @@
|
||||
const core = require('@actions/core');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
// const querystring = require('querystring');
|
||||
|
||||
const req_timeout_ms = 30_000;
|
||||
const manifest_media_type = 'application/vnd.docker.distribution.manifest.v2+json';
|
||||
const image_index_media_type = 'application/vnd.oci.image.index.v1+json';
|
||||
|
||||
main();
|
||||
|
||||
@@ -11,16 +14,22 @@ 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'),
|
||||
registry_url: null,
|
||||
insecure: core.getInput('insecure') === 'true',
|
||||
username: core.getInput('username'),
|
||||
password: core.getInput('password'),
|
||||
manifest: parse_manifest_tag(core.getInput('manifest')),
|
||||
tags: parse_tags(core.getInput('tags').trim()),
|
||||
token: null,
|
||||
};
|
||||
|
||||
console.log('Tagging %s:%s with new tags', input.image, input.old_tag, input);
|
||||
input.registry_url = `http${input.insecure ? '' : 's'}://${input.registry}`;
|
||||
console.log('Tagging %s/%s:%s with new tags', input.registry, input.manifest.image, input.manifest.tag, input.tags);
|
||||
|
||||
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);
|
||||
const manifest = await get_manifest(input);
|
||||
|
||||
const promises = input.tags.map((tag) => {
|
||||
return put_manifest(tag, manifest, input);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
@@ -32,28 +41,114 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
async function get_manifest(url_str) {
|
||||
const { status, body: existing_manifest } = await http_req('GET', url_str, {
|
||||
accept: manifest_media_type,
|
||||
});
|
||||
function parse_manifest_tag(full_tag) {
|
||||
const colon_index = full_tag.lastIndexOf(':');
|
||||
const image = full_tag.slice(0, colon_index);
|
||||
const tag = full_tag.slice(colon_index + 1);
|
||||
return { image, tag };
|
||||
}
|
||||
|
||||
function parse_tags(tags) {
|
||||
if (tags.indexOf('\n') < 0) {
|
||||
return tags.split(',').map((tag) => tag.trim());
|
||||
}
|
||||
|
||||
return tags.split('\n').map((tag) => tag.trim());
|
||||
}
|
||||
|
||||
async function get_manifest(input) {
|
||||
const path = `/v2/${input.manifest.image}/manifests/${input.manifest.tag}`;
|
||||
const req_headers = {
|
||||
accept: `${manifest_media_type}, ${image_index_media_type}`,
|
||||
};
|
||||
|
||||
http_basic_auth(req_headers, input);
|
||||
|
||||
let { status, headers, body } = await http_req('GET', input.registry_url + path, req_headers);
|
||||
|
||||
// if (status === 401 && headers['www-authenticate']) {
|
||||
// await get_token(headers['www-authenticate'], input);
|
||||
|
||||
// ({ status, headers, body } = await http_req('GET', input.registry_url + path, {
|
||||
// accept: manifest_media_type,
|
||||
// authorization: `Bearer ${input.token}`,
|
||||
// }));
|
||||
// }
|
||||
|
||||
if (status !== 200) {
|
||||
console.error('get manifest response', { status, headers, body });
|
||||
throw new Error('failed to fetch existing manifest');
|
||||
}
|
||||
|
||||
return existing_manifest;
|
||||
return {
|
||||
content: body,
|
||||
type: headers['content-type'],
|
||||
length: headers['content-length'],
|
||||
};
|
||||
}
|
||||
|
||||
async function put_manifest(url_str, manifest) {
|
||||
const { status } = await http_req('PUT', url_str, {
|
||||
'content-type': manifest_media_type,
|
||||
}, manifest);
|
||||
async function put_manifest(new_tag, manifest, input) {
|
||||
const path = `/v2/${input.manifest.image}/manifests/${new_tag}`;
|
||||
const req_headers = {
|
||||
'content-type': manifest.type,
|
||||
'content-length': manifest.length,
|
||||
};
|
||||
|
||||
http_basic_auth(req_headers, input);
|
||||
|
||||
// if (input.token) {
|
||||
// req_headers.authorization = `Bearer ${input.token}`;
|
||||
// }
|
||||
|
||||
const { status, headers, body } = await http_req('PUT', input.registry_url + path, req_headers, manifest.content);
|
||||
|
||||
if (status >= 400) {
|
||||
console.error('put manifest response', { status, headers, body });
|
||||
throw new Error('failed to put manifest');
|
||||
}
|
||||
}
|
||||
|
||||
function http_basic_auth(headers, input) {
|
||||
if (input.username) {
|
||||
const credentials = Buffer.from(`${input.username}:${input.password}`, 'utf8').toString('base64');
|
||||
headers.authorization = `Basic ${credentials}`;
|
||||
}
|
||||
}
|
||||
|
||||
// async function get_token(www_authenticate, input) {
|
||||
// const params = parse_www_authenticate(www_authenticate);
|
||||
|
||||
// if (! input.username || ! input.password) {
|
||||
// throw new Error('registry requested authentication, but not credentials were provided');
|
||||
// }
|
||||
|
||||
// const query_params = {
|
||||
// service: params.service,
|
||||
// scope: `repository:${input.manifest.image}:pull,push`,
|
||||
// grant_type: 'password',
|
||||
// username: input.username,
|
||||
// password: input.password,
|
||||
// };
|
||||
|
||||
// const { status, headers, body } = await http_req('GET', params.realm + '?' + querystring.stringify(query_params), {
|
||||
// 'content-type': 'application/x-www-form-urlencoded',
|
||||
// });
|
||||
|
||||
// if (status !== 200) {
|
||||
// console.error('get token response', { status, headers, body });
|
||||
// throw new Error('error response from get token request');
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const { token } = JSON.parse(body);
|
||||
// input.token = token;
|
||||
// }
|
||||
|
||||
// catch (error) {
|
||||
// throw new Error('invalid response from get token request');
|
||||
// }
|
||||
// }
|
||||
|
||||
async function http_req(method, url_str, headers, body) {
|
||||
const url = new URL(url_str);
|
||||
const make_request
|
||||
@@ -79,7 +174,7 @@ async function http_req(method, url_str, headers, body) {
|
||||
const path = url.pathname + (url.search || '');
|
||||
const port = url.port ? parseInt(url.port, 10) : (url.protocol === 'https:' ? 443 : 80);
|
||||
|
||||
return new Promise<HttpResult>((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = make_request({
|
||||
method: method,
|
||||
protocol: url.protocol,
|
||||
@@ -101,7 +196,7 @@ async function http_req(method, url_str, headers, body) {
|
||||
});
|
||||
});
|
||||
|
||||
req.setTimeout(timeout, () => {
|
||||
req.setTimeout(req_timeout_ms, () => {
|
||||
req.destroy();
|
||||
reject(new Error('request timeout'));
|
||||
});
|
||||
@@ -117,3 +212,66 @@ async function http_req(method, url_str, headers, body) {
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// function parse_www_authenticate(www_authenticate) {
|
||||
// if (! www_authenticate.startsWith('Bearer ')) {
|
||||
// throw new Error('invalid www-authenticate header (no "Bearer " prefix)');
|
||||
// }
|
||||
|
||||
// let remaining = www_authenticate.slice(7);
|
||||
// const parameters = Object.create(null);
|
||||
|
||||
// while (remaining) {
|
||||
// const eq_index = remaining.indexOf('=');
|
||||
|
||||
// if (eq_index < 0) {
|
||||
// parameters[remaining] = '';
|
||||
// break;
|
||||
// }
|
||||
|
||||
// const key = remaining.slice(0, eq_index);
|
||||
// remaining = remaining.slice(eq_index + 1);
|
||||
|
||||
// if (! remaining) {
|
||||
// parameters[key] = '';
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if (remaining[0] === '"') {
|
||||
// const close_index = remaining.slice(1).indexOf('"');
|
||||
|
||||
// if (close_index < 0) {
|
||||
// throw new Error('invalid www-authenticate header (unclosed quotes)');
|
||||
// }
|
||||
|
||||
// parameters[key] = remaining.slice(1, close_index + 1);
|
||||
// remaining = remaining.slice(close_index + 2);
|
||||
|
||||
// if (remaining && remaining[0] !== ',') {
|
||||
// throw new Error('invalid www-authenticate header (expected comma after closing quote)');
|
||||
// }
|
||||
|
||||
// remaining = remaining.slice(1);
|
||||
// }
|
||||
|
||||
// else {
|
||||
// const comma_index = remaining.indexOf(',');
|
||||
|
||||
// if (comma_index < 0) {
|
||||
// parameters[key] = remaining;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// parameters[key] = remaining.slice(1, comma_index);
|
||||
// remaining = remaining.slice(comma_index + 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// const { realm, service, scope } = parameters;
|
||||
|
||||
// if (! realm || ! service || ! scope) {
|
||||
// throw new Error('invalid www-authenticate header (missing "realm", "service", or "scope")');
|
||||
// }
|
||||
|
||||
// return parameters;
|
||||
// }
|
||||
|
14
package.json
14
package.json
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"prepare": "ncc build index.js -o dist --source-map --license licenses.txt",
|
||||
"all": "npm run lint && npm run prepare"
|
||||
"lint": "eslint .",
|
||||
"prepare": "ncc build index.js -o dist --source-map --license licenses.txt",
|
||||
"all": "npm run lint && npm run prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0"
|
||||
"@actions/core": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"eslint": "^8.37.0"
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"eslint": "^8.37.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
56
readme.md
56
readme.md
@@ -3,7 +3,42 @@ Action for re-tagging an existing docker multi-arch manifest
|
||||
|
||||
## Inputs
|
||||
|
||||
<!-- -->
|
||||
### `registry`
|
||||
|
||||
URL to the container registry
|
||||
|
||||
required: true
|
||||
|
||||
### `insecure`
|
||||
|
||||
If "true", will use HTTP rather than HTTPS
|
||||
|
||||
required: false
|
||||
|
||||
### `username`
|
||||
|
||||
Username to use to authenticate to the container registry
|
||||
|
||||
required: false
|
||||
|
||||
### `password`
|
||||
|
||||
Password to use to authenticate to the container registry
|
||||
|
||||
required: false
|
||||
|
||||
### `manifest`
|
||||
|
||||
The name/tag of the existing manifest in the registry
|
||||
|
||||
required: true
|
||||
|
||||
### `tags`
|
||||
|
||||
Newline or comma delimited list of new tags to apply to the manifest
|
||||
|
||||
required: true
|
||||
|
||||
|
||||
## Example usage
|
||||
|
||||
@@ -13,12 +48,21 @@ jobs:
|
||||
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
|
||||
uses: https://gitea.jbrumond.me/actions/tag-manifest@v0.1
|
||||
with:
|
||||
registry: https://gitea.example.com
|
||||
image: owner/package
|
||||
old-tag: latest
|
||||
new-tags: |
|
||||
# Container registry where the manifest is hosted
|
||||
registry: gitea.example.com
|
||||
|
||||
# Credentials to access the registry
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
# Existing manifest to apply tags to
|
||||
manifest: owner/package:latest
|
||||
|
||||
# New tags to apply (either of these works):
|
||||
tags: tag1,tag2,tag3
|
||||
tags: |
|
||||
tag1
|
||||
tag2
|
||||
tag3
|
||||
|
Reference in New Issue
Block a user