Add feature to push multiple tags of the image

Signed-off-by: divyansh42 <diagrawa@redhat.com>
This commit is contained in:
divyansh42 2021-01-25 15:52:13 +05:30
parent 23eb62f550
commit 73da53f4a0
6 changed files with 96 additions and 55 deletions

View file

@ -1,7 +1,7 @@
# This workflow will perform a test whenever there
# is some change in code done to ensure that the changes
# are not buggy and we are getting the desired output.
name: Test Push without image
name: Test Build and Push
on: [ push, workflow_dispatch ]
env:
IMAGE_NAME: myimage
@ -19,10 +19,13 @@ jobs:
- name: Build Image using Docker
run: |
docker build -t ${{ env.IMAGE_NAME }}:latest -<<EOF
docker build -t ${{ env.IMAGE_NAME }}:v1 -<<EOF
FROM busybox
RUN echo "hello world"
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
- name: Push To Quay
@ -30,12 +33,12 @@ jobs:
id: push
with:
image: ${{ env.IMAGE_NAME }}
tag: ${{ env.IMAGE_TAG }}
tags: v1 v2 v3
registry: ${{ env.IMAGE_REGISTRY }}/${{ secrets.REGISTRY_USER }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Echo outputs
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 }}"

View file

@ -73,11 +73,12 @@ Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-man
## Action Outputs
`registry-path`: The registry path to which the image was pushed.<br>
For example, `quay.io/username/spring-image:v1`.
`registry-paths`: The List of registry paths to which the tag(s) were pushed.<br>
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>
For example, `sha256:66ce924069ec4181725d15aa27f34afbaf082f434f448dc07a42daa3305cdab3`.
For multiple tags, digest remains same.
## Examples

View file

@ -8,8 +8,8 @@ inputs:
image:
description: 'Name of the image to push'
required: true
tag:
description: 'Tag of the image to push'
tags:
description: 'The tags of the image to push. For multiple tags, seperate by a space. For example, "v1 v0.1".'
required: false
default: 'latest'
registry:
@ -25,10 +25,18 @@ inputs:
description: 'Verify TLS certificates when contacting the registry'
required: false
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:
registry-path:
description: 'The registry path to which the image was pushed'
digest:
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:
using: 'node12'
main: 'dist/index.js'

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,7 @@ let podmanPath: string | undefined;
// boolean value to check if pushed image is from Docker image storage
let isImageFromDocker = false;
let imageToPush: string;
let tagsList: string[];
async function getPodmanPath(): Promise<string> {
if (podmanPath == null) {
@ -29,24 +30,28 @@ const dockerBaseUrl = "docker.io/library";
async function run(): Promise<void> {
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 username = core.getInput("username", { required: true });
const password = core.getInput("password", { required: true });
const tlsVerify = core.getInput("tls-verify");
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();
// 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();
// 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) {
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) {
@ -60,38 +65,42 @@ async function run(): Promise<void> {
}
else {
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.`);
}
}
else if (isPresentInDocker) {
imageToPush = `${dockerBaseUrl}/${imageToPush}`;
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.`);
isImageFromDocker = true;
}
let pushMsg = `Pushing ${imageToPush} to ${registry}`;
let pushMsg = `Pushing ${imageToPush} with tags ${tagsList.toString()} to ${registry}`;
if (username) {
pushMsg += ` as ${username}`;
}
core.info(pushMsg);
const registryWithoutTrailingSlash = registry.replace(/\/$/, "");
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}:${tag}`;
const creds = `${username}:${password}`;
let digestFile = digestFileInput;
const imageNameWithTag = `${imageToPush}:${tagsList[0]}`;
if (!digestFile) {
digestFile = `${imageToPush.replace(
digestFile = `${imageNameWithTag.replace(
/[/\\/?%*:|"<>]/g,
"-",
)}_digest.txt`;
}
// push the image
for (const tag of tagsList) {
const imageWithTag = `${imageToPush}:${tag}`;
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}:${tag}`;
const args = [
"push",
"--quiet",
@ -99,7 +108,7 @@ async function run(): Promise<void> {
digestFile,
"--creds",
creds,
imageToPush,
imageWithTag,
registryPath,
];
@ -109,9 +118,10 @@ async function run(): Promise<void> {
}
await execute(await getPodmanPath(), args);
core.info(`Successfully pushed ${imageWithTag} to ${registryPath}.`);
core.info(`Successfully pushed ${imageToPush} to ${registryPath}.`);
core.setOutput("registry-path", registryPath);
registryPathList.push(registryPath);
}
try {
const digest = (await fs.promises.readFile(digestFile)).toString();
@ -121,41 +131,57 @@ async function run(): Promise<void> {
catch (err) {
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
}
core.setOutput("registry-paths", registryPathList.toString());
}
async function pullImageFromDocker(): Promise<boolean> {
let imageWithTag;
try {
await execute(await getPodmanPath(), [ "pull", `docker-daemon:${imageToPush}` ]);
core.info(`${imageToPush} found in Docker image storage`);
return true;
for (const tag of tagsList) {
imageWithTag = `${imageToPush}:${tag}`;
await execute(await getPodmanPath(), [ "pull", `docker-daemon:${imageWithTag}` ]);
core.info(`${imageWithTag} found in Docker image storage`);
}
}
catch (err) {
core.info(`${imageToPush} not found in Docker image storage`);
core.info(`${imageWithTag} not found in Docker image storage`);
return false;
}
return true;
}
async function checkImageInPodman(): Promise<boolean> {
// 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 {
await execute(await getPodmanPath(), [ "image", "exists", imageToPush ]);
core.info(`${imageToPush} found in Podman image storage`);
return true;
for (const tag of tagsList) {
imageWithTag = `${imageToPush}:${tag}`;
await execute(await getPodmanPath(), [ "image", "exists", imageWithTag ]);
core.info(`${imageWithTag} found in Podman image storage`);
}
}
catch (err) {
core.info(`${imageToPush} not found in Podman image storage`);
core.info(`${imageWithTag} not found in Podman image storage`);
core.debug(err);
return false;
}
return true;
}
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
const podmanLocalImageTimeStamp = await execute(await getPodmanPath(), [
"image",
"inspect",
imageToPush,
imageWithTag,
"--format",
"{{.Created}}",
]);
@ -166,7 +192,7 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
const pulledImageCreationTimeStamp = await execute(await getPodmanPath(), [
"image",
"inspect",
`${dockerBaseUrl}/${imageToPush}`,
`${dockerBaseUrl}/${imageWithTag}`,
"--format",
"{{.Created}}",
]);
@ -181,8 +207,11 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
// remove the pulled image from the Podman image storage
async function removeDockerImage(): Promise<void> {
if (imageToPush) {
core.info(`Removing ${imageToPush} from the Podman image storage`);
await execute(await getPodmanPath(), [ "rmi", imageToPush ]);
for (const tag of tagsList) {
const imageWithTag = `${imageToPush}:${tag}`;
await execute(await getPodmanPath(), [ "rmi", imageWithTag ]);
core.info(`Removing ${imageWithTag} from the Podman image storage`);
}
}
}