diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..fc75b71 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,263 @@ +#!groovy + +/* + * This work is protected under copyright law in the Kingdom of + * The Netherlands. The rules of the Berne Convention for the + * Protection of Literary and Artistic Works apply. + * Digital Me B.V. is the copyright owner. + * + * 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. + */ + +// Load Jenkins shared libraries common to all projects +def libLazy = [ + remote: 'https://github.com/digital-me/jenkins-lib-lazy.git', + branch: 'stable', + credentialsId: null, +] + +library( + identifier: "libLazy@${libLazy.branch}", + retriever: modernSCM([ + $class: 'GitSCMSource', + remote: libLazy.remote, + credentialsId: libLazy.credentialsId + ]), + changelog: false, +) + +// Load Jenkins shared libraries to customize this project +def libCustom = [ + remote: 'ssh://git@code.in.digital-me.nl:2222/DEVops/JenkinsLibCustom.git', + branch: 'stable', + credentialsId: 'bot-ci-dgm-rsa', +] + +library( + identifier: "libCustom@${libCustom.branch}", + retriever: modernSCM([ + $class: 'GitSCMSource', + remote: libCustom.remote, + credentialsId: libCustom.credentialsId + ]), + changelog: false, +) + +// Define the remotes and the working and deploy branches +def remote = 'origin' +def workingBranch = 'main' +def releaseBranch = 'stable' + +// Initialize configuration +lazyConfig( + name: 'vue-dummy', + env: [ + RELEASE: true, + DRYRUN: false, + BUILD_DIR: 'dist', // directory where the site be build + GIT_CRED: 'bot-ci-dgm', + DEPLOY_USER: 'root', + DEPLOY_HOST_TST: 'bxtsvctwas001.in.dolden.net', + DEPLOY_HOST_STS: 'bxtsvctwas001.in.dolden.net', + DEPLOY_HOST_ACC: 'bxtsvctwas003.in.dolden.net', + DEPLOY_HOST_PRD: 'bxtsvctwas004.in.dolden.net', + DEPLOY_DIR: '/var/www/html/dummyvue', + DEPLOY_CRED: 'bot-ci-dgm-rsa', + ], + inLabels: [ 'centos7', ], + onLabels: [ default: 'master', docker: 'docker', ], + noIndex: "(.+_.+)", // Avoid automatic indexing for release and private branches + xmppTargets: 'design@conference.qiy.nl', +) + +// Validate by linting the code +lazyStage { + name = 'validate' + onlyif = ( lazyConfig['branch'] != releaseBranch ) // Skip when releasing + tasks = [ + pre: { + def currentVersion = null + gitAuth(env.GIT_CRED, { + currentVersion = gitLastTag() + }) + currentBuild.displayName = "#${env.BUILD_NUMBER} ${currentVersion}" + }, + run: { sh("npm run-script validate") }, + in: [ 'centos7'] , on: 'docker' + ] +} + +// Test the code +lazyStage { + name = 'test' + onlyif = ( lazyConfig['branch'] != releaseBranch ) // Skip when releasing + tasks = [ + pre: { + def currentVersion = null + gitAuth(env.GIT_CRED, { + currentVersion = gitLastTag() + }) + currentBuild.displayName = "#${env.BUILD_NUMBER} ${currentVersion}" + }, + run: { sh("npm run-script test") }, + in: [ 'centos7'] , on: 'docker' + ] +} + +// Generate and package the site +lazyStage { + name = 'generate' + tasks = [ + pre: { + // Read version from last git tag first + def currentVersion = null + gitAuth(env.GIT_CRED, { + currentVersion = gitLastTag() + }) + if (lazyConfig['branch'] != releaseBranch) { + // Write version from tag to generate the site + jekyllVersion(currentVersion) + } + currentBuild.displayName = "#${env.BUILD_NUMBER} ${currentVersion}" + }, + run: { sh("npm run-script build") }, + post: { + archiveArtifacts(artifacts: "${env.BUILD_DIR}/**", allowEmptyArchive: false) + }, + in: [ 'centos7'], + on: 'docker', + ] +} + +// Deliver the site on each environment +lazyStage { + name = 'test' + onlyif = ( env.LAZY_BRANCH ==~ /^devel_.+/ ) + input = 'Deploy to test?' + tasks = [ + pre: { + unarchive(mapping:["${env.BUILD_DIR}/" : '.']) + }, + run: { + sshagent(credentials: [env.DEPLOY_CRED]) { + env.DEPLOY_HOST_TST.split(',').each { host -> + sshDeploy(env.BUILD_DIR, "${env.DEPLOY_USER}@${host}", env.DEPLOY_DIR, 'rsync') + } + } + }, + on: 'linux', + ] +} + +// Release stage only only if criteria are met +lazyStage { + name = 'release' + onlyif = ( lazyConfig['branch'] == workingBranch && lazyConfig.env.RELEASE ) + // Ask version if release flag and set and we are in the branch to fork release from + input = [ + message: 'Version string', + parameters: [string( + defaultValue: '', + description: "Version to be release: 'build', 'micro', 'minor', 'major' or a specific string (i.e.: 1.2.3-4)", + name: 'VERSION' + )] + ] + tasks = [ + run: { + gitAuth(env.GIT_CRED, { + // Define next version based on optional input + def currentVersion = jekyllVersion() + def nextVersion = null + if (env.lazyInput) { + if (env.lazyInput ==~ /[a-z]+/) { + nextVersion = bumpVersion(env.lazyInput, currentVersion) + } else { + nextVersion = env.lazyInput + } + } else { + nextVersion = bumpVersion('build', currentVersion) + } + // Merge changes from working into release branch + gitMerge(workingBranch, releaseBranch) + // Bump version into release branch + jekyllVersion(nextVersion) + gitCommit("Update version to ${nextVersion}", '_version.yml') + // Uncomment the following to merge version bump back into the working branch + //gitMerge(releaseBranch, workingBranch) + // Tag and publish changes in release branch + gitTag("${nextVersion}") + gitPush(remote, "${releaseBranch} ${nextVersion}") + // Update the displayed version for this build + currentVersion = gitLastTag() + currentBuild.displayName = "#${env.BUILD_NUMBER} ${currentVersion}" + }) + }, + // Can not be done in parallel + ] +} + +// Deliver the site on each environment +lazyStage { + name = 'systemtest' + onlyif = ( env.LAZY_BRANCH == releaseBranch ) + tasks = [ + pre: { + unarchive(mapping:["${env.BUILD_DIR}/" : '.']) + }, + run: { + sshagent(credentials: [env.DEPLOY_CRED]) { + env.DEPLOY_HOST_STS.split(',').each { host -> + sshDeploy(env.BUILD_DIR, "${env.DEPLOY_USER}@${host}", env.DEPLOY_DIR, 'rsync') + } + } + }, + on: 'linux', + ] +} + +lazyStage { + name = 'acceptance' + onlyif = ( env.LAZY_BRANCH == releaseBranch ) + tasks = [ + pre: { + unarchive(mapping:["${env.BUILD_DIR}/" : '.']) + }, + run: { + sshagent(credentials: [env.DEPLOY_CRED]) { + env.DEPLOY_HOST_ACC.split(',').each { host -> + sshDeploy(env.BUILD_DIR, "${env.DEPLOY_USER}@${host}", env.DEPLOY_DIR, 'rsync') + } + } + }, + on: 'linux', + ] +} + +lazyStage { + name = 'production' + onlyif = ( env.LAZY_BRANCH == releaseBranch ) + input = 'Deploy to production?' + tasks = [ + pre: { + unarchive(mapping:["${env.BUILD_DIR}/" : '.']) + }, + run: { + sshagent(credentials: [env.DEPLOY_CRED]) { + env.DEPLOY_HOST_PRD.split(',').each { host -> + sshDeploy(env.BUILD_DIR, "${env.DEPLOY_USER}@${host}", env.DEPLOY_DIR, 'rsync') + } + } + }, + on: 'linux', + ] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f35cff9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3' +services: + centos7: + build: + context: lazyDir + dockerfile: centos7.Dockerfile + args: + uid: "${_UID:-1000}" + gid: "${_GID:-1000}" + volumes: + - .:/var/tmp/nodejs + working_dir: /var/tmp/nodejs + stdin_open: true + tty: true + hostname: nodejs.local + container_name: nodejs.local + command: bash + networks: + - bridge + deploy: + resources: + limits: + cpus: '1.5' + memory: 256M + +networks: + bridge: + external: true diff --git a/lazyDir/centos7.Dockerfile b/lazyDir/centos7.Dockerfile new file mode 100644 index 0000000..dad737a --- /dev/null +++ b/lazyDir/centos7.Dockerfile @@ -0,0 +1,109 @@ +############################## +# General level requirements # +############################## + +# Pull base image from official repo +FROM centos:centos7.9.2009 + +# Import local GPG keys and enable epel repo +RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 && \ + yum -q clean all && \ + yum -q makecache && \ + yum -y install --setopt=tsflags=nodocs \ + epel-release \ + && \ + rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 && \ + yum -q -y clean all --enablerepo='*' + +# Install common requirements +RUN INSTALL_PKGS="git unzip wget which" && \ + yum -q clean expire-cache && \ + yum -q makecache && \ + yum -y install --setopt=tsflags=nodocs $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum -q -y clean all --enablerepo='*' + +# Prepare locales +ARG locale=en_US.UTF-8 +ENV LANG "${locale}" +ENV LC_ALL "${locale}" + +# Configure desired timezone +ENV TZ=Europe/Amsterdam +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ + echo $TZ > /etc/timezone + +################################## +# Application level requirements # +################################## + +# Update to some specific versions +RUN INSTALL_PKGS="ca-certificates-2021.2.50-72.el7_9" && \ + yum -q clean expire-cache && \ + yum -q makecache && \ + yum -y install --setopt=tsflags=nodocs $INSTALL_PKGS && \ + rpm -V $INSTALL_PKGS && \ + yum -q -y clean all --enablerepo='*' + +# Add internal CA +RUN wget -q https://share.dolden.net/public/certs/ca-dolden-root.crt \ +-O /etc/pki/ca-trust/source/anchors/ca-dolden-root.pem && \ + update-ca-trust force-enable && \ + update-ca-trust extract + +# Install NodeJS from pre-built packages +ENV NODE_VERSION 16.14.2 +RUN SRC_URL="https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" && \ + wget --no-verbose --output-document /tmp/node-v$NODE_VERSION-linux-x64.tar.xz $SRC_URL 2>&1 && \ + tar -xJf /tmp/node-v$NODE_VERSION-linux-x64.tar.xz -C /usr/local --strip-components=1 --no-same-owner && \ + rm -f /tmp/node-v$NODE_VERSION-linux-x64* && \ + ln -s /usr/local/bin/node /usr/local/bin/nodejs && \ + node --version && \ + npm --version + +# Update npm which tend to evolve from the previous package +ENV NPM_VERSION 8.13.2 +RUN npm install -g npm@$NPM_VERSION + +# Add specific entrypoint script to init node_modules when required +COPY container-entrypoint.sh /usr/local/bin/container-entrypoint +RUN chmod 0755 /usr/local/bin/container-entrypoint +ENTRYPOINT [ "/usr/local/bin/container-entrypoint" ] + +########################### +# User level requirements # +########################### + +# Parameters for default user:group +ARG uid=1000 +ARG user=nodejs +ARG gid=1000 +ARG group=nodejs + +# Add or modify user and group for build and runtime (convenient) +RUN id ${user} > /dev/null 2>&1 && \ + { groupmod -g "${gid}" "${group}" && usermod -md /home/${user} -s /bin/bash -g "${group}" -u "${uid}" "${user}"; } || \ + { groupadd -g "${gid}" "${group}" && useradd -md /home/${user} -s /bin/bash -g "${group}" -u "${uid}" "${user}"; } + +# Copy requirements in non-root user home directory +COPY package.json package-lock.json "/home/${user}/" +RUN chown "${user}:${group}" "/home/${user}/package.json" "/home/${user}/package-lock.json" + +# Install required packages +WORKDIR /home/${user} +RUN npm clean-install && \ + npm cache clean --force 2> /dev/null + +# Switch to non-root user +USER ${user} +WORKDIR /home/${user} + +# Prepare user variables +ENV USER ${user} +ENV HOME=/home/${user} + +# Adapt paths for NodeJS +ENV PATH=/home/${user}/node_modules/.bin:$PATH + +# Get script directory from lazyLib at last to avoid warning w/o invalidating the cache +ARG dir=. diff --git a/lazyDir/container-entrypoint.sh b/lazyDir/container-entrypoint.sh new file mode 100644 index 0000000..801b68d --- /dev/null +++ b/lazyDir/container-entrypoint.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# Configure bash behavior +set -o errexit # exit on failed command +set -o nounset # exit on undeclared variables +set -o pipefail # exit on any failed command in pipes +# set -o xtrace # debug if needed + +# Set magic variables for current file & dir +__DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +__FILE="${__DIR}/$(basename "${BASH_SOURCE[0]}")" +__BASE="$(basename ${__FILE} .sh)" +__ROOT="$(cd "$(dirname "${__DIR}")" && pwd)" +__CWD="$(pwd)" + +# Explicitely define TMPDIR if required +: ${TMPDIR:='/tmp'} + +# Define temp file(s) +TMP_LOG="$(mktemp --tmpdir="${TMPDIR}" "${__BASE}_log.XXXXXXXXXX")" + +# Define variables and their default values +: ${USER:=nodejs} +: ${LIB_DIR:=${__CWD}/node_modules} +: ${LOG_DIR:='/var/tmp'} +: ${MODULES_INIT_LOG:="${LOG_DIR}/modules-init.log"} + +# Clean exit +on_exit () { + rm -f "${TMP_LOG}" + test ! -L "${LIB_DIR}" || rm -f "${LIB_DIR}" + cd "${__CWD}" +} + +trap "on_exit" EXIT + + +######## +# Main # +######## +# Synchronize or link node_modules from user home if needed +if [ ! -L "${LIB_DIR}" -a -d "${LIB_DIR}" ]; then + echo "Synchronize modules directory" &>> "${MODULES_INIT_LOG}" + rsync -halvi --delete "/home/${USER}/node_modules/" "${LIB_DIR}/" +elif [ ! -e "${LIB_DIR}" ]; then + ln -s /home/${USER}/node_modules "${LIB_DIR}" +fi + +# Using bash login +exec "$@" diff --git a/lazyDir/package.json b/lazyDir/package.json index e52d30a..022b47d 100644 --- a/lazyDir/package.json +++ b/lazyDir/package.json @@ -4,6 +4,7 @@ "description": "Dummy vueJS project based on npm and vite", "scripts": { "dev": "vite", + "validate": "npm audit report && eslint . --ext .vue,.js,.ts,.jsx,.tsx --ignore-path .gitignore", "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --ignore-path .gitignore --fix", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview"