Packages Build Pipeline with OpenShift
As an other follow-up to my previous OpenShift posts, today we would look into Jenkins and Nexus integration with OpenShift, while building a dummy package shipping SSH Keys, both as a debien archive and RPM package.
If you’re not concerned with automating Nexus configuration, then you may use sonatype/nexus3 from the Docker hub setting up Nexus Repository Manager on OpenShift.
As I wanted to automate a few configuration tasks, I eventually started working on my own image, forking from a repository offered by Accenture. My copy isn’t yet released publicly, so I’ld just point out it creates a couple users uploading and downloading Artifacts.
Another subject to address would be to prepare a couple images building our Debian and RPM packages. Regarding RPMs, we could divert from Jenkins base slave image:
FROM openshift/jenkins-slave-base-centos7
RUN yum -y install epel-release \
&& yum -y install @development-tools centos-packager rpmdevtools \
&& yum -y install make wget git curlUSER 1001
While for Debian we would want to build some Stretch-based equivalent:
FROM debian:stretch
ENV HOME=/home/jenkins \
DEBIAN_FRONTEND=noninteractiveUSER root
ADD config/* /usr/local/bin/
RUN apt-get -y update \
&& apt-get -y install bc gettext git subversion openjdk-8-jre-headless gnupg curl wget \
lsof rsync tar unzip debianutils zip bzip2 make gcc g++ devscripts debhelper \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /home/jenkins \
&& chown -R 1001:0 /home/jenkins \
&& chmod -R g+w /home/jenkins \
&& chmod 664 /etc/passwd \
&& chmod -R 775 /etc/alternatives /usr/lib/jvm \
&& chmod 775 /usr/bin /usr/share/man/man1USER 1001
ENTRYPOINT [“/usr/local/bin/run-jnlp-client”]
From there, the last item we’ll need, building our packages, is their sources.
Building RPMs, we would write a Spec file such as the following:
Summary: My Package
Name: my-package
Version: 0.0.1
Release: 1%{?dist}
License: MIT
Source: https://repo/sources/el-%{name}-%{version}.tar.gz
URL: https://my.example.comAutoreq: no
BuildRequires: git
BuildRequires: make%description
Does something awesome%global __os_install_post %{nil}
%define debug_package %{nil}
%prep
%autosetup
%build
%install
make install PREFIX=%{buildroot}%pre
%prerun
%post
%files
%defattr(-,root,root)
%dir %{_datadir}/mydir
%{_datadir}/mydir/myfile%changelog
* Thu Aug 30 2018 It’s Me <mario@example.com> 0.0.1-1
– Initial release – In an other castle?
Now regarding Debian packages, we would need to create a couple subdirectories, configuration files and scripts:
$ mkdir -p debian/source
$ echo “3.0 (quit)” >debian/source/format
$ echo 9 >debian/compat
$ for i in postinst preinst prerm pstrm; do
cat <<EOF >debian/$i
#!/bin/sh
# $i script for my-packageset -e
case “$1” in
purge|remove|abort-install|disappear) ;;upgrade|failed-upgrade|abort-upgrade) ;;
*)
echo “postrm called with unknown argument \`$1′” >&2
exit 1
;;
esac#DEBHELPER#
exit 0
EOF
chmod +x debian/$i
done
$ for i in docs copyright missing-sources README.Debian; do
touch $i
done
$ cat <<EOF >debian/rules
#!/usr/bin/make -f
#DH_VERBOSE = 1DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/default.mk# see FEATURE AREAS in dpkg-buildflags(1)
export DEB_BUILD_MAINT_OPTIONS = hardening=+all# main packaging script based on dh7 syntax
%:
dh $@override_dh_auto_install:
$(MAKE) install PREFIX=$(CURDIR)/debian/my-packageoverride_dh_auto_build:
echo nothing to dooverride_dh_auto_test:
echo nothing to do
EOF
$ chmod +x debian/rules
$ cat <<EOF >debian/changelog
my-package (0.0.1-1) unstable; urgency=low* Initial release – In an other castle?
— It’s Me <mario@example.com> Thu, 30 Aug 2018 11:30:42 +0200
EOF
From there, we ensure our sources ships with a Makefile, providing with the following rules:
SHARE_DIR = $(PREFIX)/usr/share
createdebsource:
LANG=C debuild -S -sacreatedebbin:
LANG=C dpkg-buildpackage -us -uccreaterpm:
versionNumber=`awk ‘/^Version:/{print $$2;exit;}’ el/my-package.spec`; \
wdir=”`pwd`/..”; \
buildroot=”$$wdir/rpmbuild”; \
for d in SOURCES SPECS BUILD RPMS SRPMS; \
do \
mkdir -p “$$buildroot/$$d”; \
done; \
cp -p “$$wdir/el-my-package-$$versionNumber.tar.gz” “$$buildroot/SOURCES/”; \
cp -p “$$wdir/my-package/el/my-package.spec” “$$buildroot/SPECS/”; \
if ! whoami >/dev/null 2>&1; then \
chown -R root:root “$$buildroot/SOURCES” “$$buildroot/SPECS”; \
elif whoami 2>/dev/null | grep default >/dev/null; then \
chown -R :root “$$buildroot/SOURCES” “$$buildroot/SPECS”; \
fi; \
( \
cd “$$buildroot”; \
LANG=C rpmbuild –define “_topdir $$buildroot” -ba SPECS/my-package.spec && \
find *RPMS -type f | while read output; \
do \
mv “$$output” $$wdir/; \
done; \
)createinitialarchive:
rm -fr .git .gitignore README.md
versionNumber=`cat debian/changelog | awk ‘/my-package/{print $$2;exit}’ | sed -e ‘s|[()]||g’ -e ‘s|\\(.*\\)-[0-9]*\$$|\\1|’`; \
( \
cd ..; \
tar -czf my-package_$$versionNumber.orig.tar.gz my-package; \
mv my-package my-package-$$versionNumber; \
tar -czf el-my-package-$$versionNumber.tar.gz my-package-$$versionNumber; \
mv my-package-$$versionNumber my-package; \
)install:
mkdir -p $(SHARE_DIR)/mydir
install -c -m 0644 myfile $(SHARE_DIR)/mydir/myfile
At which point, we may use the following OpenShift Template, creating a few secrets and a pair of Jenkins Pipelines, building Debian and RPM packages based on our previous images, then uploading their Artifacts to Nexus :
apiVersion: v1
kind: Template
metadata:
name: my-package-template
objects:
– apiVersion: v1
kind: Secret
metadata:
annotations:
jenkins.io/credentials-description : ${APPLICATION_NAME} Git Token credential from Kubernetes
labels:
jenkins.io/credentials-type: secretText
name: git-${APPLICATION_NAME}
stringData:
text: ${GIT_DEPLOYMENT_TOKEN}
– apiVersion: v1
kind: Secret
metadata:
annotations:
jenkins.io/credentials-description : ${APPLICATION_NAME} Nexus Credentials from Kubernetes
labels:
jenkins.io/credentials-type: usernamePassword
name: nexus-${APPLICATION_NAME}
stringData:
password: ${NEXUS_ARTIFACTS_PASSWORD}
username: ${NEXUS_ARTIFACTS_USERNAME}
– apiVersion: v1
kind: BuildConfig
metadata:
annotations:
description: Builds ${APPLICATION_NAME} rpm archive
name: ${APPLICATION_NAME}-rpm
spec:
strategy:
jenkinsPipelineStrategy:
jenkinsfile: |-
try {
def pkgname = “${APPLICATION_NAME}”
def label = “${pkgname}-${UUID.randomUUID().toString()}”
podTemplate(label: label, name: label, cloud: ‘openshift’,
containers: [ containerTemplate(name: ‘jnlp’, image: ‘${DOCKER_REGISTRY}/${CENTOS_IMAGE}’) ],
inheritFrom: ‘nodejs’, serviceAccount: ‘jenkins’) {
timeout(time: 40, unit: ‘MINUTES’) {
node (label) {
stage(“Fetch”) {
sh “git config –global http.sslVerify false”
sh “mkdir ${pkgname}”
withCredentials([string(credentialsId: “git-${pkgname}”, variable: ‘GIT_TOKEN’)]) {
sh “echo ‘${SOURCE_REPOSITORY_URL}’ | sed ‘s|^\\(http[s]*://\\)\\(.*\\)|\\1${GIT_TOKEN}@\\2|’ >cloneFrom 2>/dev/null”
}
def cloneAddress = readFile(‘cloneFrom’).trim()
dir (“${pkgname}”) {
git([ branch: “master”, changelog: false, poll: false, url: cloneAddress ])
}
}
stage(“Build”) {
sh “””
( cd ${pkgname} ; git rev-parse –short HEAD ) >gitHash
( cd ${pkgname} ; make createinitialarchive ; make createrpm )
awk ‘/^Release:/{print \$2;exit;}’ ${pkgname}/el/${pkgname}.spec | cut -d% -f1 >patchNumber
awk ‘/^Version:/{print \$2;exit;}’ ${pkgname}/el/${pkgname}.spec >versionNumber
“””
}
stage(“Upload”) {
def gitHash = readFile(‘gitHash’).trim()
def patch = readFile(‘patchNumber’).trim()
def version = readFile(‘versionNumber’).trim()
sh “echo Uploading artifacts for ${version}-${patch}-${gitHash}”
nexusArtifactUploader(
nexusVersion: ‘${NEXUS_VERSION}’,
protocol: “${NEXUS_PROTO}”,
nexusUrl: “${NEXUS_REMOTE}”,
groupId: “${NEXUS_GROUP_ID}”,
version: “${version}-${patch}-${gitHash}”,
repository: “${NEXUS_RPM_REPOSITORY}”,
credentialsId: “nexus-${pkgname}”,
artifacts: [
[ artifactId: “${pkgname}-rpm”,
classifier: ”, type: ‘rpm’,
file: “${pkgname}-${version}-${patch}.el7.src.rpm” ],
[ artifactId: “${pkgname}-rpm”,
classifier: ”, type: ‘rpm’,
file: “${pkgname}-${version}-${patch}.el7.x86_64.rpm” ],
[ artifactId: “${pkgname}-rpm”,
classifier: ”, type: ‘tar.gz’,
file: “el-${pkgname}-${version}.tar.gz” ]
]
)
}
}
}
}
} catch (err) {
echo “in catch block”
echo “Caught: ${err}”
currentBuild.result = ‘FAILURE’
throw err
}
type: JenkinsPipeline
– apiVersion: v1
kind: BuildConfig
metadata:
annotations:
description: Builds ${APPLICATION_NAME} deb archive
name: ${APPLICATION_NAME}-deb
spec:
strategy:
jenkinsPipelineStrategy:
jenkinsfile: |-
try {
def pkgname = “${APPLICATION_NAME}”
def label = “${pkgname}-${UUID.randomUUID().toString()}”
podTemplate(label: label, name: label, cloud: ‘openshift’,
containers: [ containerTemplate(name: ‘jnlp’, image: ‘${DOCKER_REGISTRY}/${DEBIAN_IMAGE}’) ],
inheritFrom: ‘nodejs’, serviceAccount: ‘jenkins’) {
timeout(time: 40, unit: ‘MINUTES’) {
node (label) {
stage(“Fetch”) {
sh “git config –global http.sslVerify false”
sh “mkdir ${pkgname}”
withCredentials([string(credentialsId: “git-${pkgname}”, variable: ‘GIT_TOKEN’)]) {
sh “echo ‘${SOURCE_REPOSITORY_URL}’ | sed ‘s|^\\(http[s]*://\\)\\(.*\\)|\\1${GIT_TOKEN}@\\2|’ >cloneFrom 2>/dev/null”
}
def cloneAddress = readFile(‘cloneFrom’).trim()
dir (“${pkgname}”) {
git([ branch: “master”, changelog: false, poll: false, url: cloneAddress ])
}
}
stage(“Build”) {
sh “””
( cd ${pkgname} ; git rev-parse –short HEAD ) >gitHash
( cd ${pkgname} ; make createinitialarchive ; make createdebbin )
cat ${pkgname}/debian/changelog | awk ‘/${pkgname}/{print \$2;exit}’ | sed -e ‘s|[()]||g’ -e ‘s|.*-\\([0-9]*\\)\$|\\1|’ >patchNumber
cat ${pkgname}/debian/changelog | awk ‘/${pkgname}/{print \$2;exit}’ | sed -e ‘s|[()]||g’ -e ‘s|\\(.*\\)-[0-9]*\$|\\1|’ >versionNumber
“””
}
stage(“Upload”) {
def gitHash = readFile(‘gitHash’).trim()
def patch = readFile(‘patchNumber’).trim()
def version = readFile(‘versionNumber’).trim()
sh “echo Uploading artifacts for ${version}-${patch}-${gitHash}”
nexusArtifactUploader(
nexusVersion: ‘${NEXUS_VERSION}’,
protocol: “${NEXUS_PROTO}”,
nexusUrl: “${NEXUS_REMOTE}”,
groupId: “${NEXUS_GROUP_ID}”,
version: “${version}-${patch}-${gitHash}”,
repository: “${NEXUS_DEB_REPOSITORY}”,
credentialsId: “nexus-${pkgname}”,
artifacts: [
[ artifactId: “${pkgname}-deb”,
classifier: ”, type: ‘deb’,
file: “${pkgname}_${version}-${patch}_all.deb” ],
[ artifactId: “${pkgname}-deb”,
classifier: ”, type: ‘txt’,
file: “${pkgname}_${version}-${patch}_amd64.buildinfo” ],
[ artifactId: “${pkgname}-deb”,
classifier: ”, type: ‘txt’,
file: “${pkgname}_${version}-${patch}_amd64.changes” ],
[ artifactId: “${pkgname}-deb”,
classifier: ”, type: ‘tar.xz’,
file: “${pkgname}_${version}-${patch}.debian.tar.xz” ],
[ artifactId: “${pkgname}-dev”,
classifier: ”, type: ‘tar.gz’,
file: “${pkgname}_${version}.orig.tar.gz” ],
[ artifactId: “${pkgname}-deb”,
classifier: ”, type: ‘txt’,
file: “${pkgname}_${version}-${patch}.dsc” ]
]
)
}
}
}
}
} catch (err) {
echo “in catch block”
echo “Caught: ${err}”
currentBuild.result = ‘FAILURE’
throw err
}
type: JenkinsPipeline
parameters:
– name: APPLICATION_NAME
description: Package Name – should match that expected by package we’ll build
displayName: Package Name
value: my-package
– name: DEBIAN_IMAGE
description: Jenkins Debian Agent Image – relative to DOCKER_REGISTRY
displayName: Jenkins Debian Agent Image
required: true
value: “cicd/jenkins-agent-debian:latest”
– name: DOCKER_REGISTRY
description: Docker Registry
displayName: Docker Registry
required: true
value: docker-registry.default.svc:5000
– name: CENTOS_IMAGE
description: Jenkins Centos Agent Image – relative to DOCKER_REGISTRY
displayName: Jenkins Centos Agent Image
required: true
value: “cicd/jenkins-agent-centos:latest”
– name: GIT_DEPLOYMENT_TOKEN
description: Git deployment token
displayName: Git Deployment Token
required: true
– name: NEXUS_ARTIFACTS_PASSWORD
description: Nexus Artifacts Upload Password
displayName: Nexus Artifacts Upload Password
required: true
value: admin123
– name: NEXUS_ARTIFACTS_USERNAME
description: Nexus Artifacts Upload Username
displayName: Nexus Artifacts Upload Username
required: true
value: admin
– name: NEXUS_GROUP_ID
description: Nexus Group ID
displayName: Nexus Group ID
required: true
value: com.example
– name: NEXUS_DEB_REPOSITORY
description: Nexus Artifact Debian Repository – remote repository name
displayName: Nexus Artifact Debian Repository
required: true
value: debian
– name: NEXUS_PROTO
description: Nexus Proto – http or https
displayName: Nexus Proto
required: true
value: http
– name: NEXUS_REMOTE
description: Nexus Remote URL – proto-less URI connecting to Nexus
displayName: Nexus Remote URL
value: “nexus:8081”
required: true
– name: NEXUS_RPM_REPOSITORY
description: Nexus Artifact EL Repository – remote repository name
displayName: Nexus Artifact EL Repository
required: true
value: centos
– name: NEXUS_VERSION
description: Nexus Repository Version
displayName: Nexus Repository Version
required: true
value: nexus3
– name: SOURCE_REPOSITORY_URL
description: The URL of the repository with your application source code
displayName: Git Repository URL
required: true
value: https://git.example.com/project/my-package