From 9ca93b5bb210bbe95204ba19f02644c394767923 Mon Sep 17 00:00:00 2001 From: Cronocide Date: Thu, 12 Sep 2024 21:15:38 -0600 Subject: [PATCH] Added build files --- Dockerfile | 8 ++ Jenkinsfile | 79 ++++++++++++++++++++ build.sh | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 294 insertions(+) create mode 100644 Dockerfile create mode 100644 Jenkinsfile create mode 100755 build.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f2de854 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +# This version of Numpy requires python 3.10 +FROM python:3.10 AS python +ENV PROJ_NAME=fitbit-web-ui-app +ADD ./requirements.txt /requirements.txt +RUN pip3 install -r /requirements.txt +ADD ./ /$PROJ_NAME +WORKDIR /$PROJ_NAME +CMD gunicorn --chdir src app:server --bind 0.0.0.0:80 \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e0a239c --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,79 @@ +#!/usr/bin/env groovy + +pipeline { + agent { + label 'nomad' + } + parameters { + string(name: 'GIT_REPO_NAME', defaultValue: 'git.cronocide.net', description: 'The hostname of the git repository.') + string(name: 'USERN', defaultValue: 'cronocide', description: 'The username of the user in the git repository.') + booleanParam(name: 'PREPARE', defaultValue: true, description: 'Do preparations on this project.') + booleanParam(name: 'INSPECT', defaultValue: false, description: 'Do inspections on this project.') + booleanParam(name: 'BUILD', defaultValue: true, description: 'Do builds on this project.') + booleanParam(name: 'TEST', defaultValue: true, description: 'Do tests on this project.') + booleanParam(name: 'PUBLISH', defaultValue: true, description: 'Publish this project.') + booleanParam(name: 'DEPLOY', defaultValue: false, description: 'Deploy this project.') + } + environment { + WORKSPACE_PATH = "/opt/nomad/alloc/${NOMAD_ALLOC_ID}/${NOMAD_TASK_NAME}${WORKSPACE}" + DESCRIPTION = "Fitbit Wellness Report WebUI" + } + stages { + stage('Prepare') { + when { expression { params.PREPARE } } + steps { + withEnv(['ACTION=cicd_prepare']) { + sh ( script: './build.sh') + } + } + } + stage('Inspect') { + when { expression { params.INSPECT } } + steps { + withEnv(['ACTION=cicd_inspect']) { + sh ( script: './build.sh') + } + } + } + stage('Build') { + when { expression { params.BUILD } } + steps { + withEnv(['ACTION=cicd_build']) { + sh ( script: './build.sh') + } + } + } + stage('Test') { + when { expression { params.TEST } } + steps { + withEnv(['ACTION=cicd_test']) { + sh ( script: './build.sh') + } + } + } + stage('Publish') { + when { expression { params.PUBLISH } } + steps { + withEnv(['ACTION=cicd_publish']) { + sh ( script: './build.sh') + } + } + } + stage('Deploy') { + when { expression { params.DEPLOY } } + steps { + withEnv(['ACTION=cicd_deploy']) { + sh ( script: './build.sh') + } + } + } + } + post { + success { + echo 'Pipeline succeeded.' + } + failure { + echo 'Pipeline failed.' + } + } +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6fa1607 --- /dev/null +++ b/build.sh @@ -0,0 +1,207 @@ +#!/bin/bash +# Generic Build Script for Jenkins +# v1.0 Mar 2023 by Cronocide + +# ___ ___ ___ _ ___ ___ ___ _ _ _____ ___ +# | _ )/ _ \_ _| | | __| _ \ _ \ | /_\_ _| __| +# | _ \ (_) | || |__| _|| / _/ |__ / _ \| | | _| +# |___/\___/___|____|___|_|_\_| |____/_/ \_\_| |___| + +# Get the OS type. Most specific distributions first. +export OS="$(uname -a)" +[[ "$OS" == *"iPhone"* || "$OS" == *"iPad"* ]] && export OS="iOS" +[[ "$OS" == *"ndroid"* ]] && export OS="Android" +[[ "$OS" == *"indows"* ]] && export OS="Windows" +[[ "$OS" == *"arwin"* ]] && export OS="macOS" +[[ "$OS" == *"BSD"* ]] && export OS="BSD" +[[ "$OS" == *"inux"* ]] && export OS="Linux" + +# Verify a list of software or operating systems. Inverted returns for ease-of-use. +__missing_os() { + for i in $(echo "$@"); do + ! __no_os "$i" && return 1 + done + echo "This function is not available on $OS." && return 0 +} + +__no_os() { + [[ "$OS" == "$1" ]] && return 1 + return 0 +} + +# Verify we have dependencies needed to execute successfully +__missing_reqs() { + for i in "$@"; do + [[ "$0" != "$i" ]] && __no_req "$i" && echo "$i is required to perform this function." && return 0 + done + return 1 +} + +__no_req() { + [[ "$(type $1 2>/dev/null)" == '' ]] && return 0 + return 1 +} + +# An abstraction for curl/wget. Accepts url and +__http_get() { + OUTPUT="$2"; [ -z "$OUTPUT" ] && export OUTPUT="-" + if [[ "$(type curl 2>/dev/null)" != '' ]]; then + curl -s "$1" -o "$OUTPUT" + [ "$OUTPUT" != "-" ] && (! [ -f "$OUTPUT" ] || [[ $(cat "$OUTPUT" | tr -d '\0' 2>/dev/null) == '' ]]) && return 1 + return 0 + else + if ! [[ "$(type wget 2>/dev/null)" != '' ]]; then + wget "$1" -O "$2" + [ "$OUTPUT" != "-" ] && (! [ -f "$2" ] || [[ $(cat "$2" | tr -d '\0' 2>/dev/null) == '' ]]) && return 1 + return 0 + fi + fi + echo "curl or wget is required to perform this function." && return 1 +} + +__missing_sed() { + __no_req "sed" && __no_req "gsed" && echo "sed or gsed is required to perform this function." && return 0 +} + +sed_i() { + __missing_sed && return 1; + if [[ "$OS" == "macOS" ]]; then + if [[ $(type gsed 2>/dev/null) != '' ]]; then + gsed -i "$@"; + else + sed -i '' "$@"; + fi; + else + sed -i "$@"; + fi +} + +# Echo errors to stderr +error() { + echo "$@" 1>&2 +} + +# Provide a reliable ISO 8601 timestamp. +isotime() { + [[ "$OS" == "Linux" ]] && echo $(date --iso-8601=seconds) && return 0 + echo $(date +"%Y-%m-%dT%H:%M:%S%z" | sed 's#\(-[0-9]\{2\}\)00#\1:00#') +} + +# ___ _ _ _ _ ___ _____ ___ ___ _ _ ___ +# | __| | | | \| |/ __|_ _|_ _/ _ \| \| / __| +# | _|| |_| | .` | (__ | | | | (_) | .` \__ \ +# |_| \___/|_|\_|\___| |_| |___\___/|_|\_|___/ + + +cicd_prepare() { + # Prepare the build environment. + echo "Preparing for Build" + # TODO + echo "Completed Preparing for Build" +} + +cicd_inspect() { + # Information about the build environment. + echo "Inspecting Build Environment" + env + echo "Completed Inspecting Build Environment" +} + +cicd_build() { + # Build a new software artifact. + __missing_reqs "docker" && exit 1 + echo "Building Software" + docker build --pull=true \ + --label "org.opencontainers.image.vendor=cronocide.net" \ + --label "org.opencontainers.image.title=${PROJECT_NAME}" \ + --label "org.opencontainers.image.url=https://${GIT_REPO_NAME}" \ + --label "org.opencontainers.image.source=https://${IMAGE_NAME}" \ + --label "net.cronocide.build-info.git-repo=${GIT_URL}" \ + --label "net.cronocide.build-info.git-branch=${GIT_BRANCH}" \ + --label "net.cronocide.build-info.git-commit=${GIT_COMMIT}" \ + --label "net.cronocide.build-info.build-time=$(isotime)" \ + --tag="$COMMIT_TAG" \ + --tag="$LATEST_TAG" \ + . + echo "Completed Building Software" +} + +cicd_test() { + # Run tests on the built software artifact. + echo "Testing Software" + # TODO + echo "Completed Testing Software" +} + +cicd_publish() { + # Publish the software to artifact repositories. + __missing_reqs "docker" && exit 1 + echo "Publishing Software" + # TODO: Improve the logic of this Docker login flow. + LOGIN_CREDS="DOCKER_USERNAME DOCKER_PASSWORD" + for CRED in $(echo "$LOGIN_CREDS"); do + [ -z "${!CRED}" ] && echo "Missing $CRED, skipping docker login." && export SKIP_DOCKER_LOGIN=1 + done + [[ "$SKIP_DOCKER_LOGIN" != "1" ]] && docker login "$GIT_REPO_NAME" -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" + docker push ${COMMIT_TAG} + docker push ${LATEST_TAG} + echo "Completed Publishing Software" +} + +cicd_deploy() { + echo "Deploying Software" + # TODO: Check for a nomad folder + if ! [ -f "$PROJECT_NAME".hcl ]; then + __http_get "https://setup.cronocide.com/nomad/base.hcl" "$PROJECT_NAME".hcl + __http_get "https://setup.cronocide.com/nomad/base.volume" "$PROJECT_NAME".volume + sed_i "s#base#$PROJECT_NAME#g" "$PROJECT_NAME".hcl + sed_i "s#image:latest#$PROJECT_NAME:latest#g" "$PROJECT_NAME".hcl + sed_i "s#base#$PROJECT_NAME#g" "$PROJECT_NAME".volume + nomad volume create "$PROJECT_NAME".volume + else + nomad job run "$PROJECT_NAME".hcl + fi + echo "Completed Deploying Software" +} + +prepare_devenv() { + # Place actions here to prepare the environment for development + echo +} + +# __ __ _ ___ _ _ +# | \/ | /_\ |_ _| \| | +# | |\/| |/ _ \ | || .` | +# |_| |_/_/ \_\___|_|\_| + +__missing_reqs "git sed" && exit 1 + +# Verify that an ACTION is supplied in the environment. +BUILD_PREFIX="cicd" +[ -z "$ACTION" ] && error "No ACTION supplied, no action taken." && exit 1 +[[ "$ACTION" != "$BUILD_PREFIX"* ]] && error "Action $ACTION is not recognized as a valid action." +__no_req "$ACTION" && error "Action $ACTION is not recognized as a valid action." && exit 1 + +# Fill in variables if not supplied by CICD +[ -z "$USERN" ] && export USERN=cronocide +[ -z "$GIT_REPO_NAME" ] && export GIT_REPO_NAME=git.cronocide.net + +# Update submodules if the build system did not +git submodule update --init --recursive + +# Prepare build system +prepare_devenv + +# Define needed build strings +DIR=$(cd $(dirname $BASH_SOURCE[0]) && pwd) +PROJECT_NAME="$(git config --local remote.origin.url|sed -n 's#.*/\([^/.]*\)\(\.git\)\{0,1\}$#\1#p')" +IMAGE_NAME=$(echo "$GIT_REPO_NAME/$USERN/$PROJECT_NAME" | tr "[:upper:]" "[:lower:]") +GIT_COMMIT=$(git rev-parse HEAD) +GIT_COMMITTER=$(git log -1 --pretty=format:'%ae') +GIT_URL=$(git config --get remote.origin.url) +GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2) +COMMIT_TAG="${IMAGE_NAME}:${GIT_COMMIT}" +LATEST_TAG="${IMAGE_NAME}:latest" + +# Run specified build task +"$ACTION"