buildContainerImage2: Support multiarch OCI images
The `buildContainerImage2` function is an improvement on the existing `buildContainerImage` function, with a couple of enhancements. Primarily, it supports building images for multiple architectures. For each listed architecture, a pod is launched on a matching worker node to build the image natively. The image is exported to an OCI archive and "stashed" in the Jenkins workspace. After images for all architectures are built, another pod is launched and all of the stashed archives are copied there and assembled into a manifest list. Finally, the manifest list is published to the OCI repository as usual, creating tags for the Git branch and build number. In addition to adding support for multiarch images, the `buildContainerImage2` performs image builds in *unprivileged* pods. Whereas the old function performed builds in a rootful container, the new one configures requests pods with unique user namespaces. The build runs as *root* in the container, but that user is mapped to an arbitrary unprivileged user on the host.bci2-resources
parent
356d9ecc1d
commit
96f4e59cbc
|
@ -0,0 +1,19 @@
|
|||
spec:
|
||||
containers:
|
||||
- name: buildah
|
||||
image: quay.io/containers/buildah:v1
|
||||
command:
|
||||
- cat
|
||||
stdin: true
|
||||
tty: true
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_ADMIN
|
||||
- MKNOD
|
||||
- SYS_CHROOT
|
||||
- SETFCAP
|
||||
resources:
|
||||
limits:
|
||||
github.com/fuse: 1
|
||||
hostUsers: false
|
|
@ -0,0 +1,144 @@
|
|||
// vim: set sw=4 sts=4 ts=4 et :
|
||||
|
||||
def call(args) {
|
||||
properties([
|
||||
pipelineTriggers([cron('H H H * *')])
|
||||
])
|
||||
|
||||
def registry = args?.registry
|
||||
def project = args?.project
|
||||
def name = args?.name
|
||||
def tag = args?.tag
|
||||
def archlist = args?.archlist
|
||||
|
||||
if (registry == null) {
|
||||
registry = 'git.pyrocufflink.net'
|
||||
}
|
||||
if (project == null) {
|
||||
project = 'containerimages'
|
||||
}
|
||||
if (name == null) {
|
||||
name = env.JOB_NAME.
|
||||
split('/')[1].
|
||||
toLowerCase().
|
||||
replaceAll('[^a-zA-z0-9._-]', '-').
|
||||
replaceAll('^[.-]', '_')
|
||||
}
|
||||
if (tag == null) {
|
||||
tag = env.BRANCH_NAME.
|
||||
toLowerCase().
|
||||
replaceAll('[^a-zA-z0-9._-]', '-').
|
||||
replaceAll('^[.-]', '_')
|
||||
}
|
||||
if (archlist == null) {
|
||||
archlist = ['amd64']
|
||||
}
|
||||
def repo = "${registry}/${project}/${name}"
|
||||
def full_name = "${repo}:${tag}"
|
||||
|
||||
def stages = [:]
|
||||
archlist.each { arch ->
|
||||
def stageName = "Build ${arch}"
|
||||
stages[stageName] = {
|
||||
stage(stageName) {
|
||||
buildStage(
|
||||
name: name,
|
||||
full_name: full_name,
|
||||
registry: registry,
|
||||
arch: arch,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
parallel stages
|
||||
|
||||
runInPod {
|
||||
container('buildah') {
|
||||
withBuildahCreds(registry) {
|
||||
if (archlist.size() > 1) {
|
||||
stage('Build Manifest') {
|
||||
sh "buildah manifest create '${full_name}'"
|
||||
archlist.each { arch ->
|
||||
unstash arch
|
||||
sh "buildah manifest add '${full_name}' oci-archive:\${PWD}/${name}-${arch}.tar"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Push') {
|
||||
if (archlist.size() > 1) {
|
||||
sh "buildah manifest push --all ${full_name} docker://${full_name}-${env.BUILD_NUMBER}"
|
||||
sh "buildah manifest push ${full_name} docker://${full_name}"
|
||||
if (env.BRANCH_NAME == 'main') {
|
||||
sh "buildah manifest push ${full_name} docker://${repo}:latest"
|
||||
}
|
||||
} else {
|
||||
sh "buildah push ${full_name} ${full_name}-${env.BUILD_NUMBER}"
|
||||
sh "buildah push ${full_name}"
|
||||
if (env.BRANCH_NAME == 'main') {
|
||||
sh "buildah push ${full_name} ${repo}:latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def buildStage(args) {
|
||||
def name = args.name
|
||||
def full_name = args.full_name
|
||||
def registry = args.registry
|
||||
def arch = args.arch
|
||||
|
||||
runInPod(arch) {
|
||||
checkout scm
|
||||
|
||||
container('buildah') {
|
||||
withBuildahCreds(registry) {
|
||||
sh "buildah build -t '${full_name}' ."
|
||||
sh "buildah push '${full_name}' oci-archive:\${PWD}/${name}-${arch}.tar"
|
||||
stash name: arch, includes: "${name}-*.tar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def runInPod(... args) {
|
||||
def arch = null
|
||||
def block = args.last()
|
||||
if (args.size() > 1) {
|
||||
arch = args[0]
|
||||
}
|
||||
|
||||
def podTemplateYaml = libraryResource('podTemplate2.yaml')
|
||||
podTemplate(
|
||||
yaml: podTemplateYaml,
|
||||
nodeSelector: arch ? "kubernetes.io/arch=${arch}" : null,
|
||||
workspaceVolume: emptyDirWorkspaceVolume(),
|
||||
) {
|
||||
node(POD_LABEL) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def withBuildahCreds(registry, block) {
|
||||
withEnv([
|
||||
"REGISTRY_AUTH_FILE=${env.WORKSPACE_TMP}/auth.json"
|
||||
]) {
|
||||
withCredentials([usernamePassword(
|
||||
credentialsId: 'jenkins-packages',
|
||||
usernameVariable: 'BUILDAH_USERNAME',
|
||||
passwordVariable: 'BUILDAH_PASSWORD',
|
||||
)]) {
|
||||
sh """
|
||||
buildah login \
|
||||
--username \${BUILDAH_USERNAME} \
|
||||
--password \${BUILDAH_PASSWORD} \
|
||||
${registry}
|
||||
"""
|
||||
}
|
||||
|
||||
block()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue