forked from repo-mirrors/cnpg-postgres-containers
feat: generate image catalogs for bake images (#305)
Generate default cluster image catalogs for all combinations of types and OS versions, including all supported PostgreSQL versions. The catalogs also include predefined labels to easily identify the type, OS version, date, and origin of the catalog. Closes cloudnative-pg/artifacts#1 Signed-off-by: Niccolò Fei <niccolo.fei@enterprisedb.com> Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com> Co-authored-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
This commit is contained in:
160
.github/catalogs_generator.py
vendored
Normal file
160
.github/catalogs_generator.py
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import yaml
|
||||
import urllib.request
|
||||
from packaging import version
|
||||
from subprocess import check_output
|
||||
|
||||
supported_img_types = ["minimal", "standard", "system"]
|
||||
supported_os_names = ["bullseye", "bookworm", "trixie"]
|
||||
min_supported_major = 13
|
||||
|
||||
repo_name = "cloudnative-pg/postgresql"
|
||||
full_repo_name = f"ghcr.io/{repo_name}"
|
||||
pg_regexp = r"(\d+)(?:\.\d+|beta\d+|rc\d+|alpha\d+)-(\d{12})"
|
||||
_token_cache = {"value": None, "expires_at": 0}
|
||||
|
||||
|
||||
def get_json(image_name):
|
||||
data = check_output(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"quay.io/skopeo/stable",
|
||||
"list-tags",
|
||||
f"docker://{image_name}",
|
||||
]
|
||||
)
|
||||
repo_json = json.loads(data.decode("utf-8"))
|
||||
return repo_json
|
||||
|
||||
|
||||
def get_token(repository_name):
|
||||
global _token_cache
|
||||
now = time.time()
|
||||
|
||||
if _token_cache["value"] and now < _token_cache["expires_at"]:
|
||||
return _token_cache["value"]
|
||||
|
||||
url = "https://ghcr.io/token?scope=repository:{}:pull".format(repository_name)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
data = json.load(response)
|
||||
token = data["token"]
|
||||
|
||||
_token_cache["value"] = token
|
||||
_token_cache["expires_at"] = now + 300
|
||||
return token
|
||||
|
||||
|
||||
def get_digest(repository_name, tag):
|
||||
token = get_token(repository_name)
|
||||
media_types = [
|
||||
"application/vnd.oci.image.index.v1+json",
|
||||
"application/vnd.oci.image.manifest.v1+json",
|
||||
"application/vnd.docker.distribution.manifest.v2+json",
|
||||
]
|
||||
url = f"https://ghcr.io/v2/{repository_name}/manifests/{tag}"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header("Authorization", "Bearer {}".format(token))
|
||||
req.add_header("Accept", ",".join(media_types))
|
||||
with urllib.request.urlopen(req) as response:
|
||||
digest = response.headers.get("Docker-Content-Digest")
|
||||
return digest
|
||||
|
||||
|
||||
def write_catalog(tags, version_re, img_type, os_name, output_dir="."):
|
||||
image_suffix = f"-{img_type}-{os_name}"
|
||||
version_re = re.compile(rf"^{version_re}{re.escape(image_suffix)}$")
|
||||
|
||||
# Filter out all the tags which do not match the version regexp
|
||||
tags = [item for item in tags if version_re.search(item)]
|
||||
|
||||
# Filter out preview versions
|
||||
exclude_preview = re.compile(r"(alpha|beta|rc)")
|
||||
tags = [item for item in tags if not exclude_preview.search(item)]
|
||||
|
||||
# Sort the tags according to semantic versioning
|
||||
tags.sort(key=lambda v: version.Version(v.removesuffix(image_suffix)), reverse=True)
|
||||
|
||||
results = {}
|
||||
for item in tags:
|
||||
match = version_re.search(item)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
major = match.group(1)
|
||||
|
||||
# Skip too old versions
|
||||
if int(major) < min_supported_major:
|
||||
continue
|
||||
|
||||
if major not in results:
|
||||
digest = get_digest(repo_name, item)
|
||||
results[major] = [f"{full_repo_name}:{item}@{digest}"]
|
||||
|
||||
catalog = {
|
||||
"apiVersion": "postgresql.cnpg.io/v1",
|
||||
"kind": "ClusterImageCatalog",
|
||||
"metadata": {
|
||||
"name": f"postgresql{image_suffix}",
|
||||
"labels": {
|
||||
"images.cnpg.io/family": "postgresql",
|
||||
"images.cnpg.io/type": img_type,
|
||||
"images.cnpg.io/os": os_name,
|
||||
"images.cnpg.io/date": time.strftime("%Y%m%d"),
|
||||
"images.cnpg.io/publisher": "cnpg.io",
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"images": [
|
||||
{"major": int(major), "image": images[0]}
|
||||
for major, images in sorted(results.items(), key=lambda x: int(x[0]))
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_file = os.path.join(output_dir, f"catalog{image_suffix}.yaml")
|
||||
with open(output_file, "w") as f:
|
||||
yaml.dump(catalog, f, sort_keys=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="CloudNativePG ClusterImageCatalog YAML generator"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir", default=".", help="Directory to save the YAML files"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_json = get_json(full_repo_name)
|
||||
tags = repo_json["Tags"]
|
||||
|
||||
for img_type in supported_img_types:
|
||||
for os_name in supported_os_names:
|
||||
print(f"Generating catalog-{img_type}-{os_name}.yaml")
|
||||
write_catalog(tags, pg_regexp, img_type, os_name, args.output_dir)
|
12
.github/workflows/bake.yml
vendored
12
.github/workflows/bake.yml
vendored
@@ -51,3 +51,15 @@ jobs:
|
||||
with:
|
||||
environment: ${{ github.event.inputs.environment }}
|
||||
postgresql_version: ${{ matrix.version }}
|
||||
|
||||
Catalogs:
|
||||
name: Update Catalogs
|
||||
needs: Bake
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||
with:
|
||||
event-type: update-catalogs
|
||||
|
57
.github/workflows/catalogs.yml
vendored
Normal file
57
.github/workflows/catalogs.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Update Catalogs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
types: [update-catalogs]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: "bash -Eeuo pipefail -x {0}"
|
||||
|
||||
jobs:
|
||||
update-catalogs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
path: postgres-containers
|
||||
|
||||
- name: Checkout artifacts
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
path: artifacts
|
||||
repository: cloudnative-pg/artifacts
|
||||
token: ${{ secrets.REPO_GHA_PAT }}
|
||||
ref: main
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install packaging==25.0 PyYAML==6.0.2
|
||||
|
||||
- name: Generate catalogs
|
||||
run: |
|
||||
python postgres-containers/.github/catalogs_generator.py --output-dir artifacts/image-catalogs/
|
||||
|
||||
- name: Diff
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
git status
|
||||
git diff
|
||||
|
||||
- uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
with:
|
||||
cwd: 'artifacts'
|
||||
add: 'image-catalogs'
|
||||
author_name: CloudNativePG Automated Updates
|
||||
author_email: noreply@cnpg.com
|
||||
message: 'chore: update imageCatalogs'
|
Reference in New Issue
Block a user