mirror of
https://github.com/redhat-actions/push-to-registry.git
synced 2025-04-20 06:36:17 +02:00
Add feature to push multiple tags of the image
Signed-off-by: divyansh42 <diagrawa@redhat.com>
This commit is contained in:
parent
23eb62f550
commit
73da53f4a0
6 changed files with 96 additions and 55 deletions
13
.github/workflows/verify-push.yaml
vendored
13
.github/workflows/verify-push.yaml
vendored
|
@ -1,7 +1,7 @@
|
||||||
# This workflow will perform a test whenever there
|
# This workflow will perform a test whenever there
|
||||||
# is some change in code done to ensure that the changes
|
# is some change in code done to ensure that the changes
|
||||||
# are not buggy and we are getting the desired output.
|
# are not buggy and we are getting the desired output.
|
||||||
name: Test Push without image
|
name: Test Build and Push
|
||||||
on: [ push, workflow_dispatch ]
|
on: [ push, workflow_dispatch ]
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: myimage
|
IMAGE_NAME: myimage
|
||||||
|
@ -19,10 +19,13 @@ jobs:
|
||||||
|
|
||||||
- name: Build Image using Docker
|
- name: Build Image using Docker
|
||||||
run: |
|
run: |
|
||||||
docker build -t ${{ env.IMAGE_NAME }}:latest -<<EOF
|
docker build -t ${{ env.IMAGE_NAME }}:v1 -<<EOF
|
||||||
FROM busybox
|
FROM busybox
|
||||||
RUN echo "hello world"
|
RUN echo "hello world"
|
||||||
EOF
|
EOF
|
||||||
|
docker tag ${{ env.IMAGE_NAME }}:v1 ${{ env.IMAGE_NAME }}:v2
|
||||||
|
docker tag ${{ env.IMAGE_NAME }}:v1 ${{ env.IMAGE_NAME }}:v3
|
||||||
|
|
||||||
|
|
||||||
# Push the image to image registry
|
# Push the image to image registry
|
||||||
- name: Push To Quay
|
- name: Push To Quay
|
||||||
|
@ -30,12 +33,12 @@ jobs:
|
||||||
id: push
|
id: push
|
||||||
with:
|
with:
|
||||||
image: ${{ env.IMAGE_NAME }}
|
image: ${{ env.IMAGE_NAME }}
|
||||||
tag: ${{ env.IMAGE_TAG }}
|
tags: v1 v2 v3
|
||||||
registry: ${{ env.IMAGE_REGISTRY }}/${{ secrets.REGISTRY_USER }}
|
registry: ${{ env.IMAGE_REGISTRY }}/${{ secrets.REGISTRY_USER }}
|
||||||
username: ${{ secrets.REGISTRY_USER }}
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
- name: Echo outputs
|
- name: Echo outputs
|
||||||
run: |
|
run: |
|
||||||
echo "registry-path ${{ steps.push.outputs.registry-path }}"
|
echo "Digest: ${{ steps.push.outputs.digest }}"
|
||||||
echo "digest ${{ steps.push.outputs.digest }}"
|
echo "Registry Paths: ${{ steps.push.outputs.registry-paths }}"
|
||||||
|
|
|
@ -73,11 +73,12 @@ Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-man
|
||||||
|
|
||||||
## Action Outputs
|
## Action Outputs
|
||||||
|
|
||||||
`registry-path`: The registry path to which the image was pushed.<br>
|
`registry-paths`: The List of registry paths to which the tag(s) were pushed.<br>
|
||||||
For example, `quay.io/username/spring-image:v1`.
|
For example, `quay.io/username/spring-image:v1,quay.io/username/spring-image:v2`.
|
||||||
|
|
||||||
`digest`: The pushed image digest, as written to the `digestfile`.<br>
|
`digest`: The pushed image digest, as written to the `digestfile`.<br>
|
||||||
For example, `sha256:66ce924069ec4181725d15aa27f34afbaf082f434f448dc07a42daa3305cdab3`.
|
For example, `sha256:66ce924069ec4181725d15aa27f34afbaf082f434f448dc07a42daa3305cdab3`.
|
||||||
|
For multiple tags, digest remains same.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
16
action.yml
16
action.yml
|
@ -8,8 +8,8 @@ inputs:
|
||||||
image:
|
image:
|
||||||
description: 'Name of the image to push'
|
description: 'Name of the image to push'
|
||||||
required: true
|
required: true
|
||||||
tag:
|
tags:
|
||||||
description: 'Tag of the image to push'
|
description: 'The tags of the image to push. For multiple tags, seperate by a space. For example, "v1 v0.1".'
|
||||||
required: false
|
required: false
|
||||||
default: 'latest'
|
default: 'latest'
|
||||||
registry:
|
registry:
|
||||||
|
@ -25,10 +25,18 @@ inputs:
|
||||||
description: 'Verify TLS certificates when contacting the registry'
|
description: 'Verify TLS certificates when contacting the registry'
|
||||||
required: false
|
required: false
|
||||||
default: 'true'
|
default: 'true'
|
||||||
|
digestfile:
|
||||||
|
description: |
|
||||||
|
After copying the image, write the digest of the resulting image to the file.
|
||||||
|
By default, the filename will be determined from the image and tag.
|
||||||
|
The contents of this file are the digest output.
|
||||||
|
required: false
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
registry-path:
|
digest:
|
||||||
description: 'The registry path to which the image was pushed'
|
description: 'The pushed image digest, as written to the `digestfile`'
|
||||||
|
registry-paths:
|
||||||
|
description: 'List of registry paths to which the tag(s) were pushed'
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
83
src/index.ts
83
src/index.ts
|
@ -15,6 +15,7 @@ let podmanPath: string | undefined;
|
||||||
// boolean value to check if pushed image is from Docker image storage
|
// boolean value to check if pushed image is from Docker image storage
|
||||||
let isImageFromDocker = false;
|
let isImageFromDocker = false;
|
||||||
let imageToPush: string;
|
let imageToPush: string;
|
||||||
|
let tagsList: string[];
|
||||||
|
|
||||||
async function getPodmanPath(): Promise<string> {
|
async function getPodmanPath(): Promise<string> {
|
||||||
if (podmanPath == null) {
|
if (podmanPath == null) {
|
||||||
|
@ -29,24 +30,28 @@ const dockerBaseUrl = "docker.io/library";
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
const imageInput = core.getInput("image", { required: true });
|
const imageInput = core.getInput("image", { required: true });
|
||||||
const tag = core.getInput("tag") || "latest";
|
const tags = core.getInput("tags");
|
||||||
|
// split tags
|
||||||
|
tagsList = tags.split(" ");
|
||||||
const registry = core.getInput("registry", { required: true });
|
const registry = core.getInput("registry", { required: true });
|
||||||
const username = core.getInput("username", { required: true });
|
const username = core.getInput("username", { required: true });
|
||||||
const password = core.getInput("password", { required: true });
|
const password = core.getInput("password", { required: true });
|
||||||
const tlsVerify = core.getInput("tls-verify");
|
const tlsVerify = core.getInput("tls-verify");
|
||||||
const digestFileInput = core.getInput("digestfile");
|
const digestFileInput = core.getInput("digestfile");
|
||||||
|
|
||||||
imageToPush = `${imageInput}:${tag}`;
|
imageToPush = `${imageInput}`;
|
||||||
|
const registryPathList: string[] = [];
|
||||||
|
|
||||||
// check if image exist in Podman image storage
|
// check if image with all the required tags exist in Podman image storage
|
||||||
const isPresentInPodman: boolean = await checkImageInPodman();
|
const isPresentInPodman: boolean = await checkImageInPodman();
|
||||||
|
|
||||||
// check if image exist in Docker image storage and if exist pull the image to Podman
|
// check if image with all the required tags exist in Docker image storage
|
||||||
|
// and if exist pull the image with all the tags to Podman
|
||||||
const isPresentInDocker: boolean = await pullImageFromDocker();
|
const isPresentInDocker: boolean = await pullImageFromDocker();
|
||||||
|
|
||||||
// failing if image is not found in Docker as well as Podman
|
// failing if image with any of the tag is not found in Docker as well as Podman
|
||||||
if (!isPresentInDocker && !isPresentInPodman) {
|
if (!isPresentInDocker && !isPresentInPodman) {
|
||||||
throw new Error(`${imageToPush} not found in Podman local storage, or Docker local storage.`);
|
throw new Error(`All the tags of ${imageToPush} not found in Podman local storage, or Docker local storage.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresentInPodman && isPresentInDocker) {
|
if (isPresentInPodman && isPresentInDocker) {
|
||||||
|
@ -60,38 +65,42 @@ async function run(): Promise<void> {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
core.warning(`The version of ${imageToPush} in the Podman image storage is more recent `
|
core.warning(`The version of ${imageToPush} in the Podman image storage is more recent `
|
||||||
+ `than the version in the Docker image storage. The image from the Podman image `
|
+ `than the version in the Docker image storage. Tag(s) of the image from the Podman image `
|
||||||
+ `storage will be pushed.`);
|
+ `storage will be pushed.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isPresentInDocker) {
|
else if (isPresentInDocker) {
|
||||||
imageToPush = `${dockerBaseUrl}/${imageToPush}`;
|
imageToPush = `${dockerBaseUrl}/${imageToPush}`;
|
||||||
core.info(`${imageToPush} was found in the Docker image storage, but not in the Podman `
|
core.info(`${imageToPush} was found in the Docker image storage, but not in the Podman `
|
||||||
+ `image storage. The image will be pulled into Podman image storage, pushed, and then `
|
+ `image storage. Tag(s) of the image will be pulled into Podman image storage, pushed, and then `
|
||||||
+ `removed from the Podman image storage.`);
|
+ `removed from the Podman image storage.`);
|
||||||
isImageFromDocker = true;
|
isImageFromDocker = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pushMsg = `Pushing ${imageToPush} to ${registry}`;
|
let pushMsg = `Pushing ${imageToPush} with tags ${tagsList.toString()} to ${registry}`;
|
||||||
if (username) {
|
if (username) {
|
||||||
pushMsg += ` as ${username}`;
|
pushMsg += ` as ${username}`;
|
||||||
}
|
}
|
||||||
core.info(pushMsg);
|
core.info(pushMsg);
|
||||||
|
|
||||||
const registryWithoutTrailingSlash = registry.replace(/\/$/, "");
|
const registryWithoutTrailingSlash = registry.replace(/\/$/, "");
|
||||||
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}:${tag}`;
|
|
||||||
|
|
||||||
const creds = `${username}:${password}`;
|
const creds = `${username}:${password}`;
|
||||||
|
|
||||||
let digestFile = digestFileInput;
|
let digestFile = digestFileInput;
|
||||||
|
const imageNameWithTag = `${imageToPush}:${tagsList[0]}`;
|
||||||
if (!digestFile) {
|
if (!digestFile) {
|
||||||
digestFile = `${imageToPush.replace(
|
digestFile = `${imageNameWithTag.replace(
|
||||||
/[/\\/?%*:|"<>]/g,
|
/[/\\/?%*:|"<>]/g,
|
||||||
"-",
|
"-",
|
||||||
)}_digest.txt`;
|
)}_digest.txt`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// push the image
|
// push the image
|
||||||
|
for (const tag of tagsList) {
|
||||||
|
const imageWithTag = `${imageToPush}:${tag}`;
|
||||||
|
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}:${tag}`;
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
"push",
|
"push",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
@ -99,7 +108,7 @@ async function run(): Promise<void> {
|
||||||
digestFile,
|
digestFile,
|
||||||
"--creds",
|
"--creds",
|
||||||
creds,
|
creds,
|
||||||
imageToPush,
|
imageWithTag,
|
||||||
registryPath,
|
registryPath,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -109,9 +118,10 @@ async function run(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
await execute(await getPodmanPath(), args);
|
await execute(await getPodmanPath(), args);
|
||||||
|
core.info(`Successfully pushed ${imageWithTag} to ${registryPath}.`);
|
||||||
|
|
||||||
core.info(`Successfully pushed ${imageToPush} to ${registryPath}.`);
|
registryPathList.push(registryPath);
|
||||||
core.setOutput("registry-path", registryPath);
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const digest = (await fs.promises.readFile(digestFile)).toString();
|
const digest = (await fs.promises.readFile(digestFile)).toString();
|
||||||
|
@ -121,41 +131,57 @@ async function run(): Promise<void> {
|
||||||
catch (err) {
|
catch (err) {
|
||||||
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
|
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core.setOutput("registry-paths", registryPathList.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullImageFromDocker(): Promise<boolean> {
|
async function pullImageFromDocker(): Promise<boolean> {
|
||||||
|
let imageWithTag;
|
||||||
try {
|
try {
|
||||||
await execute(await getPodmanPath(), [ "pull", `docker-daemon:${imageToPush}` ]);
|
for (const tag of tagsList) {
|
||||||
core.info(`${imageToPush} found in Docker image storage`);
|
imageWithTag = `${imageToPush}:${tag}`;
|
||||||
return true;
|
await execute(await getPodmanPath(), [ "pull", `docker-daemon:${imageWithTag}` ]);
|
||||||
|
core.info(`${imageWithTag} found in Docker image storage`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
core.info(`${imageToPush} not found in Docker image storage`);
|
core.info(`${imageWithTag} not found in Docker image storage`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkImageInPodman(): Promise<boolean> {
|
async function checkImageInPodman(): Promise<boolean> {
|
||||||
// check if images exist in Podman's storage
|
// check if images exist in Podman's storage
|
||||||
core.info(`Checking if ${imageToPush} is in Podman image storage`);
|
core.info(`Checking if ${imageToPush} with tag(s) ${tagsList.toString()} is present in Podman image storage`);
|
||||||
|
let imageWithTag;
|
||||||
try {
|
try {
|
||||||
await execute(await getPodmanPath(), [ "image", "exists", imageToPush ]);
|
for (const tag of tagsList) {
|
||||||
core.info(`${imageToPush} found in Podman image storage`);
|
imageWithTag = `${imageToPush}:${tag}`;
|
||||||
return true;
|
await execute(await getPodmanPath(), [ "image", "exists", imageWithTag ]);
|
||||||
|
core.info(`${imageWithTag} found in Podman image storage`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
core.info(`${imageToPush} not found in Podman image storage`);
|
core.info(`${imageWithTag} not found in Podman image storage`);
|
||||||
core.debug(err);
|
core.debug(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isPodmanLocalImageLatest(): Promise<boolean> {
|
async function isPodmanLocalImageLatest(): Promise<boolean> {
|
||||||
|
// checking for only one tag as creation time will be
|
||||||
|
// same for all the tags present
|
||||||
|
const imageWithTag = `${imageToPush}:${tagsList[0]}`;
|
||||||
|
|
||||||
// get creation time of the image present in the Podman image storage
|
// get creation time of the image present in the Podman image storage
|
||||||
const podmanLocalImageTimeStamp = await execute(await getPodmanPath(), [
|
const podmanLocalImageTimeStamp = await execute(await getPodmanPath(), [
|
||||||
"image",
|
"image",
|
||||||
"inspect",
|
"inspect",
|
||||||
imageToPush,
|
imageWithTag,
|
||||||
"--format",
|
"--format",
|
||||||
"{{.Created}}",
|
"{{.Created}}",
|
||||||
]);
|
]);
|
||||||
|
@ -166,7 +192,7 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
|
||||||
const pulledImageCreationTimeStamp = await execute(await getPodmanPath(), [
|
const pulledImageCreationTimeStamp = await execute(await getPodmanPath(), [
|
||||||
"image",
|
"image",
|
||||||
"inspect",
|
"inspect",
|
||||||
`${dockerBaseUrl}/${imageToPush}`,
|
`${dockerBaseUrl}/${imageWithTag}`,
|
||||||
"--format",
|
"--format",
|
||||||
"{{.Created}}",
|
"{{.Created}}",
|
||||||
]);
|
]);
|
||||||
|
@ -181,8 +207,11 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
|
||||||
// remove the pulled image from the Podman image storage
|
// remove the pulled image from the Podman image storage
|
||||||
async function removeDockerImage(): Promise<void> {
|
async function removeDockerImage(): Promise<void> {
|
||||||
if (imageToPush) {
|
if (imageToPush) {
|
||||||
core.info(`Removing ${imageToPush} from the Podman image storage`);
|
for (const tag of tagsList) {
|
||||||
await execute(await getPodmanPath(), [ "rmi", imageToPush ]);
|
const imageWithTag = `${imageToPush}:${tag}`;
|
||||||
|
await execute(await getPodmanPath(), [ "rmi", imageWithTag ]);
|
||||||
|
core.info(`Removing ${imageWithTag} from the Podman image storage`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue