mirror of
https://github.com/redhat-actions/push-to-registry.git
synced 2025-04-20 14:36:18 +02:00
Compare commits
No commits in common. "main" and "v0.1" have entirely different histories.
26 changed files with 4189 additions and 4558 deletions
|
@ -1,6 +0,0 @@
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
module.exports = {
|
|
||||||
extends: [
|
|
||||||
"@redhat-actions/eslint-config",
|
|
||||||
],
|
|
||||||
};
|
|
3
.github/install_latest_podman.sh
vendored
3
.github/install_latest_podman.sh
vendored
|
@ -1,3 +0,0 @@
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y upgrade
|
|
||||||
sudo apt-get -y install podman
|
|
65
.github/workflows/check-lowercase.yaml
vendored
65
.github/workflows/check-lowercase.yaml
vendored
|
@ -1,65 +0,0 @@
|
||||||
# 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) }}"
|
|
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
|
@ -1,48 +0,0 @@
|
||||||
name: CI checks
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Run ESLint
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run lint
|
|
||||||
|
|
||||||
check-dist:
|
|
||||||
name: Check Distribution
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
env:
|
|
||||||
BUNDLE_FILE: "dist/index.js"
|
|
||||||
BUNDLE_COMMAND: "npm run bundle"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Verify Latest Bundle
|
|
||||||
uses: redhat-actions/common/bundle-verifier@v1
|
|
||||||
with:
|
|
||||||
bundle_file: ${{ env.BUNDLE_FILE }}
|
|
||||||
bundle_command: ${{ env.BUNDLE_COMMAND }}
|
|
||||||
|
|
||||||
check-inputs-outputs:
|
|
||||||
name: Check Input and Output enums
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
env:
|
|
||||||
IO_FILE: ./src/generated/inputs-outputs.ts
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Verify Input and Output enums
|
|
||||||
uses: redhat-actions/common/action-io-generator@v1
|
|
||||||
with:
|
|
||||||
io_file: ${{ env.IO_FILE }}
|
|
65
.github/workflows/ghcr-push.yaml
vendored
65
.github/workflows/ghcr-push.yaml
vendored
|
@ -1,65 +0,0 @@
|
||||||
# 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: Push to GHCR
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: ptr-test
|
|
||||||
IMAGE_TAGS: v1 ${{ 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) }}"
|
|
20
.github/workflows/link_check.yml
vendored
20
.github/workflows/link_check.yml
vendored
|
@ -1,20 +0,0 @@
|
||||||
name: Link checker
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '**.md'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**.md'
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
markdown-link-check:
|
|
||||||
name: Check links in markdown
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- 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
75
.github/workflows/manifest-build-push.yaml
vendored
|
@ -1,75 +0,0 @@
|
||||||
# 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) }}"
|
|
75
.github/workflows/multiple-build.yaml
vendored
75
.github/workflows/multiple-build.yaml
vendored
|
@ -1,75 +0,0 @@
|
||||||
name: Multiple container CLI build tests
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE_REGISTRY: quay.io
|
|
||||||
IMAGE_NAMESPACE: redhat-github-actions
|
|
||||||
IMAGE_NAME: ptr-test
|
|
||||||
IMAGE_TAG: v1
|
|
||||||
SHORT_IMAGE_NAME_TAG: ptr-test:v1
|
|
||||||
FULLY_QUALIFIED_IMAGE_NAME_TAG: quay.io/redhat-github-actions/ptr-test:v1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: |-
|
|
||||||
Build with ${{ matrix.build_with }} and push${{ matrix.fully_qualified_image_name_tag && ' FQIN' || '' }} (latest: ${{ matrix.install_latest }})
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
install_latest: [ true, false ]
|
|
||||||
build_with: [ "docker after podman", "podman after docker", "podman only", "docker only" ]
|
|
||||||
fully_qualified_image_name_tag: [ 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: Build image using Docker
|
|
||||||
if: endsWith(matrix.build_with, 'docker')
|
|
||||||
run: |
|
|
||||||
docker build -t ${{ matrix.fully_qualified_image_name_tag && env.FULLY_QUALIFIED_IMAGE_NAME_TAG || env.SHORT_IMAGE_NAME_TAG }} -<<EOF
|
|
||||||
FROM busybox
|
|
||||||
RUN echo "hello world"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Build image using Podman
|
|
||||||
if: contains(matrix.build_with, 'podman')
|
|
||||||
run: |
|
|
||||||
podman build -t ${{ matrix.fully_qualified_image_name_tag && env.FULLY_QUALIFIED_IMAGE_NAME_TAG || env.SHORT_IMAGE_NAME_TAG }} -<<EOF
|
|
||||||
FROM busybox
|
|
||||||
RUN echo "hello world"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Build image using Docker
|
|
||||||
if: startsWith(matrix.build_with, 'docker')
|
|
||||||
run: |
|
|
||||||
docker build -t ${{ matrix.fully_qualified_image_name_tag && env.FULLY_QUALIFIED_IMAGE_NAME_TAG || env.SHORT_IMAGE_NAME_TAG }} -<<EOF
|
|
||||||
FROM busybox
|
|
||||||
RUN echo "hello world"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Push image to ${{ env.IMAGE_REGISTRY }}
|
|
||||||
id: push
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
image: ${{ env.IMAGE_NAME }}
|
|
||||||
tags: ${{ matrix.fully_qualified_image_name_tag && env.FULLY_QUALIFIED_IMAGE_NAME_TAG || env.IMAGE_TAG }}
|
|
||||||
registry: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAMESPACE }}
|
|
||||||
username: ${{ secrets.REGISTRY_USER }}
|
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Echo outputs
|
|
||||||
run: |
|
|
||||||
echo "${{ toJSON(steps.push.outputs) }}"
|
|
65
.github/workflows/quay-push.yaml
vendored
65
.github/workflows/quay-push.yaml
vendored
|
@ -1,65 +0,0 @@
|
||||||
# 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: Push to Quay.io
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: ptr-test
|
|
||||||
IMAGE_TAGS: v1 ${{ github.sha }}
|
|
||||||
IMAGE_REGISTRY: quay.io
|
|
||||||
IMAGE_NAMESPACE: redhat-github-actions
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
push-quay:
|
|
||||||
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 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 }}
|
|
||||||
extra-args: |
|
|
||||||
--disable-content-trust
|
|
||||||
|
|
||||||
- name: Echo outputs
|
|
||||||
run: |
|
|
||||||
echo "${{ toJSON(steps.push.outputs) }}"
|
|
35
.github/workflows/security_scan.yml
vendored
35
.github/workflows/security_scan.yml
vendored
|
@ -1,35 +0,0 @@
|
||||||
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
|
|
22
.github/workflows/verify-bundle.yml
vendored
Normal file
22
.github/workflows/verify-bundle.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Verify Bundle
|
||||||
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify-bundle:
|
||||||
|
name: Verify Distribution Bundle
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
env:
|
||||||
|
DEFAULT_BRANCH: main
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Check Distribution
|
||||||
|
uses: tetchel/bundle-verifier-action@v0.0.2
|
||||||
|
with:
|
||||||
|
bundle_file: dist/index.js
|
||||||
|
bundle_command: "npm run bundle"
|
76
.github/workflows/verify-login-push.yml
vendored
76
.github/workflows/verify-login-push.yml
vendored
|
@ -1,76 +0,0 @@
|
||||||
# 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: Login and Push
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE_REGISTRY: quay.io
|
|
||||||
IMAGE_NAMESPACE: redhat-github-actions
|
|
||||||
IMAGE_NAME: ptr-test
|
|
||||||
IMAGE_TAGS: v1 ${{ github.sha }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
login-and-push:
|
|
||||||
name: Login and push image to Quay.io
|
|
||||||
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: Create Dockerfile
|
|
||||||
run: |
|
|
||||||
cat > Dockerfile<<EOF
|
|
||||||
FROM busybox
|
|
||||||
RUN echo "hello world"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Build image using Buildah action
|
|
||||||
- name: Build Image
|
|
||||||
id: build_image
|
|
||||||
uses: redhat-actions/buildah-build@v2
|
|
||||||
with:
|
|
||||||
image: ${{ env.IMAGE_NAME }}
|
|
||||||
layers: false
|
|
||||||
tags: ${{ env.IMAGE_TAGS }}
|
|
||||||
dockerfiles: |
|
|
||||||
./Dockerfile
|
|
||||||
|
|
||||||
# Authenticate to container image registry to push the image
|
|
||||||
- name: Podman Login
|
|
||||||
uses: redhat-actions/podman-login@v1
|
|
||||||
with:
|
|
||||||
registry: quay.io
|
|
||||||
username: ${{ secrets.REGISTRY_USER }}
|
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
|
||||||
|
|
||||||
# Push the image 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 }}
|
|
||||||
extra-args: |
|
|
||||||
--disable-content-trust
|
|
||||||
|
|
||||||
- name: Echo outputs
|
|
||||||
run: |
|
|
||||||
echo "${{ toJSON(steps.push.outputs) }}"
|
|
53
.github/workflows/verify-push.yaml
vendored
Normal file
53
.github/workflows/verify-push.yaml
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# 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
|
||||||
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: hello-world
|
||||||
|
IMAGE_REGISTRY: quay.io
|
||||||
|
IMAGE_TAG: latest
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Push image to Quay.io
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Fetch name of the Forked Repository with Branch
|
||||||
|
# if workflow is triggered from pull request
|
||||||
|
- name: Fetch PR head repo and branch name
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
run: |
|
||||||
|
HEAD_REPO_NAME=$(jq -r '.pull_request.head.repo.full_name' "$GITHUB_EVENT_PATH")
|
||||||
|
echo "PR head repo: $HEAD_REPO_NAME"
|
||||||
|
echo "repo=$HEAD_REPO_NAME" >> $GITHUB_ENV
|
||||||
|
echo "branch=$GITHUB_HEAD_REF" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Extract repository name with branch
|
||||||
|
- name: Fetch Repository name with branch
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "repo=$GITHUB_REPOSITORY" >> $GITHUB_ENV
|
||||||
|
echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# Checkout push-to-registry action github repository
|
||||||
|
- name: Checkout Push to Registry action
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: ${{ env.repo }}
|
||||||
|
ref: ${{ env.branch }}
|
||||||
|
|
||||||
|
# Pull hello-world image to push in next step
|
||||||
|
- name: Pull Hello world image
|
||||||
|
run: docker pull ${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
# Push the image to image registry
|
||||||
|
- name: Push To Quay
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
image: ${{ env.IMAGE_NAME }}
|
||||||
|
tag: ${{ env.IMAGE_TAG }}
|
||||||
|
registry: ${{ env.IMAGE_REGISTRY }}/${{ secrets.REGISTRY_USER }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
72
CHANGELOG.md
72
CHANGELOG.md
|
@ -1,72 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
||||||
## v2.4
|
|
||||||
- Allow fully qualified image names in `tags` input, for compatibility with [docker/metadata-action`](https://github.com/docker/metadata-action). [#50](https://github.com/redhat-actions/push-to-registry/pull/50)
|
|
||||||
- Fix issue where image pulled from Docker storage would overwrite image in Podman storage [733d8e9](https://github.com/redhat-actions/buildah-build/commit/733d8e9a389084e2f8c441f0a568e5d467497557)
|
|
||||||
|
|
||||||
## v2.3.2
|
|
||||||
- Add the word `local` to the image check messages.
|
|
||||||
- Add matrix to install latest podman. (Internal)
|
|
||||||
- Simplify push tests. (Internal)
|
|
||||||
|
|
||||||
## v2.3.1
|
|
||||||
- Fix issue if image is present in docker storage and it's name has '/' in it.
|
|
||||||
- Fix outputs `registry_path` and `registry_paths` not consisting of image tag.
|
|
||||||
|
|
||||||
## v2.3
|
|
||||||
- Warn users if input `image` and `registry` both has `/` in it's name.
|
|
||||||
- Update README to better explain inputs `image` and `registry`
|
|
||||||
|
|
||||||
## v2.2
|
|
||||||
- Make input `username` and `password` optional, so that user can skip if they are already logged in to container image registry.
|
|
||||||
|
|
||||||
## v2.1.1
|
|
||||||
- Add output message if input `tags` is not provided
|
|
||||||
- Modify output message if tag(s) are not found
|
|
||||||
|
|
||||||
## v2.1
|
|
||||||
- Add output `registy-path` to output first element of `registry-paths`
|
|
||||||
- Print image digest after every push to verify image digest for each tag
|
|
||||||
- Print `podman version` at start of the action to verify that required version is being used
|
|
||||||
- (Internal) Add `Link checker` workflow to identify dangling links
|
|
||||||
|
|
||||||
## v2
|
|
||||||
- Rename `tag` input to `tags`, to allow you to push multiple tags of the same image
|
|
||||||
- Add input `extra_args` to append arbitrary arguments to the `podman push`
|
|
||||||
- Rename `registry-path` output to `registry-paths`, which is a JSON-parseable array containing all registry paths of the pushed image. The size of the output array is the number of `tags` that were pushed.
|
|
||||||
- (Internal) Add test workflows to test build and push using multiple container CLIs (Podman and Docker)
|
|
||||||
- (Internal) Add CI checks to the action that includes ESlint, bundle verifier and IO checker
|
|
||||||
|
|
||||||
## v1.2
|
|
||||||
- Solve issue when image is present in Podman and Docker both
|
|
||||||
|
|
||||||
## v1.1
|
|
||||||
- Add digestfile input and output argument
|
|
||||||
|
|
||||||
## v1.0
|
|
||||||
- Initial marketplace release
|
|
||||||
|
|
||||||
## v0.1
|
|
||||||
- Initial pre-release
|
|
193
README.md
193
README.md
|
@ -1,156 +1,129 @@
|
||||||
# push-to-registry
|
# push-to-registry
|
||||||
|
|
||||||
[](https://github.com/redhat-actions/push-to-registry/actions?query=workflow%3A%22CI+checks%22)
|
[](https://github.com/redhat-actions/push-to-registry/actions?query=workflow%3A%22Verify+Bundle%22)
|
||||||
[](https://github.com/redhat-actions/push-to-registry/actions?query=workflow%3A%22Link+checker%22)
|
[](https://github.com/redhat-actions/push-to-registry/tags)
|
||||||
<br><br>
|
|
||||||
[](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)
|
[](./LICENSE)
|
||||||
[](./dist)
|
[](./dist)
|
||||||
|
|
||||||
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.
|
Push-to-registry is a GitHub Action for pushing a container image to an image registry, such as Dockerhub, Quay.io, 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).
|
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).
|
||||||
|
|
||||||
You can log in to your container registry for the entire job using the [**podman-login**](https://github.com/redhat-actions/podman-login) action. Otherwise, use the `username` and `password` inputs to log in for this step.
|
|
||||||
|
|
||||||
## Action Inputs
|
## Action Inputs
|
||||||
|
|
||||||
Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-manifest-push.1.html) documentation for more information.
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Input</th>
|
||||||
|
<th>Required</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
| Input Name | Description | Default |
|
<tr>
|
||||||
| ---------- | ----------- | ------- |
|
<td>image</td>
|
||||||
| 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
|
<td>Yes</td>
|
||||||
| 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`
|
<td>
|
||||||
| 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
|
Name of the image you want to push.
|
||||||
| username | Username with which to authenticate to the registry. Required unless already logged in to the registry. | None
|
</td>
|
||||||
| password | Password, encrypted password, or access token to use to log in to the registry. Required unless already logged in to the registry. | None
|
</tr>
|
||||||
| tls-verify | Verify TLS certificates when contacting the registry. Set to `false` to skip certificate verification. | `true`
|
|
||||||
| digestfile | After copying the image, write the digest of the resulting image to the file. The contents of this file are the digest output. | Auto-generated from image and tag
|
|
||||||
| extra-args | Extra args to be passed to podman push. Separate arguments by newline. Do not use quotes. | None
|
|
||||||
|
|
||||||
<a id="image-tag-inputs"></a>
|
<tr>
|
||||||
|
<td>tag</td>
|
||||||
|
<td>No</td>
|
||||||
|
<td>
|
||||||
|
Image tag to push.<br>
|
||||||
|
Defaults to <code>latest</code>.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
### Image, Tag and Registry Inputs
|
<tr>
|
||||||
The **push-to-registry** `image` and `tag` input work very similarly to [**buildah-build**](https://github.com/redhat-actions/buildah-build#image-tag-inputs).
|
<td>registry</td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>URL of the registry to push the image to.<br>
|
||||||
|
Eg. <code>quay.io/<username></code></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
However, when using **push-to-registry** when the `tags` input are not fully qualified, the `registry` input must also be set.
|
<tr>
|
||||||
|
<td>username</td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>Username with which to authenticate to the registry.</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
So, for **push-to-registry** the options are as follows:
|
<tr>
|
||||||
|
<td>password</td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>Password, encrypted password, or access token with which to authenticate to the registry.</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
**Option 1**: Provide `registry`, `image`, and `tags` inputs. The image(s) will be pushed to `${registry}/${image}:${tag}`.
|
<tr>
|
||||||
|
<td>tls-verify</td>
|
||||||
For example:
|
<td>No</td>
|
||||||
```yaml
|
<td>Verify TLS certificates when contacting the registry. Set to "false" to skip certificate verification.</td>
|
||||||
registry: quay.io/my-namespace
|
</tr>
|
||||||
image: my-image
|
</table>
|
||||||
tags: v1 v1.0.0
|
|
||||||
```
|
|
||||||
will push the image tags: `quay.io/my-namespace/my-image:v1` and `quay.io/my-namespace/my-image:v1.0.0`.
|
|
||||||
|
|
||||||
**Option 2**: Provide only the `tags` input, including the fully qualified image name in each tag. In this case, the `registry` and `image` inputs are ignored.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
```yaml
|
|
||||||
# 'registry' and 'image' inputs are not set
|
|
||||||
tags: quay.io/my-namespace/my-image:v1 quay.io/my-namespace/my-image:v1.0.0
|
|
||||||
```
|
|
||||||
will push the image tags: `quay.io/my-namespace/my-image:v1` and `quay.io/my-namespace/my-image:v1.0.0`.
|
|
||||||
|
|
||||||
If the `tags` input does not have image names in the `${registry}/${name}:${tag}` form, then the `registry` and `image` inputs must be set.
|
|
||||||
|
|
||||||
## Action Outputs
|
## Action Outputs
|
||||||
|
|
||||||
`digest`: The pushed image digest, as written to the `digestfile`.<br>
|
This action produces one output which can be referenced in other workflow `steps`.
|
||||||
For example:
|
|
||||||
```
|
|
||||||
sha256:66ce924069ec4181725d15aa27f34afbaf082f434f448dc07a42daa3305cdab3
|
|
||||||
```
|
|
||||||
|
|
||||||
For multiple tags, the digest is the same.
|
`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" ]
|
|
||||||
```
|
|
||||||
|
|
||||||
`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
|
## 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.
|
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.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: Build and Push Image
|
name: Build and Push Image
|
||||||
on: [ push ]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and push image
|
name: Build and push image
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: my-app
|
||||||
|
IMAGE_TAG: latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Build Image
|
- name: Build Image
|
||||||
id: build-image
|
uses: redhat-actions/buildah-build@v1
|
||||||
uses: redhat-actions/buildah-build@v2
|
|
||||||
with:
|
with:
|
||||||
image: my-app
|
image: ${{ env.IMAGE_NAME }}
|
||||||
tags: latest ${{ github.sha }}
|
tag: ${{ env.TAG }}
|
||||||
containerfiles: |
|
dockerfiles: |
|
||||||
./Containerfile
|
./Dockerfile
|
||||||
|
|
||||||
# Podman Login action (https://github.com/redhat-actions/podman-login) also be used to log in,
|
- name: Push To Quay
|
||||||
# in which case 'username' and 'password' can be omitted.
|
|
||||||
- name: Push To quay.io
|
|
||||||
id: push-to-quay
|
id: push-to-quay
|
||||||
uses: redhat-actions/push-to-registry@v2
|
uses: redhat-actions/push-to-registry@v1
|
||||||
with:
|
with:
|
||||||
image: ${{ steps.build-image.outputs.image }}
|
image: ${{ env.IMAGE_NAME }}
|
||||||
tags: ${{ steps.build-image.outputs.tags }}
|
tag: ${{ env.TAG }}
|
||||||
registry: quay.io/quay-user
|
registry: ${{ secrets.QUAY_REPO }}
|
||||||
username: quay-user
|
username: ${{ secrets.QUAY_USERNAME }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.QUAY_TOKEN }}
|
||||||
|
|
||||||
- name: Print image url
|
- name: Use the image
|
||||||
run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}"
|
run: echo "New image has been pushed to ${{ steps.push-to-quay.outputs.registry-path }}"
|
||||||
```
|
```
|
||||||
<!-- 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
|
|
||||||
|
|
||||||
This action uses `Podman` to push, but can also push images built with `Docker`. However, Docker and Podman store their images in different locations, and Podman can only push images in its own storage.
|
|
||||||
|
|
||||||
If the image to push is present in the Docker image storage but not in the Podman image storage, it will be pulled into Podman's storage.
|
|
||||||
|
|
||||||
If the image to push is present in both the Docker and Podman image storage, the action will push the image which was more recently built, and log a warning.
|
|
||||||
|
|
||||||
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-22.04` since it has a newer version of Podman.
|
|
||||||
|
|
||||||
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
|
## Troubleshooting
|
||||||
Note that quay.io repositories are private by default.<br>
|
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.
|
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.
|
||||||
|
|
||||||
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).
|
## Contributing
|
||||||
|
|
||||||
|
This is an open source project open to anyone. This project welcomes contributions and suggestions!
|
||||||
|
|
||||||
|
## Feedback & Questions
|
||||||
|
|
||||||
|
If you discover an issue please file a bug in [GitHub issues](https://github.com/redhat-actions/push-to-registry/issues) and we will fix it as soon as possible.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT, See [LICENSE](./LICENSE) for more information.
|
||||||
|
|
37
action.yml
37
action.yml
|
@ -6,46 +6,29 @@ branding:
|
||||||
color: red
|
color: red
|
||||||
inputs:
|
inputs:
|
||||||
image:
|
image:
|
||||||
description: 'Name of the image/manifest to push (e.g. username/imagename or imagename)'
|
description: 'Name of the image to push'
|
||||||
required: false
|
required: true
|
||||||
tags:
|
tag:
|
||||||
description: |
|
description: 'Tag of the image to push'
|
||||||
'The tag or tags of the image/manifest to push.
|
|
||||||
For multiple tags, separate by whitespace. For example, "latest v1"'
|
|
||||||
required: false
|
required: false
|
||||||
default: 'latest'
|
default: 'latest'
|
||||||
registry:
|
registry:
|
||||||
description: 'Hostname and optional namespace to push the image to (eg. quay.io/username or quay.io)'
|
description: 'Registry where to push the image (eg. quay.io/username)'
|
||||||
required: false
|
required: true
|
||||||
username:
|
username:
|
||||||
description: 'Username to use as credential to authenticate to the registry'
|
description: 'Username to use as credential to authenticate to the registry'
|
||||||
required: false
|
required: true
|
||||||
password:
|
password:
|
||||||
description: 'Password to use as credential to authenticate to the registry'
|
description: 'Password to use as credential to authenticate to the registry'
|
||||||
required: false
|
required: true
|
||||||
tls-verify:
|
tls-verify:
|
||||||
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
|
|
||||||
extra-args:
|
|
||||||
description: |
|
|
||||||
Extra args to be passed to podman push.
|
|
||||||
Separate arguments by newline. Do not use quotes - @actions/exec will do the quoting for you.
|
|
||||||
required: false
|
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
digest:
|
|
||||||
description: 'The pushed image/manifest digest, as written to the "digestfile"'
|
|
||||||
registry-path:
|
registry-path:
|
||||||
description: 'The first element of registry-paths.'
|
description: 'The registry path to which the image was pushed'
|
||||||
registry-paths:
|
|
||||||
description: 'A JSON array of registry paths to which the tag(s) were pushed'
|
|
||||||
runs:
|
runs:
|
||||||
using: 'node20'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
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
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
### Copy this into .git/hooks and overwrite the empty one.
|
|
||||||
### This will ensure the bundle and ins-outs verification checks won't fail for you.
|
|
||||||
|
|
||||||
echo "----- Pre-commit -----"
|
|
||||||
set -ex
|
|
||||||
npx action-io-generator -o src/generated/inputs-outputs.ts
|
|
||||||
npm run lint
|
|
||||||
npm run bundle
|
|
||||||
git add -v dist/ src/generated
|
|
||||||
|
|
||||||
set +x
|
|
||||||
|
|
||||||
echo "Success"
|
|
3128
package-lock.json
generated
3128
package-lock.json
generated
File diff suppressed because it is too large
Load diff
24
package.json
24
package.json
|
@ -1,34 +1,24 @@
|
||||||
{
|
{
|
||||||
"name": "push-to-registry",
|
"name": "push-to-registry",
|
||||||
"version": "2.0.0",
|
"version": "0.0.1",
|
||||||
"description": "Action to push images to registry",
|
"description": "Action to push images to registry",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc -p .",
|
"compile": "tsc -p .",
|
||||||
"bundle": "ncc build src/index.ts --source-map --minify",
|
"bundle": "ncc build src/index.ts --source-map --minify",
|
||||||
"clean": "rm -rf out/ dist/",
|
"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"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Red Hat",
|
"author": "Red Hat",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.0.4",
|
||||||
"@actions/io": "^1.1.3",
|
"@actions/io": "^1.0.2"
|
||||||
"ini": "^4.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redhat-actions/action-io-generator": "^1.5.0",
|
"@types/node": "^12.12.7",
|
||||||
"@redhat-actions/eslint-config": "^1.3.2",
|
"@vercel/ncc": "^0.25.1",
|
||||||
"@redhat-actions/tsconfig": "^1.2.0",
|
"typescript": "^4.0.5"
|
||||||
"@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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
// This file was auto-generated by action-io-generator. Do not edit by hand!
|
|
||||||
export enum Inputs {
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
DIGESTFILE = "digestfile",
|
|
||||||
/**
|
|
||||||
* Extra args to be passed to podman push.
|
|
||||||
* Separate arguments by newline. Do not use quotes - @actions/exec will do the quoting for you.
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
EXTRA_ARGS = "extra-args",
|
|
||||||
/**
|
|
||||||
* Name of the image/manifest to push (e.g. username/imagename or imagename)
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
IMAGE = "image",
|
|
||||||
/**
|
|
||||||
* Password to use as credential to authenticate to the registry
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
PASSWORD = "password",
|
|
||||||
/**
|
|
||||||
* Hostname and optional namespace to push the image to (eg. quay.io/username or quay.io)
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
REGISTRY = "registry",
|
|
||||||
/**
|
|
||||||
* 'The tag or tags of the image/manifest to push.
|
|
||||||
* For multiple tags, separate by whitespace. For example, "latest v1"'
|
|
||||||
* Required: false
|
|
||||||
* Default: "latest"
|
|
||||||
*/
|
|
||||||
TAGS = "tags",
|
|
||||||
/**
|
|
||||||
* Verify TLS certificates when contacting the registry
|
|
||||||
* Required: false
|
|
||||||
* Default: "true"
|
|
||||||
*/
|
|
||||||
TLS_VERIFY = "tls-verify",
|
|
||||||
/**
|
|
||||||
* Username to use as credential to authenticate to the registry
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
USERNAME = "username",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Outputs {
|
|
||||||
/**
|
|
||||||
* The pushed image/manifest digest, as written to the "digestfile"
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
DIGEST = "digest",
|
|
||||||
/**
|
|
||||||
* The first element of registry-paths.
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
REGISTRY_PATH = "registry-path",
|
|
||||||
/**
|
|
||||||
* A JSON array of registry paths to which the tag(s) were pushed
|
|
||||||
* Required: false
|
|
||||||
* Default: None.
|
|
||||||
*/
|
|
||||||
REGISTRY_PATHS = "registry-paths",
|
|
||||||
}
|
|
563
src/index.ts
563
src/index.ts
|
@ -1,526 +1,91 @@
|
||||||
import * as core from "@actions/core";
|
import * as core from '@actions/core';
|
||||||
import * as exec from "@actions/exec";
|
import * as exec from '@actions/exec';
|
||||||
import * as io from "@actions/io";
|
import * as io from '@actions/io';
|
||||||
import * as fs from "fs";
|
|
||||||
import * as os from "os";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import {
|
|
||||||
isStorageDriverOverlay, findFuseOverlayfsPath,
|
|
||||||
splitByNewline,
|
|
||||||
isFullImageName, getFullImageName,
|
|
||||||
getFullDockerImageName,
|
|
||||||
} from "./util";
|
|
||||||
import { Inputs, Outputs } from "./generated/inputs-outputs";
|
|
||||||
|
|
||||||
interface ExecResult {
|
export async function run(): Promise<void> {
|
||||||
exitCode: number;
|
let imageToPush = core.getInput('image', { required: true });
|
||||||
stdout: string;
|
const tag = core.getInput('tag') || 'latest';
|
||||||
stderr: string;
|
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');
|
||||||
|
|
||||||
interface ImageStorageCheckResult {
|
// get podman cli
|
||||||
readonly foundTags: string[];
|
const podman = await io.which('podman', true);
|
||||||
readonly missingTags: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
let podmanPath: string | undefined;
|
imageToPush = `${imageToPush}:${tag}`;
|
||||||
|
let pushMsg = `Pushing ${imageToPush} to ${registry}`;
|
||||||
// boolean value to check if pushed image is from Docker image storage
|
|
||||||
let isImageFromDocker = false;
|
|
||||||
let sourceImages: string[];
|
|
||||||
let destinationImages: string[];
|
|
||||||
let dockerPodmanRoot: string;
|
|
||||||
let dockerPodmanOpts: string[];
|
|
||||||
|
|
||||||
async function getPodmanPath(): Promise<string> {
|
|
||||||
if (podmanPath == null) {
|
|
||||||
podmanPath = await io.which("podman", true);
|
|
||||||
await execute(podmanPath, [ "version" ], { group: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return podmanPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
|
||||||
const DEFAULT_TAG = "latest";
|
|
||||||
const image = core.getInput(Inputs.IMAGE);
|
|
||||||
const tags = core.getInput(Inputs.TAGS);
|
|
||||||
// split tags
|
|
||||||
const tagsList = tags.trim().split(/\s+/);
|
|
||||||
|
|
||||||
// info message if user doesn't provides any tag
|
|
||||||
if (tagsList.length === 0) {
|
|
||||||
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);
|
|
||||||
const tlsVerify = core.getInput(Inputs.TLS_VERIFY);
|
|
||||||
const digestFileInput = core.getInput(Inputs.DIGESTFILE);
|
|
||||||
|
|
||||||
// check if all tags provided are in `image:tag` format
|
|
||||||
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 (!normalizedImage) {
|
|
||||||
throw new Error(`Input "${Inputs.IMAGE}" must be provided when using non full name tags`);
|
|
||||||
}
|
|
||||||
if (!registry) {
|
|
||||||
throw new Error(`Input "${Inputs.REGISTRY}" must be provided when using non full name tags`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const registryWithoutTrailingSlash = registry.replace(/\/$/, "");
|
|
||||||
const registryPath = `${registryWithoutTrailingSlash}/${normalizedImage}`;
|
|
||||||
core.info(`Combining image name "${normalizedImage}" and registry "${registry}" `
|
|
||||||
+ `to form registry path "${registryPath}"`);
|
|
||||||
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 = normalizedTagsList.map((tag) => getFullImageName(normalizedImage, tag));
|
|
||||||
destinationImages = normalizedTagsList.map((tag) => getFullImageName(registryPath, tag));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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 = normalizedTagsList;
|
|
||||||
destinationImages = normalizedTagsList;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputExtraArgsStr = core.getInput(Inputs.EXTRA_ARGS);
|
|
||||||
let podmanExtraArgs: string[] = [];
|
|
||||||
if (inputExtraArgsStr) {
|
|
||||||
// transform the array of lines into an array of arguments
|
|
||||||
// by splitting over lines, then over spaces, then trimming.
|
|
||||||
const lines = splitByNewline(inputExtraArgsStr);
|
|
||||||
podmanExtraArgs = lines.flatMap((line) => line.split(" ")).map((arg) => arg.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
const registryPathList: string[] = [];
|
|
||||||
// here
|
|
||||||
// check if provided image is manifest or not
|
|
||||||
const isManifest = await checkIfManifestsExists();
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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${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`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 === 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.`
|
|
||||||
);
|
|
||||||
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.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) {
|
if (username) {
|
||||||
pushMsg += ` as "${username}"`;
|
pushMsg += ` as ${username}`;
|
||||||
}
|
}
|
||||||
core.info(pushMsg);
|
core.info(pushMsg);
|
||||||
|
|
||||||
let creds = "";
|
//check if images exist in podman's local storage
|
||||||
if (username && !password) {
|
const checkImages = await execute(podman, ['images', '--format', 'json']);
|
||||||
core.warning("Username is provided, but password is missing");
|
|
||||||
}
|
const parsedCheckImages = JSON.parse(checkImages.stdout);
|
||||||
else if (!username && password) {
|
|
||||||
core.warning("Password is provided, but username is missing");
|
// this is to temporarily solve an issue with the case-sensitive of the property field name. i.e it is Names or names??
|
||||||
}
|
const nameKeyMixedCase = parsedCheckImages[0] && Object.keys(parsedCheckImages[0]).find(key => 'names' === key.toLowerCase());
|
||||||
else if (username && password) {
|
const imagesFound = parsedCheckImages.
|
||||||
creds = `${username}:${password}`;
|
filter((image: string) => image[nameKeyMixedCase] && image[nameKeyMixedCase].find((name: string) => name.includes(`${imageToPush}`))).
|
||||||
|
map((image: string ) => image[nameKeyMixedCase]);
|
||||||
|
|
||||||
|
if (imagesFound.length === 0) {
|
||||||
|
//check inside the docker daemon local storage
|
||||||
|
await execute(podman, ['pull', `docker-daemon:${imageToPush}`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let digestFile = digestFileInput;
|
// push image
|
||||||
if (!digestFile) {
|
const registryPath = `${registry.replace(/\/$/, '')}/${imageToPush}`;
|
||||||
digestFile = `${sourceImages[0].replace(
|
|
||||||
/[/\\/?%*:|"<>]/g,
|
const creds: string = `${username}:${password}`;
|
||||||
"-",
|
|
||||||
)}_digest.txt`;
|
const args: string[] = ['push', '--quiet', '--creds', creds, imageToPush, registryPath];
|
||||||
|
|
||||||
|
// check if tls-verify is not set to null
|
||||||
|
if (tlsVerify) {
|
||||||
|
args.push(`--tls-verify=${tlsVerify}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// push the image
|
await execute(podman, args);
|
||||||
for (let i = 0; i < destinationImages.length; i++) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if tls-verify is not set to null
|
core.info(`Successfully pushed ${imageToPush} to ${registryPath}.`);
|
||||||
if (tlsVerify) {
|
|
||||||
args.push(`--tls-verify=${tlsVerify}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if registry creds are provided
|
core.setOutput('registry-path', registryPath);
|
||||||
if (creds) {
|
|
||||||
args.push(`--creds=${creds}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await execute(await getPodmanPath(), args);
|
|
||||||
core.info(`✅ Successfully pushed "${sourceImages[i]}" to "${destinationImages[i]}"`);
|
|
||||||
|
|
||||||
registryPathList.push(destinationImages[i]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const digest = (await fs.promises.readFile(digestFile)).toString();
|
|
||||||
core.info(digest);
|
|
||||||
// the digest should be the same for every image, but we log it every time
|
|
||||||
// due to https://github.com/redhat-actions/push-to-registry/issues/26
|
|
||||||
core.setOutput(Outputs.DIGEST, digest);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
core.warning(`Failed to read digest file "${digestFile}": ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core.setOutput(Outputs.REGISTRY_PATH, registryPathList[0]);
|
|
||||||
core.setOutput(Outputs.REGISTRY_PATHS, JSON.stringify(registryPathList));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullImageFromDocker(): Promise<ImageStorageCheckResult> {
|
async function execute(executable: string, args: string[], execOptions: exec.ExecOptions = {}): Promise<{ exitCode: number, stdout: string, stderr: string }> {
|
||||||
core.info(`🔍 Checking if "${sourceImages.join(", ")}" present in the local Docker image storage`);
|
|
||||||
const foundTags: string[] = [];
|
|
||||||
const missingTags: string[] = [];
|
|
||||||
try {
|
|
||||||
for (const imageWithTag of sourceImages) {
|
|
||||||
const commandResult: ExecResult = await execute(
|
|
||||||
await getPodmanPath(),
|
|
||||||
[ ...dockerPodmanOpts, "pull", `docker-daemon:${imageWithTag}` ],
|
|
||||||
{ ignoreReturnCode: true, failOnStdErr: false, group: true }
|
|
||||||
);
|
|
||||||
if (commandResult.exitCode === 0) {
|
|
||||||
foundTags.push(imageWithTag);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
missingTags.push(imageWithTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
core.debug(err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
foundTags,
|
|
||||||
missingTags,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkImageInPodman(): Promise<ImageStorageCheckResult> {
|
|
||||||
// check if images exist in Podman's storage
|
|
||||||
core.info(`🔍 Checking if "${sourceImages.join(", ")}" present in the local Podman image storage`);
|
|
||||||
const foundTags: string[] = [];
|
|
||||||
const missingTags: string[] = [];
|
|
||||||
try {
|
|
||||||
for (const imageWithTag of sourceImages) {
|
|
||||||
const commandResult: ExecResult = await execute(
|
|
||||||
await getPodmanPath(),
|
|
||||||
[ "image", "exists", imageWithTag ],
|
|
||||||
{ ignoreReturnCode: true }
|
|
||||||
);
|
|
||||||
if (commandResult.exitCode === 0) {
|
|
||||||
foundTags.push(imageWithTag);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
missingTags.push(imageWithTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
core.debug(err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = sourceImages[0];
|
|
||||||
|
|
||||||
// get creation time of the image present in the Podman image storage
|
|
||||||
const podmanLocalImageTimeStamp = await execute(await getPodmanPath(), [
|
|
||||||
"image",
|
|
||||||
"inspect",
|
|
||||||
imageWithTag,
|
|
||||||
"--format",
|
|
||||||
"{{.Created}}",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// get creation time of the image pulled from the Docker image storage
|
|
||||||
// appending 'docker.io/library' infront of image name as pulled image name
|
|
||||||
// from Docker image storage starts with the 'docker.io/library'
|
|
||||||
const pulledImageCreationTimeStamp = await execute(await getPodmanPath(), [
|
|
||||||
...dockerPodmanOpts,
|
|
||||||
"image",
|
|
||||||
"inspect",
|
|
||||||
getFullDockerImageName(imageWithTag),
|
|
||||||
"--format",
|
|
||||||
"{{.Created}}",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const podmanImageTime = new Date(podmanLocalImageTimeStamp.stdout).getTime();
|
|
||||||
|
|
||||||
const dockerImageTime = new Date(pulledImageCreationTimeStamp.stdout).getTime();
|
|
||||||
|
|
||||||
return podmanImageTime > dockerImageTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createDockerPodmanImageStroage(): Promise<void> {
|
|
||||||
core.info(`Creating temporary Podman image storage for pulling from Docker daemon`);
|
|
||||||
dockerPodmanRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), "podman-from-docker-"));
|
|
||||||
|
|
||||||
dockerPodmanOpts = [ "--root", dockerPodmanRoot ];
|
|
||||||
|
|
||||||
if (await isStorageDriverOverlay()) {
|
|
||||||
const fuseOverlayfsPath = await findFuseOverlayfsPath();
|
|
||||||
if (fuseOverlayfsPath) {
|
|
||||||
core.info(`Overriding storage mount_program with "fuse-overlayfs" in environment`);
|
|
||||||
dockerPodmanOpts.push("--storage-opt");
|
|
||||||
dockerPodmanOpts.push(`overlay.mount_program=${fuseOverlayfsPath}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core.warning(`"fuse-overlayfs" is not found. Install it before running this action. `
|
|
||||||
+ `For more detail see https://github.com/redhat-actions/buildah-build/issues/45`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core.info("Storage driver is not 'overlay', so not overriding storage configuration");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeDockerPodmanImageStroage(): Promise<void> {
|
|
||||||
if (dockerPodmanRoot) {
|
|
||||||
try {
|
|
||||||
core.info(`Removing temporary Podman image storage for pulling from Docker daemon`);
|
|
||||||
await execute(
|
|
||||||
await getPodmanPath(),
|
|
||||||
[ ...dockerPodmanOpts, "rmi", "-a", "-f" ]
|
|
||||||
);
|
|
||||||
await fs.promises.rmdir(dockerPodmanRoot, { recursive: true });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
core.warning(`Failed to remove podman image stroage ${dockerPodmanRoot}: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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[],
|
|
||||||
execOptions: exec.ExecOptions & { group?: boolean } = {},
|
|
||||||
): Promise<ExecResult> {
|
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
|
|
||||||
const finalExecOptions = { ...execOptions };
|
const finalExecOptions = { ...execOptions };
|
||||||
finalExecOptions.ignoreReturnCode = true; // the return code is processed below
|
finalExecOptions.ignoreReturnCode = true; // the return code is processed below
|
||||||
|
|
||||||
finalExecOptions.listeners = {
|
finalExecOptions.listeners = {
|
||||||
stdline: (line): void => {
|
stdline: (line) => {
|
||||||
stdout += `${line}\n`;
|
stdout += line + "\n";
|
||||||
},
|
},
|
||||||
errline: (line): void => {
|
errline: (line) => {
|
||||||
stderr += `${line}\n`;
|
stderr += line + "\n"
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitCode = await exec.exec(executable, args, finalExecOptions);
|
||||||
|
|
||||||
|
if (execOptions.ignoreReturnCode !== true && exitCode !== 0) {
|
||||||
|
// Throwing the stderr as part of the Error makes the stderr show up in the action outline, which saves some clicking when debugging.
|
||||||
|
let error = `${path.basename(executable)} exited with code ${exitCode}`;
|
||||||
|
if (stderr) {
|
||||||
|
error += `\n${stderr}`;
|
||||||
|
}
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exitCode, stdout, stderr
|
||||||
};
|
};
|
||||||
|
|
||||||
if (execOptions.group) {
|
|
||||||
const groupName = [ executable, ...args ].join(" ");
|
|
||||||
core.startGroup(groupName);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const exitCode = await exec.exec(executable, args, finalExecOptions);
|
|
||||||
|
|
||||||
if (execOptions.ignoreReturnCode !== true && exitCode !== 0) {
|
|
||||||
// Throwing the stderr as part of the Error makes the stderr show up in the action outline,
|
|
||||||
// which saves some clicking when debugging.
|
|
||||||
let error = `${path.basename(executable)} exited with code ${exitCode}`;
|
|
||||||
if (stderr) {
|
|
||||||
error += `\n${stderr}`;
|
|
||||||
}
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
exitCode,
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
if (execOptions.group) {
|
|
||||||
core.endGroup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
run().catch(core.setFailed);
|
||||||
try {
|
|
||||||
await createDockerPodmanImageStroage();
|
|
||||||
await run();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
await removeDockerPodmanImageStroage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.catch((err) => {
|
|
||||||
core.setFailed(err.message);
|
|
||||||
});
|
|
||||||
|
|
93
src/util.ts
93
src/util.ts
|
@ -1,93 +0,0 @@
|
||||||
/***************************************************************************************************
|
|
||||||
* Copyright (c) Red Hat, Inc. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
|
||||||
**************************************************************************************************/
|
|
||||||
|
|
||||||
import * as ini from "ini";
|
|
||||||
import { promises as fs } from "fs";
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
import * as path from "path";
|
|
||||||
import * as io from "@actions/io";
|
|
||||||
import * as os from "os";
|
|
||||||
|
|
||||||
async function findStorageDriver(filePaths: string[]): Promise<string> {
|
|
||||||
let storageDriver = "";
|
|
||||||
for (const filePath of filePaths) {
|
|
||||||
core.debug(`Checking if the storage file exists at ${filePath}`);
|
|
||||||
if (await fileExists(filePath)) {
|
|
||||||
core.debug(`Storage file exists at ${filePath}`);
|
|
||||||
const fileContent = ini.parse(await fs.readFile(filePath, "utf-8"));
|
|
||||||
if (fileContent.storage.driver) {
|
|
||||||
storageDriver = fileContent.storage.driver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return storageDriver;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isStorageDriverOverlay(): Promise<boolean> {
|
|
||||||
let xdgConfigHome = path.join(os.homedir(), ".config");
|
|
||||||
if (process.env.XDG_CONFIG_HOME) {
|
|
||||||
xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
||||||
}
|
|
||||||
const filePaths: string[] = [
|
|
||||||
"/etc/containers/storage.conf",
|
|
||||||
path.join(xdgConfigHome, "containers/storage.conf"),
|
|
||||||
];
|
|
||||||
const storageDriver = await findStorageDriver(filePaths);
|
|
||||||
return (storageDriver === "overlay");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fileExists(filePath: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await fs.access(filePath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function findFuseOverlayfsPath(): Promise<string | undefined> {
|
|
||||||
let fuseOverlayfsPath;
|
|
||||||
try {
|
|
||||||
fuseOverlayfsPath = await io.which("fuse-overlayfs");
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
core.debug(err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fuseOverlayfsPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function splitByNewline(s: string): string[] {
|
|
||||||
return s.split(/\r?\n/);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFullImageName(image: string): boolean {
|
|
||||||
return image.indexOf(":") > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFullImageName(image: string, tag: string): string {
|
|
||||||
if (isFullImageName(tag)) {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
return `${image}:${tag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DOCKER_IO = `docker.io`;
|
|
||||||
const DOCKER_IO_NAMESPACED = DOCKER_IO + `/library`;
|
|
||||||
|
|
||||||
export function getFullDockerImageName(image: string): string {
|
|
||||||
switch (image.split("/").length) {
|
|
||||||
case 1:
|
|
||||||
return `${DOCKER_IO_NAMESPACED}/${image}`;
|
|
||||||
case 2:
|
|
||||||
if (image.includes("amazonaws.com")) return image;
|
|
||||||
return `${DOCKER_IO}/${image}`;
|
|
||||||
default:
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"extends": "@redhat-actions/tsconfig",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src/",
|
"target": "ES6",
|
||||||
"outDir": "out/"
|
"module": "commonjs",
|
||||||
|
"lib": [
|
||||||
|
"ES2017"
|
||||||
|
],
|
||||||
|
"outDir": "out",
|
||||||
|
"rootDir": ".",
|
||||||
},
|
},
|
||||||
"include": [
|
"exclude": [
|
||||||
"src/"
|
"node_modules"
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue