mirror of
https://github.com/redhat-actions/push-to-registry.git
synced 2025-03-31 23:07:47 +02:00
Compare commits
29 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5ed88d269c | ||
![]() |
7d220213a5 | ||
![]() |
0fa2dca2e8 | ||
![]() |
e85426e5e2 | ||
![]() |
ded55cf56a | ||
![]() |
eaa95b72b2 | ||
![]() |
9986a6552b | ||
![]() |
7c03c8a51b | ||
![]() |
66a554d6f9 | ||
![]() |
9e8327fcde | ||
![]() |
8b69cb8d8b | ||
![]() |
3f429b2fec | ||
![]() |
1d4d173244 | ||
![]() |
24868e45f3 | ||
![]() |
69e294eb20 | ||
![]() |
bdd3fe419c | ||
![]() |
167752053d | ||
![]() |
1eb06d6198 | ||
![]() |
5bcdca892e | ||
![]() |
df878026e3 | ||
![]() |
287d78ef6b | ||
![]() |
ac5a9d0fd8 | ||
![]() |
7e7aa10ef2 | ||
![]() |
c24e5a78b1 | ||
![]() |
f787883d70 | ||
![]() |
cd360521c7 | ||
![]() |
5e1b62eb0c | ||
![]() |
b5df31b070 | ||
![]() |
56f05cb637 |
21 changed files with 2882 additions and 5436 deletions
4
.github/install_latest_podman.sh
vendored
4
.github/install_latest_podman.sh
vendored
|
@ -1,7 +1,3 @@
|
|||
# https://podman.io/getting-started/installation
|
||||
. /etc/os-release
|
||||
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
curl -sSfL "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key" | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get -y upgrade
|
||||
sudo apt-get -y install podman
|
||||
|
|
65
.github/workflows/check-lowercase.yaml
vendored
Normal file
65
.github/workflows/check-lowercase.yaml
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
# 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: Check Case Normalization
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # every day at midnight
|
||||
env:
|
||||
IMAGE_NAME: ImageCaseTest
|
||||
IMAGE_TAGS: v1 TagCaseTest ${{ github.sha }}
|
||||
IMAGE_REGISTRY: Ghcr.io/${{ github.repository_owner }}
|
||||
REGISTRY_USER: ${{ github.actor }}
|
||||
REGISTRY_PASSWORD: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
push-ghcr:
|
||||
name: Build and push image
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
install_latest: [ true, false ]
|
||||
|
||||
steps:
|
||||
# Checkout push-to-registry action github repository
|
||||
- name: Checkout Push to Registry action
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest podman
|
||||
if: matrix.install_latest
|
||||
run: |
|
||||
bash .github/install_latest_podman.sh
|
||||
|
||||
# Build image using Buildah action
|
||||
- name: Build Image
|
||||
id: build_image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
base-image: busybox:latest
|
||||
entrypoint: |
|
||||
bash
|
||||
-c
|
||||
echo 'hello world'
|
||||
oci: true
|
||||
|
||||
# Push the image to GHCR (Image Registry)
|
||||
- name: Push To GHCR
|
||||
uses: ./
|
||||
id: push
|
||||
with:
|
||||
image: ${{ steps.build_image.outputs.image }}
|
||||
tags: ${{ steps.build_image.outputs.tags }}
|
||||
registry: ${{ env.IMAGE_REGISTRY }}
|
||||
username: ${{ env.REGISTRY_USER }}
|
||||
password: ${{ env.REGISTRY_PASSWORD }}
|
||||
extra-args: |
|
||||
--disable-content-trust
|
||||
|
||||
- name: Echo outputs
|
||||
run: |
|
||||
echo "${{ toJSON(steps.push.outputs) }}"
|
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
@ -9,18 +9,18 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
|
||||
check-dist:
|
||||
name: Check Distribution
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
BUNDLE_FILE: "dist/index.js"
|
||||
BUNDLE_COMMAND: "npm run bundle"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install
|
||||
run: npm ci
|
||||
|
@ -33,11 +33,11 @@ jobs:
|
|||
|
||||
check-inputs-outputs:
|
||||
name: Check Input and Output enums
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
IO_FILE: ./src/generated/inputs-outputs.ts
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
|
4
.github/workflows/ghcr-push.yaml
vendored
4
.github/workflows/ghcr-push.yaml
vendored
|
@ -17,7 +17,7 @@ env:
|
|||
jobs:
|
||||
push-ghcr:
|
||||
name: Build and push image
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -26,7 +26,7 @@ jobs:
|
|||
steps:
|
||||
# Checkout push-to-registry action github repository
|
||||
- name: Checkout Push to Registry action
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest podman
|
||||
if: matrix.install_latest
|
||||
|
|
4
.github/workflows/link_check.yml
vendored
4
.github/workflows/link_check.yml
vendored
|
@ -12,9 +12,9 @@ on:
|
|||
jobs:
|
||||
markdown-link-check:
|
||||
name: Check links in markdown
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-verbose-mode: true
|
||||
|
|
75
.github/workflows/manifest-build-push.yaml
vendored
Normal file
75
.github/workflows/manifest-build-push.yaml
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
# 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: Build and Push Manifest
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # every day at midnight
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ptr-manifest
|
||||
IMAGE_TAGS: v1 ${{ github.sha }}
|
||||
IMAGE_REGISTRY: quay.io
|
||||
IMAGE_NAMESPACE: redhat-github-actions
|
||||
|
||||
jobs:
|
||||
push-quay:
|
||||
name: Build and push manifest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
install_latest: [ true, false ]
|
||||
|
||||
steps:
|
||||
# Checkout push-to-registry action github repository
|
||||
- name: Checkout Push to Registry action
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest podman
|
||||
if: matrix.install_latest
|
||||
run: |
|
||||
bash .github/install_latest_podman.sh
|
||||
|
||||
- name: Install qemu dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user-static
|
||||
|
||||
- name: Create Containerfile
|
||||
run: |
|
||||
cat > Containerfile<<EOF
|
||||
|
||||
FROM docker.io/alpine:3.14
|
||||
|
||||
RUN echo "hello world"
|
||||
|
||||
ENTRYPOINT [ "sh", "-c", "echo -n 'Machine: ' && uname -m && echo -n 'Bits: ' && getconf LONG_BIT && echo 'goodbye world'" ]
|
||||
EOF
|
||||
|
||||
- name: Build Image
|
||||
id: build_image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
archs: amd64, arm64
|
||||
containerfiles: |
|
||||
./Containerfile
|
||||
|
||||
# Push the image manifest to Quay.io (Image Registry)
|
||||
- name: Push To Quay
|
||||
uses: ./
|
||||
id: push
|
||||
with:
|
||||
image: ${{ steps.build_image.outputs.image }}
|
||||
tags: ${{ steps.build_image.outputs.tags }}
|
||||
registry: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAMESPACE }}
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Echo outputs
|
||||
run: |
|
||||
echo "${{ toJSON(steps.push.outputs) }}"
|
4
.github/workflows/multiple-build.yaml
vendored
4
.github/workflows/multiple-build.yaml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
build:
|
||||
name: |-
|
||||
Build with ${{ matrix.build_with }} and push${{ matrix.fully_qualified_image_name_tag && ' FQIN' || '' }} (latest: ${{ matrix.install_latest }})
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -29,7 +29,7 @@ jobs:
|
|||
|
||||
# Checkout push-to-registry action github repository
|
||||
- name: Checkout Push to Registry action
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest podman
|
||||
if: matrix.install_latest
|
||||
|
|
4
.github/workflows/quay-push.yaml
vendored
4
.github/workflows/quay-push.yaml
vendored
|
@ -17,7 +17,7 @@ env:
|
|||
jobs:
|
||||
push-quay:
|
||||
name: Build and push image
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -26,7 +26,7 @@ jobs:
|
|||
steps:
|
||||
# Checkout push-to-registry action github repository
|
||||
- name: Checkout Push to Registry action
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest podman
|
||||
if: matrix.install_latest
|
||||
|
|
35
.github/workflows/security_scan.yml
vendored
Normal file
35
.github/workflows/security_scan.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: Vulnerability Scan with CRDA
|
||||
on:
|
||||
# push:
|
||||
workflow_dispatch:
|
||||
# pull_request_target:
|
||||
# types: [ assigned, opened, synchronize, reopened, labeled, edited ]
|
||||
# schedule:
|
||||
# - cron: '0 0 * * *' # every day at midnight
|
||||
|
||||
jobs:
|
||||
crda-scan:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Scan project vulnerability with CRDA
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install CRDA
|
||||
uses: redhat-actions/openshift-tools-installer@v1
|
||||
with:
|
||||
source: github
|
||||
github_pat: ${{ github.token }}
|
||||
crda: "latest"
|
||||
|
||||
- name: CRDA Scan
|
||||
id: scan
|
||||
uses: redhat-actions/crda@v1
|
||||
with:
|
||||
crda_key: ${{ secrets.CRDA_KEY }}
|
||||
fail_on: never
|
4
.github/workflows/verify-login-push.yml
vendored
4
.github/workflows/verify-login-push.yml
vendored
|
@ -17,7 +17,7 @@ env:
|
|||
jobs:
|
||||
login-and-push:
|
||||
name: Login and push image to Quay.io
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
# Checkout push-to-registry action github repository
|
||||
- name: Checkout Push to Registry action
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest podman
|
||||
if: matrix.install_latest
|
||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,5 +1,24 @@
|
|||
# push-to-registry Changelog
|
||||
|
||||
## v2.8
|
||||
- Update action to run on Node20. https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
|
||||
|
||||
## v2.7.1
|
||||
- Don't add docker.io prefix to ECR images. [#69](https://github.com/redhat-actions/push-to-registry/pull/69)
|
||||
|
||||
## v2.7
|
||||
- Update action to run on Node16. https://github.blog/changelog/2022-05-20-actions-can-now-run-in-a-node-js-16-runtime/
|
||||
|
||||
## v2.6
|
||||
- Make image and tag in lowercase, if found in uppercase. https://github.com/redhat-actions/push-to-registry/issues/54
|
||||
- Remove kubic packages from the test workflows. https://github.com/redhat-actions/buildah-build/issues/93
|
||||
|
||||
## v2.5.1
|
||||
- README update
|
||||
|
||||
## v2.5
|
||||
- Allow pushing image manifest.
|
||||
|
||||
## v2.4.1
|
||||
- Fix issue when pushing multiple tags. [#57](https://github.com/redhat-actions/push-to-registry/issues/57)
|
||||
|
||||
|
|
29
README.md
29
README.md
|
@ -6,13 +6,14 @@
|
|||
[](https://github.com/redhat-actions/push-to-registry/actions/workflows/quay-push.yaml)
|
||||
[](https://github.com/redhat-actions/push-to-registry/actions/workflows/ghcr-push.yaml)
|
||||
[](https://github.com/redhat-actions/push-to-registry/actions?query=workflow%3A%22Login+and+Push%22)
|
||||
[](https://github.com/redhat-actions/push-to-registry/actions/workflows/manifest-build-push.yaml)
|
||||
[](https://github.com/redhat-actions/push-to-registry/actions?query=workflow%3A%22Multiple+container+CLI+build+tests%22)
|
||||
<br><br>
|
||||
[](https://github.com/redhat-actions/push-to-registry/tags)
|
||||
[](./LICENSE)
|
||||
[](./dist)
|
||||
|
||||
Push-to-registry is a GitHub Action for pushing a container image to an image registry, such as Dockerhub, quay.io, the GitHub Container Registry, or an OpenShift integrated registry.
|
||||
Push-to-registry is a GitHub Action for pushing a container image or an [image manifest](https://github.com/containers/buildah/blob/main/docs/buildah-manifest.1.md) to an image registry, such as Dockerhub, quay.io, the GitHub Container Registry, or an OpenShift integrated registry.
|
||||
|
||||
This action only runs on Linux, as it uses [podman](https://github.com/containers/Podman) to perform the push. [GitHub's Ubuntu action runners](https://github.com/actions/virtual-environments#available-environments) come with Podman preinstalled. If you are not using those runners, you must first [install Podman](https://podman.io/getting-started/installation).
|
||||
|
||||
|
@ -24,8 +25,8 @@ Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-man
|
|||
|
||||
| Input Name | Description | Default |
|
||||
| ---------- | ----------- | ------- |
|
||||
| image | Name of the image you want to push. Eg. `username/imagename` or `imagename`. Refer to [Image and Tag Inputs](https://github.com/redhat-actions/push-to-registry#image-tag-inputs). | **Required** - unless all tags include registry and image name
|
||||
| tags | The tag or tags of the image to push. For multiple tags, separate by whitespace. Refer to [Image and Tag Inputs](https://github.com/redhat-actions/push-to-registry#image-tag-inputs). | `latest`
|
||||
| image | Name of the image or manifest you want to push. Eg. `username/imagename` or `imagename`. Refer to [Image and Tag Inputs](https://github.com/redhat-actions/push-to-registry#image-tag-inputs). | **Required** - unless all tags include registry and image name
|
||||
| tags | The tag or tags of the image or manifest to push. For multiple tags, separate by whitespace. Refer to [Image and Tag Inputs](https://github.com/redhat-actions/push-to-registry#image-tag-inputs). | `latest`
|
||||
| registry | Hostname and optional namespace to push the image to. Eg. `quay.io` or `quay.io/username`. Refer to [Image and Tag Inputs](https://github.com/redhat-actions/push-to-registry#image-tag-inputs). | **Required** - unless all tags include registry and image name
|
||||
| username | Username with which to authenticate to the registry. Required unless already logged in to the registry. | None
|
||||
| password | Password, encrypted password, or access token to use to log in to the registry. Required unless already logged in to the registry. | None
|
||||
|
@ -83,6 +84,12 @@ For example:
|
|||
|
||||
`registry-path`: The first element of `registry-paths`, as a string.
|
||||
|
||||
## Pushing Manifest
|
||||
|
||||
If multiple tags are provided, either all tags must point to manifests, or none of them. i.e., you cannot push both manifests are regular images in one `push-to-registry` step.
|
||||
|
||||
Refer to [Manifest Build and Push example](./.github/workflows/manifest-build-push.yaml) for a sophisticated example of building and pushing a manifest.
|
||||
|
||||
## Examples
|
||||
|
||||
The example below shows how the `push-to-registry` action can be used to push an image created by the [**buildah-build**](https://github.com/redhat-actions/buildah-build) action.
|
||||
|
@ -94,10 +101,10 @@ on: [ push ]
|
|||
jobs:
|
||||
build:
|
||||
name: Build and push image
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build Image
|
||||
id: build-image
|
||||
|
@ -105,8 +112,8 @@ jobs:
|
|||
with:
|
||||
image: my-app
|
||||
tags: latest ${{ github.sha }}
|
||||
dockerfiles: |
|
||||
./Dockerfile
|
||||
containerfiles: |
|
||||
./Containerfile
|
||||
|
||||
# Podman Login action (https://github.com/redhat-actions/podman-login) also be used to log in,
|
||||
# in which case 'username' and 'password' can be omitted.
|
||||
|
@ -123,7 +130,7 @@ jobs:
|
|||
- name: Print image url
|
||||
run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}"
|
||||
```
|
||||
|
||||
<!-- markdown-link-check-disable-next-line -->
|
||||
Refer to [GHCR push example](./.github/workflows/ghcr-push.yaml) for complete example of push to [GitHub Container Registry (GHCR)](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry).
|
||||
|
||||
## Note about images built with Docker
|
||||
|
@ -137,13 +144,13 @@ If the image to push is present in both the Docker and Podman image storage, the
|
|||
If the action pulled an image from the Docker image storage into the Podman storage, it will be cleaned up from the Podman storage before the action exits.
|
||||
|
||||
## Note about GitHub runners and Podman
|
||||
We recommend using `runs-on: ubuntu-20.04` since it has a newer version of Podman.
|
||||
We recommend using `runs-on: ubuntu-22.04` since it has a newer version of Podman.
|
||||
|
||||
If you are on `ubuntu-18.04` or any other older versions of ubuntu your workflow will use an older version of Podman and may encounter issues such as [#26](https://github.com/redhat-actions/push-to-registry/issues/26).
|
||||
If you are on `ubuntu-20.04` or any other older versions of ubuntu your workflow will use an older version of Podman and may encounter issues such as [#26](https://github.com/redhat-actions/push-to-registry/issues/26).
|
||||
|
||||
## Troubleshooting
|
||||
Note that quay.io repositories are private by default.<br>
|
||||
|
||||
This means that if you push an image for the first time, you will have to authenticate before pulling it, or go to the repository's settings and change its visibility.
|
||||
|
||||
Simiarly, if you receive a 403 Forbidden from GHCR, you may have to update the Package Settings. Refer to [this issue](https://github.com/redhat-actions/push-to-registry/issues/52).
|
||||
Similarly, if you receive a 403 Forbidden from GHCR, you may have to update the Package Settings. Refer to [this issue](https://github.com/redhat-actions/push-to-registry/issues/52).
|
||||
|
|
10
action.yml
10
action.yml
|
@ -6,10 +6,12 @@ branding:
|
|||
color: red
|
||||
inputs:
|
||||
image:
|
||||
description: 'Name of the image to push (e.g. username/imagename or imagename)'
|
||||
description: 'Name of the image/manifest to push (e.g. username/imagename or imagename)'
|
||||
required: false
|
||||
tags:
|
||||
description: 'The tag or tags of the image to push. For multiple tags, seperate by whitespace. For example, "latest v1"'
|
||||
description: |
|
||||
'The tag or tags of the image/manifest to push.
|
||||
For multiple tags, separate by whitespace. For example, "latest v1"'
|
||||
required: false
|
||||
default: 'latest'
|
||||
registry:
|
||||
|
@ -39,11 +41,11 @@ inputs:
|
|||
|
||||
outputs:
|
||||
digest:
|
||||
description: 'The pushed image digest, as written to the "digestfile"'
|
||||
description: 'The pushed image/manifest digest, as written to the "digestfile"'
|
||||
registry-path:
|
||||
description: 'The first element of registry-paths.'
|
||||
registry-paths:
|
||||
description: 'A JSON array of registry paths to which the tag(s) were pushed'
|
||||
runs:
|
||||
using: 'node12'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
|
|
4
dist/index.js
vendored
4
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
3911
dist/sourcemap-register.js
vendored
3911
dist/sourcemap-register.js
vendored
File diff suppressed because one or more lines are too long
3867
package-lock.json
generated
3867
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "push-to-registry",
|
||||
"version": "0.0.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Action to push images to registry",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -8,26 +8,27 @@
|
|||
"bundle": "ncc build src/index.ts --source-map --minify",
|
||||
"clean": "rm -rf out/ dist/",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"generate-ios": "npx action-io-generator -w -o ./src/generated/inputs-outputs.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Red Hat",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/exec": "^1.1.0",
|
||||
"@actions/io": "^1.0.2",
|
||||
"ini": "^2.0.0"
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/io": "^1.1.3",
|
||||
"ini": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@redhat-actions/action-io-generator": "^1.5.0",
|
||||
"@redhat-actions/eslint-config": "^1.3.2",
|
||||
"@redhat-actions/tsconfig": "^1.1.1",
|
||||
"@types/ini": "^1.3.30",
|
||||
"@types/node": "^12.12.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"@vercel/ncc": "^0.25.1",
|
||||
"eslint": "^7.18.0",
|
||||
"typescript": "^4.0.5"
|
||||
"@redhat-actions/tsconfig": "^1.2.0",
|
||||
"@types/ini": "^4.1.0",
|
||||
"@types/node": "^20.11.24",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.57.0",
|
||||
"typescript": "5.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export enum Inputs {
|
|||
*/
|
||||
EXTRA_ARGS = "extra-args",
|
||||
/**
|
||||
* Name of the image to push (e.g. username/imagename or imagename)
|
||||
* Name of the image/manifest to push (e.g. username/imagename or imagename)
|
||||
* Required: false
|
||||
* Default: None.
|
||||
*/
|
||||
|
@ -34,7 +34,8 @@ export enum Inputs {
|
|||
*/
|
||||
REGISTRY = "registry",
|
||||
/**
|
||||
* The tag or tags of the image to push. For multiple tags, seperate by whitespace. For example, "latest v1"
|
||||
* 'The tag or tags of the image/manifest to push.
|
||||
* For multiple tags, separate by whitespace. For example, "latest v1"'
|
||||
* Required: false
|
||||
* Default: "latest"
|
||||
*/
|
||||
|
@ -55,7 +56,7 @@ export enum Inputs {
|
|||
|
||||
export enum Outputs {
|
||||
/**
|
||||
* The pushed image digest, as written to the "digestfile"
|
||||
* The pushed image/manifest digest, as written to the "digestfile"
|
||||
* Required: false
|
||||
* Default: None.
|
||||
*/
|
||||
|
|
228
src/index.ts
228
src/index.ts
|
@ -43,7 +43,7 @@ async function getPodmanPath(): Promise<string> {
|
|||
|
||||
async function run(): Promise<void> {
|
||||
const DEFAULT_TAG = "latest";
|
||||
const imageInput = core.getInput(Inputs.IMAGE);
|
||||
const image = core.getInput(Inputs.IMAGE);
|
||||
const tags = core.getInput(Inputs.TAGS);
|
||||
// split tags
|
||||
const tagsList = tags.trim().split(/\s+/);
|
||||
|
@ -53,6 +53,21 @@ async function run(): Promise<void> {
|
|||
core.info(`Input "${Inputs.TAGS}" is not provided, using default tag "${DEFAULT_TAG}"`);
|
||||
tagsList.push(DEFAULT_TAG);
|
||||
}
|
||||
|
||||
const normalizedTagsList: string[] = [];
|
||||
let isNormalized = false;
|
||||
for (const tag of tagsList) {
|
||||
normalizedTagsList.push(tag.toLowerCase());
|
||||
if (tag.toLowerCase() !== tag) {
|
||||
isNormalized = true;
|
||||
}
|
||||
}
|
||||
const normalizedImage = image.toLowerCase();
|
||||
if (isNormalized || image !== normalizedImage) {
|
||||
core.warning(`Reference to image and/or tag must be lowercase.`
|
||||
+ ` Reference has been converted to be compliant with standard.`);
|
||||
}
|
||||
|
||||
const registry = core.getInput(Inputs.REGISTRY);
|
||||
const username = core.getInput(Inputs.USERNAME);
|
||||
const password = core.getInput(Inputs.PASSWORD);
|
||||
|
@ -60,12 +75,12 @@ async function run(): Promise<void> {
|
|||
const digestFileInput = core.getInput(Inputs.DIGESTFILE);
|
||||
|
||||
// check if all tags provided are in `image:tag` format
|
||||
const isFullImageNameTag = isFullImageName(tagsList[0]);
|
||||
if (tagsList.some((tag) => isFullImageName(tag) !== isFullImageNameTag)) {
|
||||
const isFullImageNameTag = isFullImageName(normalizedTagsList[0]);
|
||||
if (normalizedTagsList.some((tag) => isFullImageName(tag) !== isFullImageNameTag)) {
|
||||
throw new Error(`Input "${Inputs.TAGS}" cannot have a mix of full name and non full name tags`);
|
||||
}
|
||||
if (!isFullImageNameTag) {
|
||||
if (!imageInput) {
|
||||
if (!normalizedImage) {
|
||||
throw new Error(`Input "${Inputs.IMAGE}" must be provided when using non full name tags`);
|
||||
}
|
||||
if (!registry) {
|
||||
|
@ -73,28 +88,28 @@ async function run(): Promise<void> {
|
|||
}
|
||||
|
||||
const registryWithoutTrailingSlash = registry.replace(/\/$/, "");
|
||||
const registryPath = `${registryWithoutTrailingSlash}/${imageInput}`;
|
||||
core.info(`Combining image name "${imageInput}" and registry "${registry}" `
|
||||
const registryPath = `${registryWithoutTrailingSlash}/${normalizedImage}`;
|
||||
core.info(`Combining image name "${normalizedImage}" and registry "${registry}" `
|
||||
+ `to form registry path "${registryPath}"`);
|
||||
if (imageInput.indexOf("/") > -1 && registry.indexOf("/") > -1) {
|
||||
if (normalizedImage.indexOf("/") > -1 && registry.indexOf("/") > -1) {
|
||||
core.warning(`"${registryPath}" does not seem to be a valid registry path. `
|
||||
+ `The registry path should not contain more than 2 slashes. `
|
||||
+ `Refer to the Inputs section of the readme for naming image and registry.`);
|
||||
}
|
||||
|
||||
sourceImages = tagsList.map((tag) => getFullImageName(imageInput, tag));
|
||||
destinationImages = tagsList.map((tag) => getFullImageName(registryPath, tag));
|
||||
sourceImages = normalizedTagsList.map((tag) => getFullImageName(normalizedImage, tag));
|
||||
destinationImages = normalizedTagsList.map((tag) => getFullImageName(registryPath, tag));
|
||||
}
|
||||
else {
|
||||
if (imageInput) {
|
||||
if (normalizedImage) {
|
||||
core.warning(`Input "${Inputs.IMAGE}" is ignored when using full name tags`);
|
||||
}
|
||||
if (registry) {
|
||||
core.warning(`Input "${Inputs.REGISTRY}" is ignored when using full name tags`);
|
||||
}
|
||||
|
||||
sourceImages = tagsList;
|
||||
destinationImages = tagsList;
|
||||
sourceImages = normalizedTagsList;
|
||||
destinationImages = normalizedTagsList;
|
||||
}
|
||||
|
||||
const inputExtraArgsStr = core.getInput(Inputs.EXTRA_ARGS);
|
||||
|
@ -107,87 +122,92 @@ async function run(): Promise<void> {
|
|||
}
|
||||
|
||||
const registryPathList: string[] = [];
|
||||
// here
|
||||
// check if provided image is manifest or not
|
||||
const isManifest = await checkIfManifestsExists();
|
||||
|
||||
// check if image with all the required tags exist in Podman image storage
|
||||
const podmanImageStorageCheckResult: ImageStorageCheckResult = await checkImageInPodman();
|
||||
if (!isManifest) {
|
||||
// check if image with all the required tags exist in Podman image storage
|
||||
const podmanImageStorageCheckResult: ImageStorageCheckResult = await checkImageInPodman();
|
||||
|
||||
const podmanFoundTags: string[] = podmanImageStorageCheckResult.foundTags;
|
||||
const podmanMissingTags: string[] = podmanImageStorageCheckResult.missingTags;
|
||||
const podmanFoundTags: string[] = podmanImageStorageCheckResult.foundTags;
|
||||
const podmanMissingTags: string[] = podmanImageStorageCheckResult.missingTags;
|
||||
|
||||
if (podmanFoundTags.length > 0) {
|
||||
core.info(`Tag${podmanFoundTags.length !== 1 ? "s" : ""} "${podmanFoundTags.join(", ")}" `
|
||||
+ `found in Podman image storage`);
|
||||
}
|
||||
if (podmanFoundTags.length > 0) {
|
||||
core.info(`Tag${podmanFoundTags.length !== 1 ? "s" : ""} "${podmanFoundTags.join(", ")}" `
|
||||
+ `found in Podman image storage`);
|
||||
}
|
||||
|
||||
// Log warning if few tags are not found
|
||||
if (podmanMissingTags.length > 0 && podmanFoundTags.length > 0) {
|
||||
core.warning(`Tag${podmanMissingTags.length !== 1 ? "s" : ""} "${podmanMissingTags.join(", ")}" `
|
||||
+ `not found in Podman image storage`);
|
||||
}
|
||||
// Log warning if few tags are not found
|
||||
if (podmanMissingTags.length > 0 && podmanFoundTags.length > 0) {
|
||||
core.warning(`Tag${podmanMissingTags.length !== 1 ? "s" : ""} "${podmanMissingTags.join(", ")}" `
|
||||
+ `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();
|
||||
// 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;
|
||||
const dockerFoundTags: string[] = dockerImageStorageCheckResult.foundTags;
|
||||
const dockerMissingTags: string[] = dockerImageStorageCheckResult.missingTags;
|
||||
|
||||
if (dockerFoundTags.length > 0) {
|
||||
core.info(`Tag${dockerFoundTags.length !== 1 ? "s" : ""} "${dockerFoundTags.join(", ")}" `
|
||||
+ `found in Docker image storage`);
|
||||
}
|
||||
if (dockerFoundTags.length > 0) {
|
||||
core.info(`Tag${dockerFoundTags.length !== 1 ? "s" : ""} "${dockerFoundTags.join(", ")}" `
|
||||
+ `found in Docker image storage`);
|
||||
}
|
||||
|
||||
// Log warning if few tags are not found
|
||||
if (dockerMissingTags.length > 0 && dockerFoundTags.length > 0) {
|
||||
core.warning(`Tag${dockerMissingTags.length !== 1 ? "s" : ""} "${dockerMissingTags.join(", ")}" `
|
||||
+ `not found in Docker image storage`);
|
||||
}
|
||||
// Log warning if few tags are not found
|
||||
if (dockerMissingTags.length > 0 && dockerFoundTags.length > 0) {
|
||||
core.warning(`Tag${dockerMissingTags.length !== 1 ? "s" : ""} "${dockerMissingTags.join(", ")}" `
|
||||
+ `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(
|
||||
`❌ All tags were not found in either Podman image storage, or Docker image storage. `
|
||||
+ `Tag${podmanMissingTags.length !== 1 ? "s" : ""} "${podmanMissingTags.join(", ")}" `
|
||||
+ `not found in Podman image storage, and tag${dockerMissingTags.length !== 1 ? "s" : ""} `
|
||||
+ `"${dockerMissingTags.join(", ")}" 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(
|
||||
`❌ All tags were not found in either Podman image storage, or Docker image storage. `
|
||||
+ `Tag${podmanMissingTags.length !== 1 ? "s" : ""} "${podmanMissingTags.join(", ")}" `
|
||||
+ `not found in Podman image storage, and tag${dockerMissingTags.length !== 1 ? "s" : ""} `
|
||||
+ `"${dockerMissingTags.join(", ")}" not found in Docker image storage.`
|
||||
);
|
||||
}
|
||||
|
||||
const allTagsinPodman: boolean = podmanFoundTags.length === tagsList.length;
|
||||
const allTagsinDocker: boolean = dockerFoundTags.length === tagsList.length;
|
||||
const allTagsinPodman: boolean = podmanFoundTags.length === normalizedTagsList.length;
|
||||
const allTagsinDocker: boolean = dockerFoundTags.length === normalizedTagsList.length;
|
||||
|
||||
if (allTagsinPodman && allTagsinDocker) {
|
||||
const isPodmanImageLatest = await isPodmanLocalImageLatest();
|
||||
if (!isPodmanImageLatest) {
|
||||
core.warning(
|
||||
`The version of "${sourceImages[0]}" 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.`
|
||||
if (allTagsinPodman && allTagsinDocker) {
|
||||
const isPodmanImageLatest = await isPodmanLocalImageLatest();
|
||||
if (!isPodmanImageLatest) {
|
||||
core.warning(
|
||||
`The version of "${sourceImages[0]}" 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.`
|
||||
);
|
||||
isImageFromDocker = true;
|
||||
}
|
||||
else {
|
||||
core.warning(
|
||||
`The version of "${sourceImages[0]}" 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 (allTagsinDocker) {
|
||||
core.info(
|
||||
`Tag "${sourceImages[0]}" 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.warning(
|
||||
`The version of "${sourceImages[0]}" 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.`
|
||||
core.info(
|
||||
`Tag "${sourceImages[0]}" was found in the Podman image storage, but not in the Docker `
|
||||
+ `image storage. The image(s) will be pushed from Podman image storage.`
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (allTagsinDocker) {
|
||||
core.info(
|
||||
`Tag "${sourceImages[0]}" 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(
|
||||
`Tag "${sourceImages[0]}" 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 "${sourceImages.join(", ")}" to "${destinationImages.join(", ")}" respectively`;
|
||||
if (username) {
|
||||
|
@ -216,16 +236,25 @@ async function run(): Promise<void> {
|
|||
|
||||
// push the image
|
||||
for (let i = 0; i < destinationImages.length; i++) {
|
||||
const args = [
|
||||
...(isImageFromDocker ? dockerPodmanOpts : []),
|
||||
const args = [];
|
||||
if (isImageFromDocker) {
|
||||
args.push(...dockerPodmanOpts);
|
||||
}
|
||||
if (isManifest) {
|
||||
args.push("manifest");
|
||||
}
|
||||
args.push(...[
|
||||
"push",
|
||||
"--quiet",
|
||||
"--digestfile",
|
||||
digestFile,
|
||||
isImageFromDocker ? getFullDockerImageName(sourceImages[i]) : sourceImages[i],
|
||||
destinationImages[i],
|
||||
];
|
||||
|
||||
]);
|
||||
// to push all the images referenced in the manifest
|
||||
if (isManifest) {
|
||||
args.push("--all");
|
||||
}
|
||||
if (podmanExtraArgs.length > 0) {
|
||||
args.push(...podmanExtraArgs);
|
||||
}
|
||||
|
@ -281,7 +310,9 @@ async function pullImageFromDocker(): Promise<ImageStorageCheckResult> {
|
|||
}
|
||||
}
|
||||
catch (err) {
|
||||
core.warning(err);
|
||||
if (err instanceof Error) {
|
||||
core.debug(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -311,7 +342,9 @@ async function checkImageInPodman(): Promise<ImageStorageCheckResult> {
|
|||
}
|
||||
}
|
||||
catch (err) {
|
||||
core.debug(err);
|
||||
if (err instanceof Error) {
|
||||
core.debug(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -392,6 +425,39 @@ async function removeDockerPodmanImageStroage(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function checkIfManifestsExists(): Promise<boolean> {
|
||||
const foundManifests = [];
|
||||
const missingManifests = [];
|
||||
// check if manifest exist in Podman's storage
|
||||
core.info(`🔍 Checking if the given image is manifest or not.`);
|
||||
for (const manifest of sourceImages) {
|
||||
const commandResult: ExecResult = await execute(
|
||||
await getPodmanPath(),
|
||||
[ "manifest", "exists", manifest ],
|
||||
{ ignoreReturnCode: true, group: true }
|
||||
);
|
||||
if (commandResult.exitCode === 0) {
|
||||
foundManifests.push(manifest);
|
||||
}
|
||||
else {
|
||||
missingManifests.push(manifest);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundManifests.length > 0) {
|
||||
core.info(`Image${foundManifests.length !== 1 ? "s" : ""} "${foundManifests.join(", ")}" `
|
||||
+ `${foundManifests.length !== 1 ? "are manifests" : "is a manifest"}.`);
|
||||
}
|
||||
|
||||
if (foundManifests.length > 0 && missingManifests.length > 0) {
|
||||
throw new Error(`Manifest${missingManifests.length !== 1 ? "s" : ""} "${missingManifests.join(", ")}" `
|
||||
+ `not found in the Podman image storage. Make sure that all the provided images are either `
|
||||
+ `manifests or container images.`);
|
||||
}
|
||||
|
||||
return foundManifests.length === sourceImages.length;
|
||||
}
|
||||
|
||||
async function execute(
|
||||
executable: string,
|
||||
args: string[],
|
||||
|
|
|
@ -54,7 +54,9 @@ export async function findFuseOverlayfsPath(): Promise<string | undefined> {
|
|||
fuseOverlayfsPath = await io.which("fuse-overlayfs");
|
||||
}
|
||||
catch (err) {
|
||||
core.debug(err);
|
||||
if (err instanceof Error) {
|
||||
core.debug(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return fuseOverlayfsPath;
|
||||
|
@ -83,6 +85,7 @@ export function getFullDockerImageName(image: string): string {
|
|||
case 1:
|
||||
return `${DOCKER_IO_NAMESPACED}/${image}`;
|
||||
case 2:
|
||||
if (image.includes("amazonaws.com")) return image;
|
||||
return `${DOCKER_IO}/${image}`;
|
||||
default:
|
||||
return image;
|
||||
|
|
Loading…
Add table
Reference in a new issue