Skip to content

Commit aff43ad

Browse files
authored
Merge pull request ceph#60755 from dmick/wip-release-container
Modify container/ software to support release containers and the promotion of prerelease containers
2 parents ee27439 + 4b3c0cb commit aff43ad

File tree

3 files changed

+164
-50
lines changed

3 files changed

+164
-50
lines changed

container/Containerfile

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ARG OSD_FLAVOR="default"
2222
# (optional) Should be 'true' for CI builds (pull from shaman, etc.)
2323
ARG CI_CONTAINER="true"
2424

25+
2526
RUN /bin/echo -e "\
2627
FROM_IMAGE: ${FROM_IMAGE}\n\
2728
CEPH_REF: ${CEPH_REF}\n\
@@ -61,30 +62,36 @@ RUN \
6162
echo "enabled=1" >> /etc/yum.repos.d/ganesha.repo
6263

6364
# ISCSI repo
64-
RUN set -x && \
65+
RUN set -ex && \
6566
curl -s -L https://shaman.ceph.com/api/repos/tcmu-runner/main/latest/centos/9/repo?arch=$(arch) -o /etc/yum.repos.d/tcmu-runner.repo && \
6667
case "${CEPH_REF}" in \
6768
quincy|reef) \
68-
curl -s -L https://download.ceph.com/ceph-iscsi/3/rpm/el9/ceph-iscsi.repo -o /etc/yum.repos.d/ceph-iscsi.repo ;\
69+
curl -fs -L https://download.ceph.com/ceph-iscsi/3/rpm/el9/ceph-iscsi.repo -o /etc/yum.repos.d/ceph-iscsi.repo ;\
6970
;;\
7071
main|*) \
71-
curl -s -L https://shaman.ceph.com/api/repos/ceph-iscsi/main/latest/centos/9/repo -o /etc/yum.repos.d/ceph-iscsi.repo ;\
72+
curl -fs -L https://shaman.ceph.com/api/repos/ceph-iscsi/main/latest/centos/9/repo -o /etc/yum.repos.d/ceph-iscsi.repo ;\
7273
;;\
7374
esac
7475

7576
# Ceph repo
76-
RUN set -x && \
77+
RUN --mount=type=secret,id=prerelease_creds set -ex && \
7778
rpm --import 'https://download.ceph.com/keys/release.asc' && \
7879
ARCH=$(arch); if [ "${ARCH}" == "aarch64" ]; then ARCH="arm64"; fi ;\
7980
IS_RELEASE=0 ;\
8081
if [[ "${CI_CONTAINER}" == "true" ]] ; then \
8182
# TODO: this can return different ceph builds (SHA1) for x86 vs. arm runs. is it important to fix?
82-
REPO_URL=$(curl -s "https://shaman.ceph.com/api/search/?project=ceph&distros=centos/9/${ARCH}&flavor=${OSD_FLAVOR}&ref=${CEPH_REF}&sha1=latest" | jq -r .[0].url) ;\
83+
REPO_URL=$(curl -fs "https://shaman.ceph.com/api/search/?project=ceph&distros=centos/9/${ARCH}&flavor=${OSD_FLAVOR}&ref=${CEPH_REF}&sha1=latest" | jq -r .[0].url) ;\
8384
else \
8485
IS_RELEASE=1 ;\
85-
REPO_URL="http://download.ceph.com/rpm-${CEPH_REF}/el9/" ;\
86+
source /run/secrets/prerelease_creds; \
87+
REPO_URL="https://${PRERELEASE_USERNAME}:${PRERELEASE_PASSWORD}@download.ceph.com/prerelease/ceph/rpm-${CEPH_REF}/el9/" ;\
8688
fi && \
87-
rpm -Uvh "$REPO_URL/noarch/ceph-release-1-${IS_RELEASE}.el9.noarch.rpm"
89+
rpm -Uvh "$REPO_URL/noarch/ceph-release-1-${IS_RELEASE}.el9.noarch.rpm" ; \
90+
if [[ "$IS_RELEASE" == 1 ]] ; then \
91+
sed -i "s;http://download.ceph.com/;https://${PRERELEASE_USERNAME}:${PRERELEASE_PASSWORD}@download.ceph.com/prerelease/ceph/;" /etc/yum.repos.d/ceph.repo ; \
92+
dnf clean expire-cache ; \
93+
fi
94+
8895

8996
# Copr repos
9097
# scikit for mgr-diskprediction-local
@@ -186,15 +193,16 @@ RUN \
186193
grep -sqo "obtain_device_list_from_udev = 0" /etc/lvm/lvm.conf
187194

188195
# CLEAN UP!
189-
RUN set -x && \
196+
RUN set -ex && \
190197
dnf clean all && \
191198
rm -rf /var/cache/dnf/* && \
192199
rm -rf /var/lib/dnf/* && \
193200
rm -f /var/lib/rpm/__db* && \
194201
# remove unnecessary files with big impact
195202
rm -rf /etc/selinux /usr/share/{doc,man,selinux} && \
196203
# don't keep compiled python binaries
197-
find / -xdev \( -name "*.pyc" -o -name "*.pyo" \) -delete
204+
find / -xdev \( -name "*.pyc" -o -name "*.pyo" \) -delete && \
205+
rm -f /etc/yum.repos.d/{ceph,ganesha,tcmu-runner,ceph-iscsi}.repo
198206

199207
# Verify that the packages installed haven't been accidentally cleaned, then
200208
# clean the package list and re-clean unnecessary RPM database files

container/build.sh

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ CONTAINER_REPO_HOSTNAME (quay.ceph.io, for CI, for instance)
2222
CONTAINER_REPO_ORGANIZATION (ceph-ci, for CI, for instance)
2323
CONTAINER_REPO_USERNAME
2424
CONTAINER_REPO_PASSWORD
25+
PRERELEASE_USERNAME for download.ceph.com:/prerelease/ceph
26+
PRERELEASE_PASSWORD
2527
2628
For a release build: (from ceph.git, built and pushed to download.ceph.com)
2729
CI_CONTAINER: must be 'false'
@@ -41,12 +43,17 @@ CEPH_SHA1=${CEPH_SHA1:-$(git rev-parse HEAD)}
4143
# default: build host arch
4244
ARCH=${ARCH:-$(arch)}
4345
if [[ "${ARCH}" == "aarch64" ]] ; then ARCH=arm64; fi
46+
REPO_ARCH=amd64
47+
if [[ "${ARCH}" = arm64 ]] ; then
48+
REPO_ARCH=arm64
49+
fi
50+
4451
if [[ ${CI_CONTAINER} == "true" ]] ; then
4552
CONTAINER_REPO_HOSTNAME=${CONTAINER_REPO_HOSTNAME:-quay.ceph.io}
46-
CONTAINER_REPO_ORGANIZATION=${CONTAINER_REPO_ORGANIZATION:-ceph/ceph-${ARCH}}
53+
CONTAINER_REPO_ORGANIZATION=${CONTAINER_REPO_ORGANIZATION:-ceph-ci/ceph}
4754
else
48-
CONTAINER_REPO_HOSTNAME=${CONTAINER_REPO_HOSTNAME:-quay.io}
49-
CONTAINER_REPO_ORGANIZATION=${CONTAINER_REPO_ORGANIZATION:-ceph/ceph}
55+
CONTAINER_REPO_HOSTNAME=${CONTAINER_REPO_HOSTNAME:-quay.ceph.io}
56+
CONTAINER_REPO_ORGANIZATION=${CONTAINER_REPO_ORGANIZATION:-ceph/prerelease-${REPO_ARCH}}
5057
# default: most-recent annotated tag
5158
VERSION=${VERSION:-$(git describe --abbrev=0)}
5259
fi
@@ -61,7 +68,7 @@ fi
6168
: "${CONTAINER_REPO_ORGANIZATION:?}"
6269
: "${CONTAINER_REPO_USERNAME:?}"
6370
: "${CONTAINER_REPO_PASSWORD:?}"
64-
if [[ ${CI_CONTAINER} != "true" ]] ; then ${VERSION:?}; fi
71+
if [[ ${CI_CONTAINER} != "true" ]] ; then : "${VERSION:?}"; fi
6572

6673
# check for valid repo auth (if pushing)
6774
ORGURL=${CONTAINER_REPO_HOSTNAME}/${CONTAINER_REPO_ORGANIZATION}
@@ -87,15 +94,26 @@ fi
8794
# BRANCH will be, say, origin/main. remove <remote>/
8895
BRANCH=${BRANCH##*/}
8996

97+
# podman build only supports secret files.
98+
# This must be removed after podman build
99+
touch prerelease.secret.txt
100+
chmod 600 prerelease.secret.txt
101+
echo -e "\
102+
PRERELEASE_USERNAME=${PRERELEASE_USERNAME}\n
103+
PRERELEASE_PASSWORD=${PRERELEASE_PASSWORD}\n " > prerelease.secret.txt
104+
90105
podman build --pull=newer --squash -f $CFILE -t build.sh.output \
91106
--build-arg FROM_IMAGE=${FROM_IMAGE:-quay.io/centos/centos:stream9} \
92107
--build-arg CEPH_SHA1=${CEPH_SHA1} \
93108
--build-arg CEPH_GIT_REPO=${CEPH_GIT_REPO} \
94109
--build-arg CEPH_REF=${BRANCH:-main} \
95110
--build-arg OSD_FLAVOR=${FLAVOR:-default} \
96111
--build-arg CI_CONTAINER=${CI_CONTAINER:-default} \
112+
--secret=id=prerelease_creds,src=./prerelease.secret.txt \
97113
2>&1
98114

115+
rm ./prerelease.secret.txt
116+
99117
image_id=$(podman image ls localhost/build.sh.output --format '{{.ID}}')
100118

101119
# grab useful image attributes for building the tag
@@ -162,13 +180,13 @@ if [[ ${CI_CONTAINER} == "true" ]] ; then
162180
else
163181
#
164182
# non-CI build. Tags are like v19.1.0-20240701
165-
# push to quay.ceph.io/ceph/prerelease
183+
# push to quay.ceph.io/ceph/prerelease-$REPO_ARCH
166184
#
167-
version_tag=${repopath}/prerelease/ceph-${ARCH}:${VERSION}-${builddate}
185+
version_tag=${repopath}/prerelease-${REPO_ARCH}:v${VERSION}-${builddate}
168186

169187
podman tag ${image_id} ${version_tag}
170188
if [[ -z "${NO_PUSH}" ]] ; then
171-
podman push ${image_id} ${version_tag}
189+
podman push ${version_tag}
172190
fi
173191
fi
174192

container/make-manifest-list.py

Lines changed: 122 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11
#!/usr/bin/python3
22
#
3+
# in default mode:
34
# make a combined "manifest-list" container out of two arch-specific containers
45
# searches for latest tags on HOST/{AMD,ARM}64_REPO, makes sure they refer
56
# to the same Ceph SHA1, and creates a manifest-list ("fat") image on
6-
# MANIFEST_HOST/MANIFEST_REPO with the 'standard' set of tags.
7+
# MANIFEST_HOST/MANIFEST_REPO with the 'standard' set of tags:
8+
# v<major>
9+
# v<major>.<minor>
10+
# v<major>.<minor>.<micro>
11+
# v<major>.<minor>.<micro>-<YYYYMMDD>
712
#
8-
# uses scratch local manifest LOCALMANIFEST, will be destroyed if present
13+
# uses scratch local manifest LOCALMANIFEST, defined here; will be destroyed if present
14+
#
15+
# in promote mode (by adding the --promote argument):
16+
# instead of building the manifest-list container, copy it
17+
# (and all of its tags) from the prerelease repo to the release repo
18+
#
19+
# Assumes valid logins to the necessary hosts/repos with permission to write images
20+
#
21+
# Environment variables to set:
22+
# ARCH_SPECIFIC_HOST (default 'quay.ceph.io'): host of prerelease repos
23+
# AMD64_REPO (default 'ceph/prerelease-amd64') prerelease amd64 repo
24+
# ARM64_REPO (default 'ceph/prerelease-arm64') prerelease arm64 repo
25+
# MANIFEST_HOST (default 'quay.ceph.io') prerelease manifest-list host
26+
# MANIFEST_REPO (default 'ceph/prerelease') prerelease manifest-list repo
27+
# RELEASE_MANIFEST_HOST (default 'quay.io') release host
28+
# RELEASE_MANIFEST_REPO (default 'ceph/ceph') release repo
929

30+
31+
import argparse
1032
from datetime import datetime
1133
import functools
1234
import json
@@ -15,16 +37,6 @@
1537
import subprocess
1638
import sys
1739

18-
# optional env vars (will default if not set)
19-
20-
OPTIONAL_VARS = (
21-
'HOST',
22-
'AMD64_REPO',
23-
'ARM64_REPO',
24-
'MANIFEST_HOST',
25-
'MANIFEST_REPO',
26-
)
27-
2840
# Manifest image. Will be destroyed if already present.
2941
LOCALMANIFEST = 'localhost/m'
3042

@@ -47,10 +59,7 @@ def run_command(args):
4759
return True, result.stdout, result.stderr
4860

4961
except subprocess.CalledProcessError as e:
50-
print(f"Command '{e.cmd}' returned {e.returncode}")
51-
print("Error output:")
52-
print(e.stderr)
53-
return False, result.stdout, result.stderr
62+
return False, e.output, e.stderr
5463

5564

5665
def get_command_output(args):
@@ -68,10 +77,16 @@ def run_command_show_failure(args):
6877

6978

7079
@functools.lru_cache
80+
def get_tags(path):
81+
cmdout = get_command_output(f'skopeo list-tags docker://{path}')
82+
return json.loads(cmdout)['Tags']
83+
84+
7185
def get_latest_tag(path):
72-
latest_tag = json.loads(
73-
get_command_output(f'skopeo list-tags docker://{path}')
74-
)['Tags'][-1]
86+
try:
87+
latest_tag = get_tags(path)[-1]
88+
except IndexError:
89+
return None
7590
return latest_tag
7691

7792

@@ -84,27 +99,53 @@ def get_image_inspect(path):
8499

85100

86101
def get_sha1(info):
87-
return info['Labels']['GIT_COMMIT']
102+
labels = info.get('Labels', None)
103+
if not labels:
104+
return None
105+
return labels.get('CEPH_SHA1', None)
88106

89107

90-
def main():
91-
host = os.environ.get('HOST', 'quay.io')
92-
amd64_repo = os.environ.get('AMD64_REPO', 'ceph/ceph-amd64')
93-
arm64_repo = os.environ.get('ARM64_REPO', 'ceph/ceph-arm64')
94-
manifest_host = os.environ.get('MANIFEST_HOST', host)
95-
manifest_repo = os.environ.get('MANIFEST_REPO', 'ceph/ceph')
108+
@functools.lru_cache
109+
def get_all_matching_digest_tags(path, tag):
110+
111+
matching_tags = list()
112+
digest = get_image_inspect(f'{path}:{tag}')['Digest']
113+
114+
for t in get_tags(path):
115+
this_digest = get_image_inspect(f'{path}:{t}')['Digest']
116+
if this_digest == digest:
117+
matching_tags.append(t)
118+
119+
return matching_tags
120+
121+
122+
def parse_args():
123+
ap = argparse.ArgumentParser()
124+
ap.add_argument('-n', '--dry-run', action='store_true', help='do all local manipulations but do not push final containers to MANIFEST_HOST, or in --promote, calculate but do not copy images to release host')
125+
ap.add_argument('-P', '--promote', action='store_true', help='promote newest prerelease manifest container to released (move from MANIFEST_HOST to RELEASE_MANIFEST_HOST')
126+
args = ap.parse_args()
127+
return args
128+
129+
def build_prerelease(sysargs):
130+
global args
131+
132+
arch_specific_host = os.environ.get('ARCH_SPECIFIC_HOST', 'quay.ceph.io')
133+
amd64_repo = os.environ.get('AMD64_REPO', 'ceph/prerelease-amd64')
134+
arm64_repo = os.environ.get('ARM64_REPO', 'ceph/prerelease-arm64')
135+
manifest_host = os.environ.get('MANIFEST_HOST', 'quay.ceph.io')
136+
manifest_repo = os.environ.get('MANIFEST_REPO', 'ceph/prerelease')
137+
96138
dump_vars(
97-
('host',
139+
('arch_specific_host',
98140
'amd64_repo',
99141
'arm64_repo',
100142
'manifest_host',
101143
'manifest_repo',
102144
),
103145
locals())
104-
105146
repopaths = (
106-
f'{host}/{amd64_repo}',
107-
f'{host}/{arm64_repo}',
147+
f'{arch_specific_host}/{amd64_repo}',
148+
f'{arch_specific_host}/{arm64_repo}',
108149
)
109150
tags = [get_latest_tag(p) for p in repopaths]
110151
print(f'latest tags: amd64:{tags[0]} arm64:{tags[1]}')
@@ -145,8 +186,8 @@ def main():
145186

146187
# create manifest list image with the standard list of tags
147188
# ignore failure on manifest rm
148-
run_command(f'podman manifest rm localhost/m')
149-
run_command_show_failure(f'podman manifest create localhost/m')
189+
run_command(f'podman manifest rm {LOCALMANIFEST}')
190+
run_command_show_failure(f'podman manifest create {LOCALMANIFEST}')
150191
for p in paths_with_tags:
151192
run_command_show_failure(f'podman manifest add m {p}')
152193
base = f'{manifest_host}/{manifest_repo}'
@@ -156,8 +197,55 @@ def main():
156197
f'v{major}.{minor}.{micro}',
157198
f'v{major}.{minor}.{micro}-{datetime.today().strftime("%Y%m%d")}',
158199
):
159-
run_command_show_failure(
160-
f'podman manifest push localhost/m {base}:{t}')
200+
if sysargs.dry_run:
201+
print(f'skipping podman manifest push {LOCALMANIFEST} {base}:{t}')
202+
else:
203+
run_command_show_failure(
204+
f'podman manifest push {LOCALMANIFEST} {base}:{t}')
205+
206+
def promote(sysargs):
207+
manifest_host = os.environ.get('MANIFEST_HOST', 'quay.ceph.io')
208+
manifest_repo = os.environ.get('MANIFEST_REPO', 'ceph/prerelease')
209+
release_manifest_host = os.environ.get('RELEASE_MANIFEST_HOST', 'quay.io')
210+
release_manifest_repo = os.environ.get('RELEASE_MANIFEST_REPO', 'ceph/ceph')
211+
dump_vars(
212+
('manifest_host',
213+
'manifest_repo',
214+
'release_manifest_host',
215+
'release_manifest_repo',
216+
),
217+
locals())
218+
219+
manifest_path = f'{manifest_host}/{manifest_repo}'
220+
release_path = f'{release_manifest_host}/{release_manifest_repo}'
221+
latest_tag = get_latest_tag(manifest_path)
222+
all_tags = get_all_matching_digest_tags(manifest_path, latest_tag)
223+
224+
copypaths = list()
225+
for t in all_tags:
226+
from_path = f'{manifest_path}:{t}'
227+
to_path = f'{release_path}:{t}'
228+
copypaths.append((from_path, to_path))
229+
230+
if sysargs.dry_run:
231+
for f, t in copypaths:
232+
print(f'dry-run: Would copy: {f} -> {t}')
233+
return(0)
234+
235+
for f, t in copypaths:
236+
print(f'Will copy: {f} -> {t}')
237+
238+
for f, t in copypaths:
239+
run_command_show_failure(f'skopeo copy --multi-arch=all docker://{f} docker://{t}')
240+
241+
242+
def main():
243+
args = parse_args()
244+
245+
if args.promote:
246+
promote(args)
247+
else:
248+
build_prerelease(args)
161249

162250

163251
if (__name__ == '__main__'):

0 commit comments

Comments
 (0)