mirror of
https://github.com/redhat-actions/push-to-registry.git
synced 2025-02-22 10:11:21 +01:00
Add feature to push manifest (#55)
* Add feature to push manifest Signed-off-by: divyansh42 <diagrawa@redhat.com>
This commit is contained in:
parent
3220bde582
commit
56f05cb637
7 changed files with 209 additions and 78 deletions
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-20.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@v2
|
||||
|
||||
- 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@main
|
||||
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) }}"
|
12
README.md
12
README.md
|
@ -12,7 +12,7 @@
|
|||
[](./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 +24,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 +83,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.
|
||||
|
|
|
@ -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, seperate by whitespace. For example, "latest v1"'
|
||||
required: false
|
||||
default: 'latest'
|
||||
registry:
|
||||
|
@ -39,7 +41,7 @@ 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:
|
||||
|
|
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
|
@ -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, seperate 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.
|
||||
*/
|
||||
|
|
181
src/index.ts
181
src/index.ts
|
@ -107,87 +107,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 === tagsList.length;
|
||||
const allTagsinDocker: boolean = dockerFoundTags.length === tagsList.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 +221,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);
|
||||
}
|
||||
|
@ -392,6 +406,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[],
|
||||
|
|
Loading…
Add table
Reference in a new issue