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 # 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, pull_request_target ]
env: env:
IMAGE_NAME: myimage PROJECT_DIR: spring-petclinic
IMAGE_NAME: spring-petclinic
IMAGE_REGISTRY: quay.io IMAGE_REGISTRY: quay.io
IMAGE_TAG: latest IMAGE_TAGS: v1 ${{ github.sha }}
MVN_REPO_DIR: ~/.m2/repository
jobs: jobs:
build: build-and-push:
name: Push image to Quay.io name: Build and push image to Quay.io
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
# Checkout push-to-registry action github repository # Checkout push-to-registry action github repository
- name: Checkout Push to Registry action - name: Checkout Push to Registry action
uses: actions/checkout@v2 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: | run: |
docker build -t ${{ env.IMAGE_NAME }}:latest -<<EOF echo "MVN_HASH=${{ hashFiles('**/pom.xml', '.mvn/**/*', 'mvnw*') }}" >> $GITHUB_ENV
FROM busybox
RUN echo "hello world"
EOF
# Push the image to image registry # Download the m2 repository from the cache to speed up the build.
- name: Push To Quay - name: Check for Maven cache
uses: ./ id: check-mvn-cache
id: push uses: actions/cache@v2
with:
path: ${{ env.MVN_REPO_DIR }}
key: ${{ env.MVN_HASH }}
# 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: with:
image: ${{ env.IMAGE_NAME }} 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 }} 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 }}"

View file

@ -31,10 +31,10 @@ Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-man
</tr> </tr>
<tr> <tr>
<td>tag</td> <td>tags</td>
<td>No</td> <td>No</td>
<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>. Defaults to <code>latest</code>.
</td> </td>
</tr> </tr>
@ -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`: A JSON array 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:latest ]`.
`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, the digest is the same.
## Examples ## Examples
@ -93,16 +94,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
IMAGE_NAME: my-app IMAGE_NAME: my-app
IMAGE_TAG: latest IMAGE_TAGS: latest v1
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Build Image - name: Build Image
id: build-image
uses: redhat-actions/buildah-build@v1 uses: redhat-actions/buildah-build@v1
with: with:
image: ${{ env.IMAGE_NAME }} image: ${{ env.IMAGE_NAME }}
tag: ${{ env.TAG }} tags: ${{ env.IMAGE_TAGS }}
base-image: some_image
dockerfiles: | dockerfiles: |
./Dockerfile ./Dockerfile
@ -110,8 +113,8 @@ jobs:
id: push-to-quay id: push-to-quay
uses: redhat-actions/push-to-registry@v1 uses: redhat-actions/push-to-registry@v1
with: with:
image: ${{ env.IMAGE_NAME }} image: ${{ steps.build-image.outputs.image }}
tag: ${{ env.TAG }} tags: ${{ steps.build-image.outputs.tags }}
registry: ${{ secrets.QUAY_REPO }} registry: ${{ secrets.QUAY_REPO }}
username: ${{ secrets.QUAY_USERNAME }} username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }} password: ${{ secrets.QUAY_TOKEN }}

View file

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

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

View file

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

View file

@ -10,11 +10,17 @@ interface ExecResult {
stderr: string; stderr: string;
} }
interface ImageStorageCheckResult {
readonly foundTags: string[];
readonly missingTags: string[];
}
let podmanPath: string | undefined; 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,69 +35,119 @@ 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") || "latest";
// 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 podmanImageStorageCheckResult: ImageStorageCheckResult = await checkImageInPodman();
// check if image exist in Docker image storage and if exist pull the image to Podman const podmanFoundTags: string[] = podmanImageStorageCheckResult.foundTags;
const isPresentInDocker: boolean = await pullImageFromDocker(); const podmanMissingTags: string[] = podmanImageStorageCheckResult.missingTags;
// failing if image is not found in Docker as well as Podman if (podmanFoundTags.length > 0) {
if (!isPresentInDocker && !isPresentInPodman) { core.info(`Tag(s) ${podmanFoundTags.join(", ")} of ${imageToPush} found in Podman image storage`);
throw new Error(`${imageToPush} not found in Podman local storage, or Docker local 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(); const isPodmanImageLatest = await isPodmanLocalImageLatest();
if (!isPodmanImageLatest) { if (!isPodmanImageLatest) {
core.warning(`The version of ${imageToPush} in the Docker image storage is more recent ` core.warning(
+ `than the version in the Podman image storage. The image from the Docker image storage ` `The version of ${imageToPush} in the Docker image storage is more recent `
+ `will be pushed.`); + `than the version in the Podman image storage. The image(s) from the Docker image storage `
+ `will be pushed.`
);
imageToPush = `${dockerBaseUrl}/${imageToPush}`; imageToPush = `${dockerBaseUrl}/${imageToPush}`;
isImageFromDocker = true; isImageFromDocker = true;
} }
else { else {
core.warning(`The version of ${imageToPush} in the Podman image storage is more recent ` core.warning(
+ `than the version in the Docker image storage. The image from the Podman image ` `The version of ${imageToPush} in the Podman image storage is more recent `
+ `storage will be pushed.`); + `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}`; imageToPush = `${dockerBaseUrl}/${imageToPush}`;
core.info(`${imageToPush} was found in the Docker image storage, but not in the Podman ` core.info(
+ `image storage. The image will be pulled into Podman image storage, pushed, and then ` `${imageToPush} was found in the Docker image storage, but not in the Podman `
+ `removed from the Podman image storage.`); + `image storage. The image(s) will be pulled into Podman image storage, pushed, and then `
+ `removed from the Podman image storage.`
);
isImageFromDocker = true; 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) { 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 +155,7 @@ async function run(): Promise<void> {
digestFile, digestFile,
"--creds", "--creds",
creds, creds,
imageToPush, imageWithTag,
registryPath, registryPath,
]; ];
@ -109,9 +165,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 +178,83 @@ 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", 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 { 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; 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) { 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); 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> { 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 +265,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}}",
]); ]);
@ -182,7 +281,10 @@ async function isPodmanLocalImageLatest(): Promise<boolean> {
async function removeDockerImage(): Promise<void> { async function removeDockerImage(): Promise<void> {
if (imageToPush) { if (imageToPush) {
core.info(`Removing ${imageToPush} from the Podman image storage`); 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 ]);
}
} }
} }