Add feature to push multiple tags of the image (#18)

* Modify workflow to trigger on pull request

* Update workflow to perform multi tag build and push

Signed-off-by: divyansh42 <diagrawa@redhat.com>
This commit is contained in:
Divyanshu Agrawal 2021-02-03 08:05:48 +05:30 committed by GitHub
parent 23eb62f550
commit 2e6cff9b90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 320 additions and 149 deletions

View file

@ -1,41 +1,99 @@
# 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
on: [ push, workflow_dispatch ]
name: Test Build and Push
on: [ push, workflow_dispatch, pull_request_target ]
env:
IMAGE_NAME: myimage
PROJECT_DIR: spring-petclinic
IMAGE_NAME: spring-petclinic
IMAGE_REGISTRY: quay.io
IMAGE_TAG: latest
IMAGE_TAGS: v1 ${{ github.sha }}
MVN_REPO_DIR: ~/.m2/repository
jobs:
build:
name: Push image to Quay.io
build-and-push:
name: Build and push image to Quay.io
runs-on: ubuntu-20.04
steps:
# Checkout push-to-registry action github repository
- name: Checkout Push to Registry action
uses: actions/checkout@v2
with:
path: "push-to-registry"
- name: Build Image using Docker
# Checkout spring-petclinic github repository
- name: Checkout spring-petclinic project
uses: actions/checkout@v2
with:
repository: "spring-projects/spring-petclinic"
path: ${{ env.PROJECT_DIR }}
# If none of these files has changed, we assume that the contents of
# .m2/repository can be fetched from the cache.
- name: Hash Maven files
working-directory: ${{ env.PROJECT_DIR }}
run: |
docker build -t ${{ env.IMAGE_NAME }}:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF
echo "MVN_HASH=${{ hashFiles('**/pom.xml', '.mvn/**/*', 'mvnw*') }}" >> $GITHUB_ENV
# Download the m2 repository from the cache to speed up the build.
- name: Check for Maven cache
id: check-mvn-cache
uses: actions/cache@v2
with:
path: ${{ env.MVN_REPO_DIR }}
key: ${{ env.MVN_HASH }}
# Push the image to image registry
- name: Push To Quay
uses: ./
id: push
# Setup java.
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
# Run maven to build the project
- name: Maven
working-directory: ${{ env.PROJECT_DIR }}
run: |
mvn package -ntp -B
# If there was no cache hit above, store the output into the cache now.
- name: Save Maven repo into cache
if: ${{ steps.check-mvn-cache.outputs.cache-hit }} != 'true'
uses: actions/cache@v2
with:
path: ${{ env.MVN_REPO_DIR }}
key: ${{ env.MVN_HASH }}
# Build image using Buildah action
- name: Build Image
id: build_image
uses: redhat-actions/buildah-build@main
with:
image: ${{ env.IMAGE_NAME }}
tag: ${{ env.IMAGE_TAG }}
tags: ${{ env.IMAGE_TAGS }}
base-image: 'registry.access.redhat.com/openjdk/openjdk-11-rhel7'
# To avoid hardcoding a particular version of the binary.
content: |
./spring-petclinic/target/spring-petclinic-*.jar
entrypoint: |
java
-jar
spring-petclinic-*.jar
port: 8080
oci: 'true'
# Push the image to Quay.io (Image Registry)
- name: Push To Quay
uses: ./push-to-registry/
id: push
with:
image: ${{ steps.build_image.outputs.image }}
tags: ${{ steps.build_image.outputs.tags }}
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

@ -31,10 +31,10 @@ Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-man
</tr>
<tr>
<td>tag</td>
<td>tags</td>
<td>No</td>
<td>
Image tag to push.<br>
The tag or tags of the image to push. For multiple tags, seperate by a space. For example, <code>latest ${{ github.sha }}</code><br>
Defaults to <code>latest</code>.
</td>
</tr>
@ -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`: A JSON array of registry paths to which the tag(s) were pushed.<br>
For example, `[ quay.io/username/spring-image:v1, quay.io/username/spring-image:latest ]`.
`digest`: The pushed image digest, as written to the `digestfile`.<br>
For example, `sha256:66ce924069ec4181725d15aa27f34afbaf082f434f448dc07a42daa3305cdab3`.
For multiple tags, the digest is the same.
## Examples
@ -93,16 +94,18 @@ jobs:
runs-on: ubuntu-latest
env:
IMAGE_NAME: my-app
IMAGE_TAG: latest
IMAGE_TAGS: latest v1
steps:
- uses: actions/checkout@v2
- name: Build Image
id: build-image
uses: redhat-actions/buildah-build@v1
with:
image: ${{ env.IMAGE_NAME }}
tag: ${{ env.TAG }}
tags: ${{ env.IMAGE_TAGS }}
base-image: some_image
dockerfiles: |
./Dockerfile
@ -110,8 +113,8 @@ jobs:
id: push-to-quay
uses: redhat-actions/push-to-registry@v1
with:
image: ${{ env.IMAGE_NAME }}
tag: ${{ env.TAG }}
image: ${{ steps.build-image.outputs.image }}
tags: ${{ steps.build-image.outputs.tags }}
registry: ${{ secrets.QUAY_REPO }}
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}

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 tag or tags of the image to push. For multiple tags, seperate by a space. For example, "latest v1"'
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: 'A JSON array 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

98
package-lock.json generated
View file

@ -114,9 +114,9 @@
}
},
"@redhat-actions/eslint-config": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redhat-actions/eslint-config/-/eslint-config-1.2.0.tgz",
"integrity": "sha512-HFpdgXIB01wmZbCsEpaY0UhQlRBiM/kJxO0WCufIx/wTxepewwsLTsgCgKkljbGfSaqN7FMJ/TbwY1CY/ltHcw==",
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@redhat-actions/eslint-config/-/eslint-config-1.2.11.tgz",
"integrity": "sha512-hS8XXkpWu32Z3S6+EFAvD32+QruxyLiSfqrXjkaSLtWZndkJLNZ/xpHcEwRM1u7KJzd1f7jPl46+GLFfrPT8RQ==",
"dev": true,
"requires": {
"eslint-config-airbnb-base": ">= 14",
@ -130,9 +130,9 @@
"dev": true
},
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"dev": true
},
"@types/json5": {
@ -148,13 +148,13 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.0.tgz",
"integrity": "sha512-IJ5e2W7uFNfg4qh9eHkHRUCbgZ8VKtGwD07kannJvM5t/GU8P8+24NX8gi3Hf5jST5oWPY8kyV1s/WtfiZ4+Ww==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz",
"integrity": "sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "4.14.0",
"@typescript-eslint/scope-manager": "4.14.0",
"@typescript-eslint/experimental-utils": "4.14.1",
"@typescript-eslint/scope-manager": "4.14.1",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.15",
@ -164,55 +164,55 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.0.tgz",
"integrity": "sha512-6i6eAoiPlXMKRbXzvoQD5Yn9L7k9ezzGRvzC/x1V3650rUk3c3AOjQyGYyF9BDxQQDK2ElmKOZRD0CbtdkMzQQ==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz",
"integrity": "sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.14.0",
"@typescript-eslint/types": "4.14.0",
"@typescript-eslint/typescript-estree": "4.14.0",
"@typescript-eslint/scope-manager": "4.14.1",
"@typescript-eslint/types": "4.14.1",
"@typescript-eslint/typescript-estree": "4.14.1",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.0.tgz",
"integrity": "sha512-sUDeuCjBU+ZF3Lzw0hphTyScmDDJ5QVkyE21pRoBo8iDl7WBtVFS+WDN3blY1CH3SBt7EmYCw6wfmJjF0l/uYg==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.1.tgz",
"integrity": "sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "4.14.0",
"@typescript-eslint/types": "4.14.0",
"@typescript-eslint/typescript-estree": "4.14.0",
"@typescript-eslint/scope-manager": "4.14.1",
"@typescript-eslint/types": "4.14.1",
"@typescript-eslint/typescript-estree": "4.14.1",
"debug": "^4.1.1"
}
},
"@typescript-eslint/scope-manager": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.0.tgz",
"integrity": "sha512-/J+LlRMdbPh4RdL4hfP1eCwHN5bAhFAGOTsvE6SxsrM/47XQiPSgF5MDgLyp/i9kbZV9Lx80DW0OpPkzL+uf8Q==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz",
"integrity": "sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.14.0",
"@typescript-eslint/visitor-keys": "4.14.0"
"@typescript-eslint/types": "4.14.1",
"@typescript-eslint/visitor-keys": "4.14.1"
}
},
"@typescript-eslint/types": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.0.tgz",
"integrity": "sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.1.tgz",
"integrity": "sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.0.tgz",
"integrity": "sha512-wRjZ5qLao+bvS2F7pX4qi2oLcOONIB+ru8RGBieDptq/SudYwshveORwCVU4/yMAd4GK7Fsf8Uq1tjV838erag==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz",
"integrity": "sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.14.0",
"@typescript-eslint/visitor-keys": "4.14.0",
"@typescript-eslint/types": "4.14.1",
"@typescript-eslint/visitor-keys": "4.14.1",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
@ -222,12 +222,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.0.tgz",
"integrity": "sha512-MeHHzUyRI50DuiPgV9+LxcM52FCJFYjJiWHtXlbyC27b80mfOwKeiKI+MHOTEpcpfmoPFm/vvQS88bYIx6PZTA==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz",
"integrity": "sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.14.0",
"@typescript-eslint/types": "4.14.1",
"eslint-visitor-keys": "^2.0.0"
}
},
@ -917,9 +917,9 @@
}
},
"flatted": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz",
"integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz",
"integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
"dev": true
},
"fs.realpath": {
@ -941,9 +941,9 @@
"dev": true
},
"get-intrinsic": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz",
"integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.0.tgz",
"integrity": "sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
@ -1806,9 +1806,9 @@
"dev": true
},
"tsutils": {
"version": "3.19.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.1.tgz",
"integrity": "sha512-GEdoBf5XI324lu7ycad7s6laADfnAqCw6wLGI+knxvw9vsIYBaJfYdmeCEG3FMMUiSm3OGgNb+m6utsWf5h9Vw==",
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz",
"integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==",
"dev": true,
"requires": {
"tslib": "^1.8.1"

View file

@ -18,11 +18,11 @@
"@actions/io": "^1.0.2"
},
"devDependencies": {
"@redhat-actions/eslint-config": "^1.2.0",
"@redhat-actions/eslint-config": "^1.2.11",
"@redhat-actions/tsconfig": "^1.1.0",
"@types/node": "^12.12.7",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"@vercel/ncc": "^0.25.1",
"eslint": "^7.18.0",
"typescript": "^4.0.5"

View file

@ -10,11 +10,17 @@ interface ExecResult {
stderr: string;
}
interface ImageStorageCheckResult {
readonly foundTags: string[];
readonly missingTags: string[];
}
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,90 +35,141 @@ 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") || "latest";
// 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
const isPresentInPodman: boolean = await checkImageInPodman();
// check if image with all the required tags exist in Podman image storage
const podmanImageStorageCheckResult: ImageStorageCheckResult = await checkImageInPodman();
// check if image exist in Docker image storage and if exist pull the image to Podman
const isPresentInDocker: boolean = await pullImageFromDocker();
const podmanFoundTags: string[] = podmanImageStorageCheckResult.foundTags;
const podmanMissingTags: string[] = podmanImageStorageCheckResult.missingTags;
// failing if image 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.`);
if (podmanFoundTags.length > 0) {
core.info(`Tag(s) ${podmanFoundTags.join(", ")} of ${imageToPush} found in Podman image storage`);
}
if (isPresentInPodman && isPresentInDocker) {
// Log warning if few tags are found
if (podmanMissingTags.length > 0 && podmanFoundTags.length > 0) {
core.warning(`Tag(s) ${podmanMissingTags.join(", ")} of ${imageToPush} not found in Podman image storage`);
}
// 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 dockerImageStorageCheckResult: ImageStorageCheckResult = await pullImageFromDocker();
const dockerFoundTags: string[] = dockerImageStorageCheckResult.foundTags;
const dockerMissingTags: string[] = dockerImageStorageCheckResult.missingTags;
if (dockerFoundTags.length > 0) {
core.info(`Tag(s) ${dockerFoundTags.join(", ")} of ${imageToPush} found in Docker image storage`);
}
// Log warning if few tags are found
if (dockerMissingTags.length > 0 && dockerFoundTags.length > 0) {
core.warning(`Tag(s) ${dockerMissingTags.join(", ")} of ${imageToPush} not found in Docker image storage`);
}
// failing if image with any of the tag is not found in Docker as well as Podman
if (podmanMissingTags.length > 0 && dockerMissingTags.length > 0) {
throw new Error(
`Tag(s) ${podmanMissingTags.join(", ")} of ${imageToPush} not found in Podman image storage `
+ `and Tag(s) ${dockerMissingTags.join(", ")} of ${imageToPush} not found in Docker image storage.`
);
}
const allTagsinPodman: boolean = podmanFoundTags.length === tagsList.length;
const allTagsinDocker: boolean = dockerFoundTags.length === tagsList.length;
if (allTagsinPodman && allTagsinDocker) {
const isPodmanImageLatest = await isPodmanLocalImageLatest();
if (!isPodmanImageLatest) {
core.warning(`The version of ${imageToPush} in the Docker image storage is more recent `
+ `than the version in the Podman image storage. The image from the Docker image storage `
+ `will be pushed.`);
core.warning(
`The version of ${imageToPush} in the Docker image storage is more recent `
+ `than the version in the Podman image storage. The image(s) from the Docker image storage `
+ `will be pushed.`
);
imageToPush = `${dockerBaseUrl}/${imageToPush}`;
isImageFromDocker = true;
}
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 `
+ `storage will be pushed.`);
core.warning(
`The version of ${imageToPush} in the Podman image storage is more recent `
+ `than the version in the Docker image storage. The image(s) from the Podman image `
+ `storage will be pushed.`
);
}
}
else if (isPresentInDocker) {
else if (allTagsinDocker) {
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 `
+ `removed from the Podman image storage.`);
core.info(
`${imageToPush} was found in the Docker image storage, but not in the Podman `
+ `image storage. The image(s) will be pulled into Podman image storage, pushed, and then `
+ `removed from the Podman image storage.`
);
isImageFromDocker = true;
}
else {
core.info(
`${imageToPush} was found in the Podman image storage, but not in the Docker `
+ `image storage. The image(s) will be pushed from Podman image storage.`
);
}
let pushMsg = `Pushing ${imageToPush} to ${registry}`;
let pushMsg = `Pushing ${imageToPush} with tags ${tagsList.join(", ")} 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
const args = [
"push",
"--quiet",
"--digestfile",
digestFile,
"--creds",
creds,
imageToPush,
registryPath,
];
for (const tag of tagsList) {
const imageWithTag = `${imageToPush}:${tag}`;
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}:${tag}`;
// check if tls-verify is not set to null
if (tlsVerify) {
args.push(`--tls-verify=${tlsVerify}`);
const args = [
"push",
"--quiet",
"--digestfile",
digestFile,
"--creds",
creds,
imageWithTag,
registryPath,
];
// check if tls-verify is not set to null
if (tlsVerify) {
args.push(`--tls-verify=${tlsVerify}`);
}
await execute(await getPodmanPath(), args);
core.info(`Successfully pushed ${imageWithTag} to ${registryPath}`);
registryPathList.push(registryPath);
}
await execute(await getPodmanPath(), args);
core.info(`Successfully pushed ${imageToPush} to ${registryPath}.`);
core.setOutput("registry-path", registryPath);
try {
const digest = (await fs.promises.readFile(digestFile)).toString();
core.info(digest);
@ -121,41 +178,83 @@ async function run(): Promise<void> {
catch (err) {
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
}
core.setOutput("registry-paths", JSON.stringify(registryPathList));
}
async function pullImageFromDocker(): Promise<boolean> {
async function pullImageFromDocker(): Promise<ImageStorageCheckResult> {
core.info(`Checking if ${imageToPush} with tag(s) ${tagsList.join(", ")} is present in Docker image storage`);
let imageWithTag;
const foundTags: string[] = [];
const missingTags: string[] = [];
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}`;
const commandResult: ExecResult = await execute(
await getPodmanPath(),
[ "pull", `docker-daemon:${imageWithTag}` ],
{ ignoreReturnCode: true, failOnStdErr: false }
);
if (!commandResult.exitCode) {
foundTags.push(tag);
}
else {
missingTags.push(tag);
}
}
}
catch (err) {
core.info(`${imageToPush} not found in Docker image storage`);
return false;
}
}
async function checkImageInPodman(): Promise<boolean> {
// check if images exist in Podman's storage
core.info(`Checking if ${imageToPush} is in Podman image storage`);
try {
await execute(await getPodmanPath(), [ "image", "exists", imageToPush ]);
core.info(`${imageToPush} found in Podman image storage`);
return true;
}
catch (err) {
core.info(`${imageToPush} not found in Podman image storage`);
core.debug(err);
return false;
}
return {
foundTags,
missingTags,
};
}
async function checkImageInPodman(): Promise<ImageStorageCheckResult> {
// check if images exist in Podman's storage
core.info(`Checking if ${imageToPush} with tag(s) ${tagsList.join(", ")} is present in Podman image storage`);
let imageWithTag;
const foundTags: string[] = [];
const missingTags: string[] = [];
try {
for (const tag of tagsList) {
imageWithTag = `${imageToPush}:${tag}`;
const commandResult: ExecResult = await execute(
await getPodmanPath(),
[ "image", "exists", imageWithTag ],
{ ignoreReturnCode: true }
);
if (!commandResult.exitCode) {
foundTags.push(tag);
}
else {
missingTags.push(tag);
}
}
}
catch (err) {
core.debug(err);
}
return {
foundTags,
missingTags,
};
}
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 +265,7 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
const pulledImageCreationTimeStamp = await execute(await getPodmanPath(), [
"image",
"inspect",
`${dockerBaseUrl}/${imageToPush}`,
`${dockerBaseUrl}/${imageWithTag}`,
"--format",
"{{.Created}}",
]);
@ -182,7 +281,10 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
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 ]);
}
}
}