From 63eef9014bf690aa5e40dca95e9b25e125753763 Mon Sep 17 00:00:00 2001 From: Mukul Sharma Date: Thu, 3 Sep 2026 14:49:05 +0530 Subject: [PATCH] Remove unused legacy files, rewrite README for the actual system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ~65 files carried over unmodified from the original org-wide library (GKE/EKS-specific pipeline entry points, real Maven/Gradle/Node/Python/ PHP/Rust build stages entangled with AWS ECR/S3, GCP GAR/GCS, JFrog, Sonar, a hardcoded internal security-scan endpoint, GitHub-specific multi-branch scripting, BU/team org validation, and their supporting pod templates/Helm value templates) — none directly usable without a full rewrite regardless (same effort as what's already built for the 5 supported languages), so removed rather than kept as unused reference. Also drops the Gradle wrapper (build.gradle, gradlew) — its test suite was already removed in an earlier cleanup this session, so nothing was left to build/test with it. README.md rewrote entirely — it was still describing the old system (eksCICD entry point, GKE/EKS, JFrog, S3, buTeamMapping) and even referenced buildObjHelper.groovy, a file this same commit removes. Only the live path remains: 7 stage files, 1 utility, 3 vars files, the 5 language fallback Dockerfiles + Jenkinsfile template, and the dind pod template + build-tools Dockerfile. --- README.md | 119 +- build.gradle | 38 - gradle/wrapper/gradle-wrapper.jar | Bin 48462 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 9 - gradlew | 248 ---- gradlew.bat | 82 -- resources/com/homelab/Dockerfile | 33 - resources/com/homelab/argoApp.yaml | 31 - resources/com/homelab/config.yaml | 14 - resources/com/homelab/cron-values.yaml | 99 -- resources/com/homelab/deployJar.yaml | 203 --- resources/com/homelab/deployment.yaml | 103 -- resources/com/homelab/go-deployment.yaml | 65 - resources/com/homelab/go-values.yaml | 202 --- resources/com/homelab/gradle-deployment.yaml | 103 -- resources/com/homelab/node-deployment.yaml | 64 - resources/com/homelab/node-values.yaml | 184 --- resources/com/homelab/php-deployment.yaml | 55 - resources/com/homelab/php-values.yaml | 169 --- resources/com/homelab/python-2.7-Dockerfile | 29 - .../com/homelab/python-3.10.12-Dockerfile | 31 - resources/com/homelab/python-3.13-Dockerfile | 31 - resources/com/homelab/python-3.7-Dockerfile | 29 - resources/com/homelab/python-deployment.yaml | 64 - resources/com/homelab/python-values.yaml | 242 ---- resources/com/homelab/rust-Dockerfile | 58 - resources/com/homelab/rust-values.yaml | 206 --- resources/com/homelab/validate_configs.py | 1233 ---------------- resources/com/homelab/validate_configs_v2.py | 1261 ----------------- resources/com/homelab/values.yaml | 202 --- resources/com/homelab/values_properties.yaml | 25 - resources/org/homelab/dev-pod.yaml | 47 - resources/org/homelab/prd-pod.yaml | 49 - resources/org/homelab/prd-sidecar-pod.yaml | 68 - resources/org/homelab/stg-pod.yaml | 48 - resources/org/homelab/stg-sidecar-pod.yaml | 67 - .../org/homelab/templates/maven-3.3-jdk-8.sh | 84 -- resources/org/homelab/templates/node-12.22.sh | 50 - resources/org/homelab/toolchain-pod.yaml | 47 - src/com/homelab/stages/automationTest.groovy | 68 - src/com/homelab/stages/buildGo.groovy | 412 ------ src/com/homelab/stages/buildGradle.groovy | 542 ------- src/com/homelab/stages/buildMaven.groovy | 1059 -------------- src/com/homelab/stages/buildNode.groovy | 568 -------- src/com/homelab/stages/buildObjHelper.groovy | 42 - src/com/homelab/stages/buildPhp.groovy | 104 -- src/com/homelab/stages/buildPython.groovy | 221 --- src/com/homelab/stages/buildRust.groovy | 315 ---- src/com/homelab/stages/deployArgoCD.groovy | 906 ------------ src/com/homelab/stages/deployJar.groovy | 32 - .../homelab/stages/deployRingmaster.groovy | 144 -- src/com/homelab/stages/helmGenerator.groovy | 185 --- src/com/homelab/stages/hotFix.groovy | 24 - .../homelab/stages/multiBranchPipeline.groovy | 73 - src/com/homelab/stages/securityScan.groovy | 24 - src/com/homelab/stages/validateBuTeam.groovy | 18 - src/com/homelab/utilities/addSSHKey.groovy | 28 - .../homelab/utilities/buTeamMapping.groovy | 125 -- .../homelab/utilities/constructParam.groovy | 391 ----- .../homelab/utilities/dockerUtilities.groovy | 52 - .../homelab/utilities/getDockerParams.groovy | 61 - .../homelab/utilities/getYamlParameter.groovy | 16 - src/com/homelab/utilities/gitActions.groovy | 342 ----- .../utilities/nodePoolSelection.groovy | 141 -- vars/buildDockerGroovyGke.groovy | 164 --- vars/buildPipeline.groovy | 43 - vars/cdHookRunner.groovy | 195 --- vars/cloudFunctionCICD.groovy | 30 - vars/createEKSconfigs.groovy | 24 - vars/eksCICD.groovy | 155 -- vars/gcpMigration.groovy | 278 ---- vars/gkeCICD.groovy | 202 --- vars/onlyPushtoJfrog.groovy | 115 -- vars/runHooks.groovy | 160 --- vars/triggerCoverageOnly.groovy | 92 -- 75 files changed, 62 insertions(+), 12981 deletions(-) delete mode 100644 build.gradle delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100644 gradlew delete mode 100644 gradlew.bat delete mode 100644 resources/com/homelab/Dockerfile delete mode 100644 resources/com/homelab/argoApp.yaml delete mode 100644 resources/com/homelab/config.yaml delete mode 100644 resources/com/homelab/cron-values.yaml delete mode 100644 resources/com/homelab/deployJar.yaml delete mode 100644 resources/com/homelab/deployment.yaml delete mode 100644 resources/com/homelab/go-deployment.yaml delete mode 100644 resources/com/homelab/go-values.yaml delete mode 100644 resources/com/homelab/gradle-deployment.yaml delete mode 100644 resources/com/homelab/node-deployment.yaml delete mode 100644 resources/com/homelab/node-values.yaml delete mode 100644 resources/com/homelab/php-deployment.yaml delete mode 100644 resources/com/homelab/php-values.yaml delete mode 100644 resources/com/homelab/python-2.7-Dockerfile delete mode 100644 resources/com/homelab/python-3.10.12-Dockerfile delete mode 100644 resources/com/homelab/python-3.13-Dockerfile delete mode 100644 resources/com/homelab/python-3.7-Dockerfile delete mode 100644 resources/com/homelab/python-deployment.yaml delete mode 100644 resources/com/homelab/python-values.yaml delete mode 100644 resources/com/homelab/rust-Dockerfile delete mode 100644 resources/com/homelab/rust-values.yaml delete mode 100644 resources/com/homelab/validate_configs.py delete mode 100644 resources/com/homelab/validate_configs_v2.py delete mode 100644 resources/com/homelab/values.yaml delete mode 100644 resources/com/homelab/values_properties.yaml delete mode 100644 resources/org/homelab/dev-pod.yaml delete mode 100644 resources/org/homelab/prd-pod.yaml delete mode 100644 resources/org/homelab/prd-sidecar-pod.yaml delete mode 100644 resources/org/homelab/stg-pod.yaml delete mode 100644 resources/org/homelab/stg-sidecar-pod.yaml delete mode 100644 resources/org/homelab/templates/maven-3.3-jdk-8.sh delete mode 100644 resources/org/homelab/templates/node-12.22.sh delete mode 100644 resources/org/homelab/toolchain-pod.yaml delete mode 100644 src/com/homelab/stages/automationTest.groovy delete mode 100644 src/com/homelab/stages/buildGo.groovy delete mode 100644 src/com/homelab/stages/buildGradle.groovy delete mode 100644 src/com/homelab/stages/buildMaven.groovy delete mode 100644 src/com/homelab/stages/buildNode.groovy delete mode 100644 src/com/homelab/stages/buildObjHelper.groovy delete mode 100644 src/com/homelab/stages/buildPhp.groovy delete mode 100644 src/com/homelab/stages/buildPython.groovy delete mode 100644 src/com/homelab/stages/buildRust.groovy delete mode 100644 src/com/homelab/stages/deployArgoCD.groovy delete mode 100644 src/com/homelab/stages/deployJar.groovy delete mode 100644 src/com/homelab/stages/deployRingmaster.groovy delete mode 100644 src/com/homelab/stages/helmGenerator.groovy delete mode 100644 src/com/homelab/stages/hotFix.groovy delete mode 100644 src/com/homelab/stages/multiBranchPipeline.groovy delete mode 100644 src/com/homelab/stages/securityScan.groovy delete mode 100644 src/com/homelab/stages/validateBuTeam.groovy delete mode 100644 src/com/homelab/utilities/addSSHKey.groovy delete mode 100644 src/com/homelab/utilities/buTeamMapping.groovy delete mode 100644 src/com/homelab/utilities/constructParam.groovy delete mode 100644 src/com/homelab/utilities/dockerUtilities.groovy delete mode 100644 src/com/homelab/utilities/getDockerParams.groovy delete mode 100644 src/com/homelab/utilities/getYamlParameter.groovy delete mode 100644 src/com/homelab/utilities/gitActions.groovy delete mode 100644 src/com/homelab/utilities/nodePoolSelection.groovy delete mode 100644 vars/buildDockerGroovyGke.groovy delete mode 100644 vars/buildPipeline.groovy delete mode 100644 vars/cdHookRunner.groovy delete mode 100644 vars/cloudFunctionCICD.groovy delete mode 100644 vars/createEKSconfigs.groovy delete mode 100644 vars/eksCICD.groovy delete mode 100644 vars/gcpMigration.groovy delete mode 100644 vars/gkeCICD.groovy delete mode 100644 vars/onlyPushtoJfrog.groovy delete mode 100644 vars/runHooks.groovy delete mode 100644 vars/triggerCoverageOnly.groovy diff --git a/README.md b/README.md index 5681d22..0b035be 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,72 @@ # devops-lib ---- +Jenkins Shared Library for this homelab's CI/CD pipeline. Adapted from a +much larger, company-wide library — everything not needed for a +single-node homelab (GKE/EKS, JFrog, S3, Sonar, org-specific BU/team +validation, and ~65 other files covering languages/deploy-targets this +setup never uses) has been removed rather than carried along unused; see +git history if any of that is ever worth reviving. -## Parameters -Most of the functionality depends on the parameters provided by the users in form of groovy map of key and value pairs. The supported parameters are as below: +## Using it in a service repo -### Required parameters -**repo_name**: The key repo_name is required for checking out the code in a subdirectory. The value is the repository name that you want to checkout - -**build_tool**: This parameter is required to identify which build_tool to use in the pipeline. The supported values are *maven*, *gradle*, *docker*, *python*, *node*, *go*, *php* (and their prefixed variants such as *maven-3.3-jdk-17*, *python-3*, *node-16*, *go1.21*) - -**maintainer** : This parameter is required to send the notification in the slack channel *#ci-cd-status*. Please provide your slack username here - -### Optional parameter - -`devops-lib` is Homelab's Jenkins Shared Library that provides a unified CI/CD pipeline for all microservices across the organisation. Consumer repos load it via `@Library('devops-lib@main')` and call a single `eksCICD(repo)` entry point — the library handles language-specific building (Maven, Go, Gradle, Node.js, Python, PHP), code quality gates (Sonar), Docker image publishing to GAR/ECR, Helm chart updates, and ArgoCD-based deployment to GKE/EKS clusters. Build status and deployment metadata are reported back to Ringmaster and Slack. - -**Stack:** Groovy (Jenkins Shared Library) · ArgoCD · Helm · GCP (GKE, GAR, GCS, Vault, Sonar) · AWS (EKS, ECR, S3) - -## Dependencies - -**push_to_jfrog**: By default master, main, gcp-main, and gcp-master branches push artifacts to jfrog/s3 repository, set this parameter to true to push artifacts from non-master branches - ---- - -## config.yaml schema (consumer services) - -Every service that uses this library must provide a `config.yaml`: - -| Key | Required | Description | -|-----|----------|-------------| -| `repo_name` | yes | GitHub repo slug — must match exactly | -| `build_tool` | yes | `maven`, `go`, `gradle`, `node-*`, `python-*`, `php`, `docker` | -| `dockerBuildVersion` | yes | Drives Dockerfile template: `maven-21`, `go-1.22`, `node-20`, etc. | -| `team` | yes | Team slug — validated against `buTeamMapping` | -| `bu` | yes | Business unit: `supply`, `demand`, `central`, `dataengg`, `datascience`, `mcache`, `infra` | -| `maintainer` | yes | GitHub handle for Slack notifications | -| `deployment_order` | yes | List of ArgoCD application names to deploy | -| `notify_channel` | no | Slack channel (default: `ci-cd-status`) | -| `skip_sonar` | no | Whitelist-gated; see `constructParam.groovy` | -| `deployArgo` | no | Set `false` to skip ArgoCD sync | -| `appConfigEnabled` | no | Required `true` for `stg`; whitelist-gated | -| `skip_test` | no | Skip unit tests (Maven) | -| `push_to_jfrog` | no | Publish JAR to JFrog Artifactory | -| `push_to_s3` | no | Push artifact to S3 | -| `build_packages` | no | System development packages required while compiling (currently consumed by Rust builds; for example `libpq-dev`) | -| `runtime_packages` | no | System runtime libraries required by the compiled binary (currently consumed by Rust builds; for example `libpq5`) | - -## Adding this library to a new service +The entire Jenkinsfile is 2 lines: ```groovy -// Jenkinsfile -@Library('devops-lib@main') _ - -eksCICD([ - repo_name: 'my-service' -]) +@Library('devops-lib') _ +homelabPipeline(repo_name: 'my-service') ``` -Place `config.yaml` at the repo root with the required fields above. +`repo_name` is the only required key. Everything else has a sensible +default — override any of them by passing extra keys to `homelabPipeline`, +or by committing a `config.yaml` to the service repo's own root (merged +in after checkout; repo-committed values win over the Jenkinsfile call). -## Adding a new build stage +| Key | Default | Notes | +|---|---|---| +| `service_name` | `repo_name` | Second path segment under `devops-helm-charts/values/` | +| `argo_app_name` | `repo_name` | Must match the ArgoCD Application's `metadata.name` | +| `harbor_project` | `homelab` | Must be an existing, public Harbor project | +| `helm_repo_url` | `devops-helm-charts` on this Gitea | — | +| `image_tag_yq_path` | `.deployment.image.tag` | **Override this if the app's chart isn't `1.0.0`** — e.g. `sts-2.0.0` uses `.podtemplate.image.tag` instead. Getting this wrong doesn't fail loudly: `yq -i` creates the path if missing rather than erroring, silently leaving the real field un-bumped. | +| `dockerBuildVersion` | none | Only read when the repo has **no Dockerfile of its own** — picks a fallback template (see below). No default; either ship a Dockerfile or set this. | -1. Create `src/com/homelab/stages/build.groovy` implementing `def run(Map config)`. -2. Add a `case` in `src/com/homelab/stages/buildObjHelper.groovy`. -3. Add a Dockerfile template in `resources/com/homelab/-Dockerfile` if needed. +## Pipeline stages + +`checkOut → loadConfig → runHooks(pre_build) → buildDocker → +runHooks(post_build) → updateHelmTag → syncArgoApp → notify`, all inside +a `podTemplate` (`resources/org/homelab/dind-pod.yaml`) via +`node(POD_LABEL) { ... }`. + +- **`loadConfig`** — if the repo has a `config.yaml` at its root, its + keys are merged into the pipeline config (repo values win). +- **`runHooks`** — reads `config.yaml`'s `hooks.pre_build`/`hooks.post_build` + lists, each `{name, script, interpreter, requirements, blocking, + timeout_seconds}`. Blocking by default; `blocking: false` demotes a + failure to advisory (log + continue). Script paths must be + repo-relative (no `..`, no absolute paths). +- **`buildDocker`** — uses the repo's own `Dockerfile` if present; + otherwise renders one from `resources/com/homelab/-Dockerfile` + based on `dockerBuildVersion` (e.g. `go-1.22`, `node-20`, + `python-3.12`, `java-21`, `php-8.3`). All fallback templates pull base + images from Harbor's `base-images` project (mirrored via the separate + `devops-base-images` repo), not Docker Hub directly. +- **`updateHelmTag`** — clones `devops-helm-charts`, bumps the image tag + via `yq` at `image_tag_yq_path`, commits, pushes to `main`. +- **`syncArgoApp`** — calls the ArgoCD REST API to sync `argo_app_name`. + +## Adding a new language's fallback template + +1. Add the base image to `devops-base-images/images.txt`, re-mirror it + into Harbor. +2. Add `resources/com/homelab/-Dockerfile`, parametrized by + `${version}` (rendered via `constructTemplate.groovy`'s + `SimpleTemplateEngine` wrapper). +3. Add a case for it in `buildDocker.groovy`'s `templates` map. + +## Build-tools image + +`resources/org/homelab/build-tools.Dockerfile` bakes git/yq/bash/ +python3+pip/venv/curl into the `docker-cli` container's image, so +nothing gets installed on demand on every single build. Built and pushed +manually (not through any Jenkins job) — see that file's own header +comment. diff --git a/build.gradle b/build.gradle deleted file mode 100644 index d25daf0..0000000 --- a/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - id 'groovy' -} - -repositories { - maven { url 'https://repo.jenkins-ci.org/releases/' } - maven { url 'https://repo.jenkins-ci.org/public/' } - mavenCentral() -} - -sourceSets { - // Main sources are Jenkins Shared Library scripts loaded at runtime by JenkinsPipelineUnit. - // They depend on Jenkins API and are not pre-compiled — loadScript() handles them at test time. - main { - groovy { srcDirs = [] } - java { srcDirs = [] } - } - test { - // test/unit — actual test classes - // test/stubs — minimal stub implementations of Jenkins stage classes used by dispatch tests - groovy { srcDirs = ['test/unit', 'test/stubs'] } - } -} - -dependencies { - testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.22' - // Match the Groovy version bundled by JenkinsPipelineUnit - testImplementation 'org.codehaus.groovy:groovy-all:2.4.21' - testImplementation 'junit:junit:4.13.2' -} - -test { - systemProperty 'user.dir', rootDir.absolutePath - testLogging { - events 'passed', 'skipped', 'failed' - exceptionFormat 'full' - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index b1b8ef56b44f16b14dc800fa8103a6d89abb526f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index b52fb7e..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,9 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip -networkTimeout=10000 -retries=0 -retryBackOffMs=500 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100644 index b9bb139..0000000 --- a/gradlew +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015 the original authors. -# -# 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 -# -# https://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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 24c62d5..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,82 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables, and ensure extensions are enabled -setlocal EnableExtensions - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -"%COMSPEC%" /c exit 1 - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -"%COMSPEC%" /c exit 1 - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -@rem endlocal doesn't take effect until after the line is parsed and variables are expanded -@rem which allows us to clear the local environment before executing the java command -endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel - -:exitWithErrorLevel -@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts -"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/resources/com/homelab/Dockerfile b/resources/com/homelab/Dockerfile deleted file mode 100644 index f71968a..0000000 --- a/resources/com/homelab/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# This sample, non-production-ready template describes an Amazon EC2 instance and an Elastic Load Balancer. -# © 2020 Amazon Web Services, Inc. or its affiliates. All Rights Reserved. -# This AWS Content is provided subject to the terms of the AWS Customer Agreement available at -# http://aws.amazon.com/agreement or other written agreement between Customer and either -# Amazon Web Services, Inc. or Amazon Web Services EMEA SARL or both. -# ARG ACCOUNT_ID=766380763301 - -FROM ${buildRegistry}/build/java:8-jdk-slim-secure_v1.0 -#FROM asia-southeast1-docker.pkg.dev/supply-poc-351106/homelab-devops/java:8 -ARG artifactId=sample -ARG XMS=2G -ARG XMX=2G -ARG target - -ADD https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.15.0/jmx_prometheus_javaagent-0.15.0.jar /opt/jmx_exporter.jar -#ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.17.0/opentelemetry-javaagent.jar /opt/opentelemetry-javaagent.jar - -### Config added through configmap -# COPY config.yaml /opt/config.yaml -EXPOSE 8880 8010 - -COPY ${artifactId}/target/*.jar /opt/target/${artifactId}.jar -RUN mkdir -p /var/log/${artifactId} && touch /var/log/${artifactId}/gc.log - -WORKDIR /opt/target - -CMD ["${artifactId}.jar", "-javaagent:/opt/jmx_exporter.jar=8880:/opt/config/jmx-config.yaml", \ - "-XX:MinRAMPercentage=50.0", "-XX:MaxRAMPercentage=80.0", \ - "-XX:+UseParallelGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails", \ - "-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime", "-XX:+PrintHeapAtGC", \ - "-Xloggc:/var/log/${artifactId}/gc.log", \ - "-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=9000k", \ - "-Xms${XMS}", "-Xmx${XMX}"] diff --git a/resources/com/homelab/argoApp.yaml b/resources/com/homelab/argoApp.yaml deleted file mode 100644 index 1075110..0000000 --- a/resources/com/homelab/argoApp.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: ${env_ns}-${app_name} - namespace: ${argoAppNS} - labels: - bu: ${bu} - team: ${team} - app_name: ${app_name} - service: ${app_name} - env: ${environment} - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - commit_id: ${commit_id} -spec: - destination: - namespace: ${env_ns}-${app_name} - <% if (CLOUD_PROVIDER == 'AWS') { print "server: ${clusterName}" } %> - <% if (CLOUD_PROVIDER == 'GCP') { print "name: ${clusterName}" } %> - project: ${buini}-${teamini} - source: - helm: - valueFiles: - - ../${helm_values_path}/values.yaml - path: ${helm_version} - repoURL: https://github.com/Homelab/devops-helm-charts.git - targetRevision: ${branch_name} - syncPolicy: - syncOptions: - - CreateNamespace=true diff --git a/resources/com/homelab/config.yaml b/resources/com/homelab/config.yaml deleted file mode 100644 index 360484b..0000000 --- a/resources/com/homelab/config.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- - -repo_name: ${repo_name} -maintainer: ${maintainer} -skip_sonar: true -build_tool: docker -dockerBuildVersion: ${dockerBuildVersion} -arch: ${arch} -<% if (excludedMoudles){ println 'excludedModules:';for(module in excludedMoudles){ println ' - '+module }} %>team: ${team} -bu: ${bu} -deployArgo: true -deployment_order: -<% for (deployment in deployment_order){println ' - '+deployment} %><% if (branch_params){ println 'branch_params:'; branch_params.each {entry -> println " $entry.key:";entry.value.each{ val_e -> println " $val_e.key:$val_e.value" }}} %> -notify_channel: ${slack_channel} \ No newline at end of file diff --git a/resources/com/homelab/cron-values.yaml b/resources/com/homelab/cron-values.yaml deleted file mode 100644 index b210c45..0000000 --- a/resources/com/homelab/cron-values.yaml +++ /dev/null @@ -1,99 +0,0 @@ -repoName: ${repo_name} -applicationName: ${app_name} -appType: ${dockerBuildVersion} -deployment: - enabled: false - serviceAccount: - enabled: false -canary: - enabled: false -autoscaling: - enabled: false -podDisruptionBudget: - enabled: false - maxUnavailable: 100% - minAvailable: "" -cron: - enabled: true - concurrencyPolicy: ${concurrencyPolicy} - failedJobsHistoryLimit: ${failedJobsHistoryLimit} - successfulJobsHistoryLimit: ${successfulJobsHistoryLimit} - suspend: false - startingDeadlineSeconds: ${startingDeadlineSeconds} - backoffLimit: ${backoffLimit} - restartPolicy: ${restartPolicy} - env: - - name: PRISMSDK_ENVIRONMENT - value: ${prismsdk_environment} - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module+'' %> - tag: ${tag} - podAnnotations: - <% if (appMetrics && dockerBuildVersion.contains("maven")) { print 'jmx.io/path: /metrics' %> - <% print 'jmx.io/port: "8880"' %> - <% print 'jmx.io/scrape: "true"'} %> - <% if (nodeSelector.contains("arm64") && (environment=="int" || environment=="prd")) { print 'telegraf.influxdata.com/image: 847438129436.dkr.ecr.ap-southeast-1.amazonaws.com/telegraf:1.24.4-arm64' } %> - <% if ( CLOUD_PROVIDER == "GCP" ) { print 'telegraf.influxdata.com/image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/sre/telegraf:1.24.4' } %> - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> -<% if (pod_annotations) {pod_annotations.each{k,v -> if(v instanceof String) { println " ${k}: '${v}'"} else { println " ${k}: ${v}" }};} else {print ''} %> -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} -<% println ' jobs:'; for(val in jobs){ val.each{k,v -> println " ${k}:"; println " resources:"; println " limits:"; println " cpu: ${v.cpu_limit}"; println " memory: ${v.memory_limit}i"; println " requests:"; println " cpu: ${v.cpu_request}"; println " memory: ${v.memory_request}i"; println " schedule: \"${v.schedule}\""; if (v.command instanceof List){ println ' command:'; for (com in v.command ) { if (com != v.command.last()){ println " - ${com}" } else {println " - ${com};touch /tmp/podtermination/telegraf-termination"} }}; if(v.args){ if (v.args instanceof List){ println ' args:'; for (arg in v.args ) { println " - ${arg}" } }}}} %> -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -ingress: - enabled: false -jmxconfig: - enabled: true -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} -nameOverride: "" -namespace: ${env_ns}-${app_name} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: - enabled: false - - - -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> diff --git a/resources/com/homelab/deployJar.yaml b/resources/com/homelab/deployJar.yaml deleted file mode 100644 index 2a3c34d..0000000 --- a/resources/com/homelab/deployJar.yaml +++ /dev/null @@ -1,203 +0,0 @@ ---- -- name: Playbook to deploy Jar - hosts: all - gather_facts: false - become: yes - become_method: sudo - - tasks: - - name: Check if region is configured - shell: "aws configure get default.region" - register: aws_cli_region_reponse - - - name: "ERROR: AWS region missing" - vars: - msg: | - ############################################################################################################################# - ####### AWS REGION IS NOT CONFIGURED PROPERLY. CURRENT REGION: {{ aws_cli_region_reponse.stdout | upper }}, SHOULD BE: AP-SOUTHEAST-1 ####### - ############################################################################################################################# - debug: - msg: "{{ msg.split('\n') }}" - when: aws_cli_region_reponse.stdout != "ap-southeast-1" - failed_when: - - aws_cli_region_reponse.stdout != "ap-southeast-1" - - - name: Check if CICD profile is configured - shell: "aws configure get region --profile cicd" - register: aws_profile_cli_region_reponse - - - name: "ERROR: AWS region missing in profile" - vars: - msg: | - ############################################################################################################################# - ####### AWS REGION IS NOT CONFIGURED PROPERLY. CURRENT REGION: {{ aws_cli_region_reponse.stdout | upper }}, SHOULD BE: AP-SOUTHEAST-1 ####### - ############################################################################################################################# - debug: - msg: "{{ msg.split('\n') }}" - when: aws_profile_cli_region_reponse.stdout != "ap-southeast-1" - failed_when: - - aws_profile_cli_region_reponse.stdout != "ap-southeast-1" - - - name: Check if IAM role is attached - shell: "aws sts get-caller-identity" - register: aws_cli_gci_reponse - - - name: "ERROR: IAM role missing" - vars: - msg: | - ############################################################################################################################# - ############################################### CICD ROLE IS NOT ATTACHED ############################################### - ############################################################################################################################# - debug: - msg: "{{ msg.split('\n') }}" - when: - - aws_cli_gci_reponse.stdout is not search("assumed-role/cicd") - failed_when: - - aws_cli_gci_reponse.stdout is not search("assumed-role/cicd") - - - name: Check if systemd service exists - stat: - path: "/etc/systemd/system/{{ app_name }}.service" - register: service_status - - - name: Systemd service status - vars: - msg: | - ############################################################################################################################# - ################################ SYSTEMD SERVICE {{ app_name | upper }} DOES NOT EXIST. EXITING. ################################## - ############################################################################################################################# - debug: - msg: "{{ msg.split('\n') }}" - when: service_status is defined and not service_status.stat.exists - failed_when: - - not service_status.stat.exists - - - name: Check application directory structure - block: - - - name: Check application directory structure - stat: - path: "/home/ubuntu/{{ app_name }}" - register: app_dir_status - - - name: Application directory does not exist - vars: - msg: | - ############################################################################################################################# - ###################### APPLICATION DIRECTORY: /home/ubuntu/{{ app_name }} DOES NOT EXIST. EXITING. ####################### - ############################################################################################################################# - debug: - msg: "{{ msg.split('\n') }}" - when: app_dir_status is defined and not app_dir_status.stat.exists - failed_when: - - not app_dir_status.stat.exists - - tags: - - artifact_deployment - - - name: Ensure jq is installed - apt: - name: "jq" - state: present - force_apt_get: yes - - - name: Checking if environment file exists, pre-deployment - stat: - path: "/etc/sysconfig/{{ app_name }}" - register: env_file - - - name: Backing up current properties - copy: - src: "/etc/sysconfig/{{ app_name }}" - dest: "/etc/sysconfig/rollback-{{ app_name }}" - owner: ubuntu - group: ubuntu - mode: '0664' - remote_src: yes - force: yes - when: env_file.stat.exists - - - name: Checking if current symlink exists, pre-deployment - stat: - path: "/home/ubuntu/{{ app_name }}/{{ app_name }}-current.jar" - register: current_symlink - - - name: Show output - debug: msg= "{{ current_symlink }}" - - - name: Checking if current artifact exists, pre-deployment - stat: - path: "{{ current_symlink.stat.lnk_source }}" - when: - - current_symlink.stat.exists - - current_symlink.stat.islnk - register: current_artifact - - - name: Show output - debug: msg= "{{ current_artifact }}" - - - name: Stopping systemd service - systemd: - name: '{{ app_name }}' - state: stopped - - - name: Download latest properties - shell: "pull-env '{{ env }}' '{{ app_name }}'" - ignore_errors: yes - - - name: Download latest artifact - copy: - src: "{{ item }}" - dest: "/home/ubuntu/{{ app_name }}/" - owner: ubuntu - group: ubuntu - with_fileglob: - - "{{ repo_name }}/target/*.jar" - - - name: Setting latest_artifact_name - set_fact: - latest_artifact_name: '{{ item.split("/")[-1] }}' - with_fileglob: - - "{{ repo_name }}/target/*.jar" - - - name: Creating rollback symlink - file: - src: "{{ current_artifact.stat.path }}" - dest: "/home/ubuntu/{{ app_name }}/rollback" - state: link - - - name: Removing current symlink - file: - path: "/home/ubuntu/{{ app_name }}/{{ app_name }}-current.jar" - state: absent - - - name: Creating current symlink to latest artifact - file: - src: "/home/ubuntu/{{ app_name }}/{{ latest_artifact_name }}" - dest: "/home/ubuntu/{{ app_name }}/{{ app_name }}-current.jar" - state: link - - - name: Starting systemd service - systemd: - name: '{{ app_name }}' - state: started - daemon_reload: yes - - - name: Trying healthcheck - uri: - url: "http://localhost:{{ app_port }}{{ healthcheck_api }}" - method: GET - register: healthcheck_response - until: healthcheck_response.status == 200 - retries: 60 - delay: 1 - ignore_errors: False - - - name: Healthcheck response - vars: - msg: | - ############################################################################################################################## - HEALTH-CHECK ({{ healthcheck_api }}) RESPONSE: {{ healthcheck_response.status }} - ############################################################################################################################## - debug: - msg: "{{ msg.split('\n') }}" \ No newline at end of file diff --git a/resources/com/homelab/deployment.yaml b/resources/com/homelab/deployment.yaml deleted file mode 100644 index 4713e88..0000000 --- a/resources/com/homelab/deployment.yaml +++ /dev/null @@ -1,103 +0,0 @@ ---- - -app_name: ${app_name} -app_port: ${app_port} -health_check: ${health_check} -module: module_less -bu: ${bu} -team: ${team} -priority: ${priority} -priority_v2: ${priority_v2} -primary_owner: ${primary_owner} -secondary_owner: ${secondary_owner} -initialDelaySeconds: ${initialDelaySeconds} -replica_count: ${replica_count} -environment: - ftr: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=dev - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k - stg: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=dev - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k - int: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=int - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k - prd: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=prd - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k diff --git a/resources/com/homelab/go-deployment.yaml b/resources/com/homelab/go-deployment.yaml deleted file mode 100644 index 07d7688..0000000 --- a/resources/com/homelab/go-deployment.yaml +++ /dev/null @@ -1,65 +0,0 @@ ---- - -app_name: ${app_name} -app_port: ${app_port} -health_check: ${health_check} -module: module_less -bu: ${bu} -team: ${team} -priority: ${priority} -priority_v2: ${priority_v2} -primary_owner: ${primary_owner} -secondary_owner: ${secondary_owner} -initialDelaySeconds: ${initialDelaySeconds} -replica_count: ${replica_count} -environment: - ftr: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: false - command: /app/server - - dev: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: false - command: /app/server - int: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: false - command: /app/server - prd: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: false - command: /app/server - \ No newline at end of file diff --git a/resources/com/homelab/go-values.yaml b/resources/com/homelab/go-values.yaml deleted file mode 100644 index be7858b..0000000 --- a/resources/com/homelab/go-values.yaml +++ /dev/null @@ -1,202 +0,0 @@ -<% if (deploymentStrategy == 'canary'){ print "canary:\n enabled: true\n slackChannel: ${canary.slackChannel}\n enableManualPromotion: ${enableManualPromotion}\n skipAnalysis: ${canary.skipAnalysis}\n service:\n port: 80\n targetPort: ${(grpc_host || grpc_hosts) ? app_port : primary_port}\n progressDeadlineSeconds: ${canary.progressDeadlineSeconds}\n minCanaryReplicas: ${minCanaryReplicas}\n maxCanaryReplicas: ${maxCanaryReplicas}\n analysisInterval: ${canary.analysisInterval}\n analysisThreshold: ${canary.analysisThreshold}\n analysisMaxWeight: ${canary.analysisMaxWeight}\n analysisStepWeight: ${canary.analysisStepWeight}\n analysisMetrics:\n thresholdRangeMin: ${canary.analysisMetrics.thresholdRangeMin}\n interval: ${canary.analysisMetrics.interval}\n" } else { print "canary:\n enabled: false\n" } %> -repoName: ${repo_name} -cron: - enabled: false - serviceAccount: - enabled: false -applicationName: ${app_name} -autoscaling: - enabled: ${as_enabled} - maxReplicas: ${as_max} - minReplicas: ${as_min} - pollingInterval: ${as_poll} - scaledown: - policies: - - periodseconds: ${as_down_period} - type: Pods - value: ${as_down_pod_count} - selectpolicy: Min - stabilizationWindowSeconds: ${as_down_stable_window} - scaleup: - policies: - - periodseconds: ${as_up_period} - type: Pods - value: ${as_up_pod_count} - - periodseconds: ${as_up_period} - type: Percent - value: ${as_up_pod_percentage} - selectpolicy: Max - stabilizationWindowSeconds: ${as_up_stable_window} -<% if(!triggers){print " triggers:\n - metadata:\n value: \"${as_trigger_value}\"\n metricType: ${as_trigger_type}\n type: ${as_trigger_metric}"} else {println ' triggers:'; for(val in triggers){if(val instanceof Map){ val.each{k,v -> if (v instanceof Map) { println ' - '+k+':'; v.each{a,b -> if(b.isNumber()){println " ${a}: \"${b}\"" } else {println " ${a}: ${b}" }}} else {println" ${k}: ${v}"}}}}} %> -replicaCount: ${replica_count} -deployment: - affinity: {} - podDistributionSkew: ${podDistributionSkew} -<% if (deployment_args){println ' args:'; for (arg in deployment_args){ println ' - '+arg}} %> - command: - - ${command} - enabled: true - env: - - name: GOMAXPROCS - value: ${activeProcessorCount} - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} -<% if(lifecycle) { println ' lifecycle:\n preStop:\n exec:\n command:'; for(val in lifecycle.preStop.exec.command){ println " - $val" }} else {print " lifecycle:\n preStop:\n exec:\n command:\n - /bin/bash\n - -c\n - kill -SIGQUIT 1 ; /bin/sleep 120\n "} %> - minReadySeconds: 10 - podAnnotations: - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> - <% if (nodeSelector.contains("arm64") && (environment=="int" || environment=="prd")) { print 'telegraf.influxdata.com/image: 847438129436.dkr.ecr.ap-southeast-1.amazonaws.com/telegraf:1.24.4-arm64' } %> -<% if (pod_annotations) {pod_annotations.each{k,v -> println " ${k}: '${v}'"};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - - containerPort: 8880 - name: metric - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} - updateStrategy: - strategy: -<% if (deploymentStrategy == 'recreate') { print " type: Recreate"} else { print " type: RollingUpdate\n rollingUpdate:\n maxUnavailable: 0%\n maxSurge: ${maxSurge}%" } %> -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -fullnameOverride: "" -ingress: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/server-snippet: | - location ~* "^/api/1.0/search/recent" { - default_type application/json; - return 200 '{"recent_searches": [],"recent_suggestions": [],"limit": 5,"autosuggest_use_recent": true,"show_recent_header": false}'; - } - location ~* "^/api/1.0/anonymous/search/recent" { - default_type application/json; - return 200 '{"recent_searches": [],"recent_suggestions": [],"limit": 5,"autosuggest_use_recent": true,"show_recent_header": false}'; - } - location ~* "^/search-queries/recent" { - default_type application/json; - return 200 '{"recent_searches": [],"recent_suggestions": [],"limit": 5,"autosuggest_use_recent": true,"show_recent_header": false}'; - } -<% if (ingress_annotations) {ingress_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true -<% if(grpc_host){println " grpc_hosts:\n - host: ${grpc_host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else if (grpc_hosts) {println " grpc_hosts:";for(host_arr in grpc_hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> -<% if(host){println " hosts:\n - host: ${host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else {println " hosts:";for(host_arr in hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> ingressClassName: ${ingress_class} - servicePort: http - enableWebsocket: ${enableWebsocket} - slowStart: - enabled: <% if (slowStartWindow) { print "true" } else { print "false" } %> - window: <% if (slowStartWindow) { print "${slowStartWindow}" } else { print "120s" } %> - aggression: <% if (slowStartAggression) { print "${slowStartAggression}" } else { print "1.0" } %> - minPercent: <% if (slowStartMinPercent) { print "${slowStartMinPercent}" } else { print "10" } %> -jmxconfig: - enabled: false -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} -nameOverride: "" -namespace: ${env_ns}-${app_name} -podDisruptionBudget: - enabled: <% if (pdbMaxUnavailable) { print "true" } else { print "false" } %> - maxUnavailable: ${pdbMaxUnavailable} - minAvailable: ${pdbMinAvailable} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: -<% if (service_annotations) { println " annotations:"; service_annotations.each{k,v -> println " ${k}: \"${v}\""};} else { println " annotations: null" } %> - enabled: true -<% if (addon_ports) { for (p in addon_ports) { println " addons:"; println " - name: ${p.name}"; println " targetPort: ${p.targetPort}"; println " type: ${p.type}"; }} else { println " addon_ports: []" } %> -<% if (grpc_port && app_port && (grpc_host || grpc_hosts)) { - println " ports:" - println " - name: http" - println " port: 80" - println " protocol: TCP" - println " targetPort: ${app_port}" - println " grpc_ports:" - println " - name: grpc" - println " port: 80" - println " protocol: TCP" - println " targetPort: ${grpc_port}" -} else { - println " ports:" - println " - name: http" - println " port: 80" - println " protocol: TCP" - println " targetPort: ${primary_port}" -} -%> - type: ClusterIP - -createContourGateway: <% if (createContourGateway) { print "${createContourGateway}" } else { print "false" } %> -contourResponseTimeout: ${contourResponseTimeout} - -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> diff --git a/resources/com/homelab/gradle-deployment.yaml b/resources/com/homelab/gradle-deployment.yaml deleted file mode 100644 index 4713e88..0000000 --- a/resources/com/homelab/gradle-deployment.yaml +++ /dev/null @@ -1,103 +0,0 @@ ---- - -app_name: ${app_name} -app_port: ${app_port} -health_check: ${health_check} -module: module_less -bu: ${bu} -team: ${team} -priority: ${priority} -priority_v2: ${priority_v2} -primary_owner: ${primary_owner} -secondary_owner: ${secondary_owner} -initialDelaySeconds: ${initialDelaySeconds} -replica_count: ${replica_count} -environment: - ftr: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=dev - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k - stg: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=dev - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k - int: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=int - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k - prd: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - -Dspring.profiles.active=prd - - -XX:+UseG1GC - - -XX:+PrintGCDateStamps - - -XX:+PrintGCDetails - - -XX:+PrintGCApplicationStoppedTime - - -XX:+PrintGCApplicationConcurrentTime - - -XX:+PrintHeapAtGC - - -Xloggc:/var/log/gc.log - - -XX:+UseGCLogFileRotation - - -XX:NumberOfGCLogFiles=5 - - -XX:GCLogFileSize=9000k diff --git a/resources/com/homelab/node-deployment.yaml b/resources/com/homelab/node-deployment.yaml deleted file mode 100644 index 08ccde1..0000000 --- a/resources/com/homelab/node-deployment.yaml +++ /dev/null @@ -1,64 +0,0 @@ ---- - -app_name: ${app_name} -app_port: ${app_port} -health_check: ${health_check} -module: module_less -bu: ${bu} -team: ${team} -priority: ${priority} -priority_v2: ${priority_v2} -primary_owner: ${primary_owner} -secondary_owner: ${secondary_owner} -initialDelaySeconds: ${initialDelaySeconds} -replica_count: ${replica_count} -environment: - ftr: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - pm2-config.json - stg: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - pm2-config.json - int: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - pm2-config.json - prd: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - - pm2-config.json - \ No newline at end of file diff --git a/resources/com/homelab/node-values.yaml b/resources/com/homelab/node-values.yaml deleted file mode 100644 index 5047130..0000000 --- a/resources/com/homelab/node-values.yaml +++ /dev/null @@ -1,184 +0,0 @@ -<% if (deploymentStrategy == 'canary'){ print "canary:\n enabled: true\n slackChannel: ${canary.slackChannel}\n enableManualPromotion: ${enableManualPromotion}\n skipAnalysis: ${canary.skipAnalysis}\n service:\n port: 80\n targetPort: ${primary_port}\n progressDeadlineSeconds: ${canary.progressDeadlineSeconds}\n minCanaryReplicas: ${minCanaryReplicas}\n maxCanaryReplicas: ${maxCanaryReplicas}\n analysisInterval: ${canary.analysisInterval}\n analysisThreshold: ${canary.analysisThreshold}\n analysisMaxWeight: ${canary.analysisMaxWeight}\n analysisStepWeight: ${canary.analysisStepWeight}\n analysisMetrics:\n thresholdRangeMin: ${canary.analysisMetrics.thresholdRangeMin}\n interval: ${canary.analysisMetrics.interval}\n" } else { print "canary:\n enabled: false\n" } %>repoName: ${repo_name} -cron: - enabled: false - serviceAccount: - enabled: false -applicationName: ${app_name} -autoscaling: - enabled: ${as_enabled} - maxReplicas: ${as_max} - minReplicas: ${as_min} - pollingInterval: ${as_poll} - scaledown: - policies: - - periodseconds: ${as_down_period} - type: Pods - value: ${as_down_pod_count} - selectpolicy: Min - stabilizationWindowSeconds: ${as_down_stable_window} - scaleup: - policies: - - periodseconds: ${as_up_period} - type: Pods - value: ${as_up_pod_count} - - periodseconds: ${as_up_period} - type: Percent - value: ${as_up_pod_percentage} - selectpolicy: Max - stabilizationWindowSeconds: ${as_up_stable_window} -<% if(!triggers){print " triggers:\n - metadata:\n value: \"${as_trigger_value}\"\n metricType: ${as_trigger_type}\n type: ${as_trigger_metric}"} else {println ' triggers:'; for(val in triggers){if(val instanceof Map){ val.each{k,v -> if (v instanceof Map) { println ' - '+k+':'; v.each{a,b -> if(b.isNumber()){println " ${a}: \"${b}\"" } else {println " ${a}: ${b}" }}} else {println" ${k}: ${v}"}}}}} %> -deployment: - affinity: {} - podDistributionSkew: ${podDistributionSkew} - args: -<% for (arg in deployment_args){ println ' - '+arg}%> - command: - - ${command} - enabled: true - env: null - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} - lifecycle: - preStop: - exec: - command: - - /bin/bash - - -c - - kill -SIGQUIT 1 ; /bin/sleep 60 - minReadySeconds: 10 - podAnnotations: - <% if (appMetrics) { print 'prometheus.io/path: "/actuator/prometheus"' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> -<% if (pod_annotations) {pod_annotations.each{k,v -> println " ${k}: \"${v}\""};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - - containerPort: 9209 - name: pm2-metrics - protocol: TCP - - containerPort: 9200 - name: metrics - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - replicaCount: ${replica_count} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} - updateStrategy: - strategy: -<% if (deploymentStrategy == 'recreate') { print " type: Recreate"} else { print " type: RollingUpdate\n rollingUpdate:\n maxUnavailable: 0%\n maxSurge: ${maxSurge}%" } %> -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -fullnameOverride: "" -ingress: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "false" -<% if (ingress_annotations) {ingress_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true -<% if(grpc_host){println " grpc_hosts:\n - host: ${grpc_host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else if (grpc_hosts) {println " grpc_hosts:";for(host_arr in grpc_hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> -<% if(host){println " hosts:\n - host: ${host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else {println " hosts:";for(host_arr in hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> ingressClassName: ${ingress_class} - servicePort: http - enableWebsocket: ${enableWebsocket} - slowStart: - enabled: <% if (slowStartWindow) { print "true" } else { print "false" } %> - window: <% if (slowStartWindow) { print "${slowStartWindow}" } else { print "120s" } %> - aggression: <% if (slowStartAggression) { print "${slowStartAggression}" } else { print "1.0" } %> - minPercent: <% if (slowStartMinPercent) { print "${slowStartMinPercent}" } else { print "10" } %> -jmxconfig: - enabled: false -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} - node_prom_client_enabled: true -nameOverride: "" -replicaCount: ${replica_count} -namespace: ${env_ns}-${app_name} -podDisruptionBudget: - enabled: <% if (pdbMaxUnavailable) { print "true" } else { print "false" } %> - maxUnavailable: ${pdbMaxUnavailable} - minAvailable: ${pdbMinAvailable} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: -<% if (service_annotations) { println " annotations:"; service_annotations.each{k,v -> println " ${k}: \"${v}\""};} else { println " annotations: null" } %> - enabled: true -<% if (addon_ports) { for (p in addon_ports) { println " addons:"; println " - name: ${p.name}"; println " targetPort: ${p.targetPort}"; println " type: ${p.type}"; }} else { println " addon_ports: []" } %> - ports: - - name: http - port: 80 - protocol: TCP - targetPort: ${primary_port} - type: ClusterIP - -createContourGateway: <% if (createContourGateway) { print "${createContourGateway}" } else { print "false" } %> -contourResponseTimeout: ${contourResponseTimeout} - - -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> diff --git a/resources/com/homelab/php-deployment.yaml b/resources/com/homelab/php-deployment.yaml deleted file mode 100644 index 0347f9e..0000000 --- a/resources/com/homelab/php-deployment.yaml +++ /dev/null @@ -1,55 +0,0 @@ ---- - -app_name: ${app_name} -app_port: ${app_port} -health_check: ${health_check} -module: module_less -bu: ${bu} -team: ${team} -priority: ${priority} -priority_v2: ${priority_v2} -primary_owner: ${primary_owner} -secondary_owner: ${secondary_owner} -initialDelaySeconds: ${initialDelaySeconds} -replica_count: ${replica_count} -environment: - ftr: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - stg: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - int: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - prd: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} \ No newline at end of file diff --git a/resources/com/homelab/php-values.yaml b/resources/com/homelab/php-values.yaml deleted file mode 100644 index f4efe23..0000000 --- a/resources/com/homelab/php-values.yaml +++ /dev/null @@ -1,169 +0,0 @@ -<% if (deploymentStrategy == 'canary'){ print "canary:\n enabled: true\n slackChannel: ${canary.slackChannel}\n enableManualPromotion: ${enableManualPromotion}\n skipAnalysis: ${canary.skipAnalysis}\n service:\n port: 80\n targetPort: ${primary_port}\n progressDeadlineSeconds: ${canary.progressDeadlineSeconds}\n minCanaryReplicas: ${minCanaryReplicas}\n maxCanaryReplicas: ${maxCanaryReplicas}\n analysisInterval: ${canary.analysisInterval}\n analysisThreshold: ${canary.analysisThreshold}\n analysisMaxWeight: ${canary.analysisMaxWeight}\n analysisStepWeight: ${canary.analysisStepWeight}\n analysisMetrics:\n thresholdRangeMin: ${canary.analysisMetrics.thresholdRangeMin}\n interval: ${canary.analysisMetrics.interval}\n" } else { print "canary:\n enabled: false\n" } %>repoName: ${repo_name} -cron: - enabled: false - serviceAccount: - enabled: false -applicationName: ${app_name} -appType: php -autoscaling: - enabled: ${as_enabled} - maxReplicas: ${as_max} - minReplicas: ${as_min} - pollingInterval: ${as_poll} - scaledown: - policies: - - periodseconds: ${as_down_period} - type: Pods - value: ${as_down_pod_count} - selectpolicy: Min - stabilizationWindowSeconds: 300 - scaleup: - policies: - - periodseconds: ${as_up_period} - type: Pods - value: ${as_up_pod_count} - - periodseconds: ${as_up_period} - type: Percent - value: ${as_up_pod_percentage} - selectpolicy: Max - stabilizationWindowSeconds: ${as_up_stable_window} -<% if(!triggers){print " triggers:\n - metadata:\n value: \"${as_trigger_value}\"\n metricType: ${as_trigger_type}\n type: ${as_trigger_metric}"} else {println ' triggers:'; for(val in triggers){if(val instanceof Map){ val.each{k,v -> if (v instanceof Map) { println ' - '+k+':'; v.each{a,b -> if(b.isNumber()){println " ${a}: \"${b}\"" } else {println " ${a}: ${b}" }}} else {println" ${k}: ${v}"}}}}} %> -deployment: - affinity: {} - podDistributionSkew: ${podDistributionSkew} - command: - - apache2-foreground - enabled: true - env: null - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} -<% if(lifecycle) { println ' lifecycle:\n preStop:\n exec:\n command:'; for(val in lifecycle.preStop.exec.command){ println " - $val" }} else {print " lifecycle:\n preStop:\n exec:\n command:\n - /bin/bash\n - -c\n - kill -SIGQUIT 1 ; /bin/sleep 120\n "} %> - minReadySeconds: 10 - podAnnotations: - <% if (nodeSelector.contains("arm64") && (environment=="int" || environment=="prd")) { print 'telegraf.influxdata.com/image: 847438129436.dkr.ecr.ap-southeast-1.amazonaws.com/telegraf:1.24.4-arm64' } %> - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> -<% if (pod_annotations) {pod_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - replicaCount: ${replica_count} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} - updateStrategy: - strategy: -<% if (deploymentStrategy == 'recreate') { print " type: Recreate"} else { print " type: RollingUpdate\n rollingUpdate:\n maxUnavailable: 0%\n maxSurge: ${maxSurge}%" } %> -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -fullnameOverride: "" -replicaCount: ${replica_count} -ingress: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/ssl-redirect: "false" -<% if (ingress_annotations) {ingress_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true -<% if(grpc_host){println " grpc_hosts:\n - host: ${grpc_host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else if (grpc_hosts) {println " grpc_hosts:";for(host_arr in grpc_hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> -<% if(host){println " hosts:\n - host: ${host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else {println " hosts:";for(host_arr in hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> ingressClassName: ${ingress_class} - servicePort: http - enableWebsocket: ${enableWebsocket} - slowStart: - enabled: <% if (slowStartWindow) { print "true" } else { print "false" } %> - window: <% if (slowStartWindow) { print "${slowStartWindow}" } else { print "120s" } %> - aggression: <% if (slowStartAggression) { print "${slowStartAggression}" } else { print "1.0" } %> - minPercent: <% if (slowStartMinPercent) { print "${slowStartMinPercent}" } else { print "10" } %> -jmxconfig: - enabled: false -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} -nameOverride: "" -namespace: ${env_ns}-${app_name} -podDisruptionBudget: - enabled: <% if (pdbMaxUnavailable) { print "true" } else { print "false" } %> - maxUnavailable: ${pdbMaxUnavailable} - minAvailable: ${pdbMinAvailable} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: -<% if (service_annotations) { println " annotations:"; service_annotations.each{k,v -> println " ${k}: \"${v}\""};} else { println " annotations: null" } %> - enabled: true -<% if (addon_ports) { for (p in addon_ports) { println " addons:"; println " - name: ${p.name}"; println " targetPort: ${p.targetPort}"; println " type: ${p.type}"; }} else { println " addon_ports: []" } %> - ports: - - name: http - port: 80 - protocol: TCP - targetPort: ${primary_port} - type: ClusterIP - -createContourGateway: <% if (createContourGateway) { print "${createContourGateway}" } else { print "false" } %> -contourResponseTimeout: ${contourResponseTimeout} - -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> \ No newline at end of file diff --git a/resources/com/homelab/python-2.7-Dockerfile b/resources/com/homelab/python-2.7-Dockerfile deleted file mode 100644 index 37f41d0..0000000 --- a/resources/com/homelab/python-2.7-Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM ${buildRegistry}/build/python:2.7-${arch}-0.8.2 as build-system - -COPY ${modules_requirements_file} /app/${modules_requirements_file} - -FROM build-system as intermediate -# add credentials on build -COPY id_github_jenkins /root/.ssh/id_rsa - -RUN ssh-keyscan github.com >> /root/.ssh/known_hosts && \ - chmod -R 600 /root/.ssh/ - -# temp-package python dependencies -RUN pip download -r /app/requirements.txt -d /temp-package/python - -### create the runtime image ### -FROM build-system as runtime - -# install temp-packageed python dependencies -COPY --from=intermediate /temp-package/python /temp-package/python -RUN pip install /temp-package/python/* && \ - rm -rf /temp-package - -WORKDIR /app - -COPY ./ /app/ -<% if (copy_file) { print "COPY copied_files/* $copy_target"} %> -<% add_files.each { print "COPY ${it.path} ${it.target}\n" } %> - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/resources/com/homelab/python-3.10.12-Dockerfile b/resources/com/homelab/python-3.10.12-Dockerfile deleted file mode 100644 index 0e62a2c..0000000 --- a/resources/com/homelab/python-3.10.12-Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM ${buildRegistry}/build/python:3.10.12-${arch} as build-system - -COPY ${modules_requirements_file} /app/${modules_requirements_file} - -FROM build-system as intermediate -# add credentials on build -COPY id_github_jenkins /root/.ssh/id_rsa - -RUN ssh-keyscan github.com >> /root/.ssh/known_hosts && \ - chmod -R 600 /root/.ssh/ - -# temp-package python dependencies -RUN pip download -r /app/requirements.txt -d /temp-package/python - -### create the runtime image ### -FROM build-system as runtime - -# install temp-packageed python dependencies -COPY --from=intermediate /temp-package/python /temp-package/python -RUN pip install /temp-package/python/* && \ - rm -rf /temp-package && \ - pip install uwsgi && \ - python3 --version - -WORKDIR /app - -COPY ./ /app/ -<% if (copy_file) { print "COPY copied_files/* $copy_target"} %> -<% add_files.each { print "COPY ${it.path} ${it.target}\n" } %> - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/resources/com/homelab/python-3.13-Dockerfile b/resources/com/homelab/python-3.13-Dockerfile deleted file mode 100644 index 4f74754..0000000 --- a/resources/com/homelab/python-3.13-Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM ${buildRegistry}/build/python:3.13-${arch} as build-system - -COPY ${modules_requirements_file} /app/${modules_requirements_file} - -FROM build-system as intermediate -# add credentials on build -COPY id_github_jenkins /root/.ssh/id_rsa - -RUN ssh-keyscan github.com >> /root/.ssh/known_hosts && \ - chmod -R 600 /root/.ssh/ - -# temp-package python dependencies -RUN pip download -r /app/requirements.txt -d /temp-package/python - -### create the runtime image ### -FROM build-system as runtime - -# install temp-packageed python dependencies -COPY --from=intermediate /temp-package/python /temp-package/python -RUN pip install /temp-package/python/* && \ - rm -rf /temp-package && \ - pip install uwsgi && \ - python3 --version - -WORKDIR /app - -COPY ./ /app/ -<% if (copy_file) { print "COPY copied_files/* $copy_target"} %> -<% add_files.each { print "COPY ${it.path} ${it.target}\n" } %> - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/resources/com/homelab/python-3.7-Dockerfile b/resources/com/homelab/python-3.7-Dockerfile deleted file mode 100644 index 36dd6d8..0000000 --- a/resources/com/homelab/python-3.7-Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM ${buildRegistry}/build/python:3.7-${arch}-0.8.2 as build-system - -COPY ${modules_requirements_file} /app/${modules_requirements_file} - -FROM build-system as intermediate -# add credentials on build -COPY id_github_jenkins /root/.ssh/id_rsa - -RUN ssh-keyscan github.com >> /root/.ssh/known_hosts && \ - chmod -R 600 /root/.ssh/ - -# temp-package python dependencies -RUN pip download -r /app/requirements.txt -d /temp-package/python - -### create the runtime image ### -FROM build-system as runtime - -# install temp-packageed python dependencies -COPY --from=intermediate /temp-package/python /temp-package/python -RUN pip install /temp-package/python/* && \ - rm -rf /temp-package - -WORKDIR /app - -COPY ./ /app/ -<% if (copy_file) { print "COPY copied_files/* $copy_target"} %> -<% add_files.each { print "COPY ${it.path} ${it.target}\n" } %> - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/resources/com/homelab/python-deployment.yaml b/resources/com/homelab/python-deployment.yaml deleted file mode 100644 index 3790aa4..0000000 --- a/resources/com/homelab/python-deployment.yaml +++ /dev/null @@ -1,64 +0,0 @@ ---- - -app_name: ${app_name} -app_port: ${app_port} -health_check: ${health_check} -module: module_less -bu: ${bu} -team: ${team} -priority: ${priority} -priority_v2: ${priority_v2} -primary_owner: ${primary_owner} -secondary_owner: ${secondary_owner} -initialDelaySeconds: ${initialDelaySeconds} -replica_count: ${replica_count} -environment: - ftr: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - supervisord_config_path: /app/configurations/supervisord/supervisord.conf - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - stg: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - supervisord_config_path: /app/configurations/supervisord/supervisord.conf - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - int: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - supervisord_config_path: /app/configurations/supervisord/supervisord.conf - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - prd: - as_enabled: ${as_enabled} - as_min: ${as_min} - as_max: ${as_max} - supervisord_config_path: /app/configurations/supervisord/supervisord.conf - cpu_limit: ${cpu_limit} - cpu_request: ${cpu_request} - memory_limit: ${memory_limit} - memory_request: ${memory_request} - deploymentStrategy: ${deploymentStrategy} - appMetrics: ${appMetrics} - deployment_args: - \ No newline at end of file diff --git a/resources/com/homelab/python-values.yaml b/resources/com/homelab/python-values.yaml deleted file mode 100644 index 56a5cf7..0000000 --- a/resources/com/homelab/python-values.yaml +++ /dev/null @@ -1,242 +0,0 @@ -<% if (deploymentStrategy == 'canary'){ print "canary:\n enabled: true\n slackChannel: ${canary.slackChannel}\n enableManualPromotion: ${enableManualPromotion}\n skipAnalysis: ${canary.skipAnalysis}\n service:\n port: 80\n targetPort: ${primary_port}\n progressDeadlineSeconds: ${canary.progressDeadlineSeconds}\n minCanaryReplicas: ${minCanaryReplicas}\n maxCanaryReplicas: ${maxCanaryReplicas}\n analysisInterval: ${canary.analysisInterval}\n analysisThreshold: ${canary.analysisThreshold}\n analysisMaxWeight: ${canary.analysisMaxWeight}\n analysisStepWeight: ${canary.analysisStepWeight}\n analysisMetrics:\n thresholdRangeMin: ${canary.analysisMetrics.thresholdRangeMin}\n interval: ${canary.analysisMetrics.interval}\n" } else { print "canary:\n enabled: false\n" } %>repoName: ${repo_name} -cron: - enabled: false - serviceAccount: - enabled: false -applicationName: ${app_name} -appType: ${dockerBuildVersion} -autoscaling: - enabled: ${as_enabled} - maxReplicas: ${as_max} - minReplicas: ${as_min} - pollingInterval: ${as_poll} - scaledown: - policies: - - periodseconds: ${as_down_period} - type: Pods - value: ${as_down_pod_count} - selectpolicy: Min - stabilizationWindowSeconds: ${as_down_stable_window} - scaleup: - policies: - - periodseconds: ${as_up_period} - type: Pods - value: ${as_up_pod_count} - - periodseconds: ${as_up_period} - type: Percent - value: ${as_up_pod_percentage} - selectpolicy: Max - stabilizationWindowSeconds: ${as_up_stable_window} -<% if(!triggers){print " triggers:\n - metadata:\n value: \"${as_trigger_value}\"\n metricType: ${as_trigger_type}\n type: ${as_trigger_metric}"} else {println ' triggers:'; for(val in triggers){if(val instanceof Map){ val.each{k,v -> if (v instanceof Map) { println ' - '+k+':'; v.each{a,b -> if(b.isNumber()){println " ${a}: \"${b}\"" } else {println " ${a}: ${b}" }}} else {println" ${k}: ${v}"}}}}} %> -deployment: - enabled: <% if (kind == "deployment" || kind == "Deployment") { print "true" } else { print "false" } %> - updateStrategy: - strategy: - rollingUpdate: - maxUnavailable: 0% - maxSurge: <% print "${maxSurge}%" %> - type: RollingUpdate - affinity: {} - podDistributionSkew: ${podDistributionSkew} - env: - - name: SUPERVISORD_CONFIG_FILE - value: ${supervisord_config_path} - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} -<% if(lifecycle) { println ' lifecycle:\n preStop:\n exec:\n command:'; for(val in lifecycle.preStop.exec.command){ println " - $val" }} else {print " lifecycle:\n preStop:\n exec:\n command:\n - /bin/bash\n - -c\n - kill -SIGQUIT 1 ; /bin/sleep 60"} %> - minReadySeconds: 10 - podAnnotations: - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> -<% if (pod_annotations) {pod_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - - containerPort: 9901 - name: metric - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - replicaCount: ${replica_count} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -fullnameOverride: "" -replicaCount: ${replica_count} -ingress: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/rewrite-target: / - nginx.ingress.kubernetes.io/ssl-redirect: "false" - enabled: true -<% if(grpc_host){println " grpc_hosts:\n - host: ${grpc_host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else if (grpc_hosts) {println " grpc_hosts:";for(host_arr in grpc_hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> -<% if(host){println " hosts:\n - host: ${host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else {println " hosts:";for(host_arr in hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> ingressClassName: ${ingress_class} - servicePort: http - enableWebsocket: ${enableWebsocket} - slowStart: - enabled: <% if (slowStartWindow) { print "true" } else { print "false" } %> - window: <% if (slowStartWindow) { print "${slowStartWindow}" } else { print "120s" } %> - aggression: <% if (slowStartAggression) { print "${slowStartAggression}" } else { print "1.0" } %> - minPercent: <% if (slowStartMinPercent) { print "${slowStartMinPercent}" } else { print "10" } %> -jmxconfig: - enabled: true -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} -nameOverride: "" -namespace: ${env_ns}-${app_name} -podDisruptionBudget: - enabled: false - maxUnavailable: 100% - minAvailable: ${pdbMinAvailable} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: -<% if (service_annotations) { println " annotations:"; service_annotations.each{k,v -> println " ${k}: \"${v}\""};} else { println " annotations: null" } %> - enabled: true - ports: - - name: http - port: 80 - protocol: TCP - targetPort: ${app_port} - type: ClusterIP -statefulset: - enabled: <% if (kind == "statefulset" || kind == "StatefulSet" || kind == "statefulSet") { print "true" } else { print "false" } %> - updateStrategy: <% if (statefulset.updateStrategy == "RollingUpdate" || statefulset.updateStrategy == "rollingUpdate" || statefulset.updateStrategy == "rollingupdate") { print "RollingUpdate" } else { print "null" } %> - volumeType: ${statefulset.volumeType} -<% if(statefulset.volumeType == "static" || statefulset.volumeType == "Static") { println " staticVolume:"; statefulset.staticVolume.each{k,v -> println " ${k}: ${v}"};} else {print " staticVolume:\n accessMode: ReadWriteMany\n mountPath: /opt/data\n size: 5Gi\n storageClass: homelab-gp2\n volumeHandle: fs-0e2a97a38b01857d1::fsap-07b146e3bc7aef280\n csiDriver: efs.csi.aws.com"} %> -<% if(statefulset.volumeType == "dynamic" || statefulset.volumeType == "Dynamic") { println " dynamicVolume:"; statefulset.dynamicVolume.each{k,v -> println " ${k}: ${v}"};} else {print " dynamicVolume:\n accessMode: ReadWriteMany\n mountPath: /opt/data\n size: 5Gi\n storageClass: homelab-gp2"} %> -createContourGateway: <% if (createContourGateway) { print "${createContourGateway}" } else { print "false" } %> -contourResponseTimeout: ${contourResponseTimeout} -podtemplate: - affinity: {} - env: - - name: SUPERVISORD_CONFIG_FILE - value: ${supervisord_config_path} - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} -<% if(lifecycle) { println ' lifecycle:\n preStop:\n exec:\n command:'; for(val in lifecycle.preStop.exec.command){ println " - $val" }} else {print " lifecycle:\n preStop:\n exec:\n command:\n - /bin/bash\n - -c\n - kill -SIGQUIT 1 ; /bin/sleep 60"} %> - minReadySeconds: 10 - podAnnotations: - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> -<% if (pod_annotations) {pod_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - - containerPort: 9901 - name: metric - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - replicaCount: ${replica_count} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 - serviceAccount: - annotations: null - enabled: false - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> diff --git a/resources/com/homelab/rust-Dockerfile b/resources/com/homelab/rust-Dockerfile deleted file mode 100644 index d1ef434..0000000 --- a/resources/com/homelab/rust-Dockerfile +++ /dev/null @@ -1,58 +0,0 @@ -FROM ${buildRegistry}/build/rust:${version} AS builder - -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - protobuf-compiler \ - cmake \ - libprotobuf-dev \ - libssl-dev \ - pkg-config \ - openssh-client \ - ca-certificates \ - libsasl2-2 \ - libsasl2-dev \ - clang \ - libclang-dev \ -<% build_packages.each { print " ${it} \\\n" } %> - && rm -rf /var/lib/apt/lists/* - -WORKDIR /usr/src/app - - -COPY id_github_jenkins /root/.ssh/id_rsa - -RUN ssh-keyscan github.com >> /root/.ssh/known_hosts && \ - chmod -R 600 /root/.ssh/ && \ - git config --global url."git@github.com:".insteadOf "https://github.com/" - -# Copy source code -COPY . . - -# Build release binary for the native architecture (amd64) -RUN cargo build --workspace --release - -# Clean up SSH key from builder stage for security -RUN rm -rf /root/.ssh/ - -FROM asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/build/debian:trixie-slim - -# Install runtime dependencies if your app needs them (e.g., SSL) -RUN apt-get update && apt-get install -y --no-install-recommends \ - libssl3t64 \ - ca-certificates \ - libsasl2-2 \ -<% runtime_packages.each { print " ${it} \\\n" } %> - && rm -rf /var/lib/apt/lists/* - -# Set workdir for the binary -WORKDIR /app - -# Copy the compiled amd64 binary from the builder -COPY --from=builder /usr/src/app/target/release/${binary_name} ./server -<% add_files.each { print "COPY ${it.path} ${it.target}\n" } %> - -# Expose port if your app listens on it -EXPOSE 8080 - -# Start the application -CMD ["./server"] diff --git a/resources/com/homelab/rust-values.yaml b/resources/com/homelab/rust-values.yaml deleted file mode 100644 index 338a5be..0000000 --- a/resources/com/homelab/rust-values.yaml +++ /dev/null @@ -1,206 +0,0 @@ -<% if (deploymentStrategy == 'canary'){ print "canary:\n enabled: true\n slackChannel: ${canary.slackChannel}\n enableManualPromotion: ${enableManualPromotion}\n skipAnalysis: ${canary.skipAnalysis}\n service:\n port: 80\n targetPort: ${(grpc_host || grpc_hosts) ? app_port : primary_port}\n progressDeadlineSeconds: ${canary.progressDeadlineSeconds}\n minCanaryReplicas: ${minCanaryReplicas}\n maxCanaryReplicas: ${maxCanaryReplicas}\n analysisInterval: ${canary.analysisInterval}\n analysisThreshold: ${canary.analysisThreshold}\n analysisMaxWeight: ${canary.analysisMaxWeight}\n analysisStepWeight: ${canary.analysisStepWeight}\n analysisMetrics:\n thresholdRangeMin: ${canary.analysisMetrics.thresholdRangeMin}\n interval: ${canary.analysisMetrics.interval}\n" } else { print "canary:\n enabled: false\n" } %> -repoName: ${repo_name} -cron: - enabled: false - serviceAccount: - enabled: false -applicationName: ${app_name} -autoscaling: - enabled: ${as_enabled} - maxReplicas: ${as_max} - minReplicas: ${as_min} - pollingInterval: ${as_poll} - scaledown: - policies: - - periodseconds: ${as_down_period} - type: Pods - value: ${as_down_pod_count} - selectpolicy: Min - stabilizationWindowSeconds: ${as_down_stable_window} - scaleup: - policies: - - periodseconds: ${as_up_period} - type: Pods - value: ${as_up_pod_count} - - periodseconds: ${as_up_period} - type: Percent - value: ${as_up_pod_percentage} - selectpolicy: Max - stabilizationWindowSeconds: ${as_up_stable_window} -<% if(!triggers){print " triggers:\n - metadata:\n value: \"${as_trigger_value}\"\n metricType: ${as_trigger_type}\n type: ${as_trigger_metric}"} else {println ' triggers:'; for(val in triggers){if(val instanceof Map){ val.each{k,v -> if (v instanceof Map) { println ' - '+k+':'; v.each{a,b -> if(b.isNumber()){println " ${a}: \"${b}\"" } else {println " ${a}: ${b}" }}} else {println" ${k}: ${v}"}}}}} %> -replicaCount: ${replica_count} -deployment: - affinity: {} - podDistributionSkew: ${podDistributionSkew} -<% if (deployment_args){println ' args:'; for (arg in deployment_args){ println ' - '+arg}} %> - command: - - ${command} - enabled: true - env: - - name: GOMAXPROCS - value: ${activeProcessorCount} - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} -<% if(lifecycle) { println ' lifecycle:\n preStop:\n exec:\n command:'; for(val in lifecycle.preStop.exec.command){ println " - $val" }} else {print " lifecycle:\n preStop:\n exec:\n command:\n - /bin/bash\n - -c\n - kill -SIGQUIT 1 ; /bin/sleep 120\n "} %> - minReadySeconds: 10 - podAnnotations: - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> - <% if (nodeSelector.contains("arm64") && (environment=="int" || environment=="prd")) { print 'telegraf.influxdata.com/image: 847438129436.dkr.ecr.ap-southeast-1.amazonaws.com/telegraf:1.24.4-arm64' } %> -<% if (pod_annotations) {pod_annotations.each{k,v -> println " ${k}: '${v}'"};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - - containerPort: 8880 - name: metric - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} - # - effect: NoSchedule - # key: kubernetes.io/arch - # operator: Equal - # value: arm64 - updateStrategy: - strategy: -<% if (deploymentStrategy == 'recreate') { print " type: Recreate"} else { print " type: RollingUpdate\n rollingUpdate:\n maxUnavailable: 0%\n maxSurge: ${maxSurge}%" } %> -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -fullnameOverride: "" -ingress: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/server-snippet: | - location ~* "^/api/1.0/search/recent" { - default_type application/json; - return 200 '{"recent_searches": [],"recent_suggestions": [],"limit": 5,"autosuggest_use_recent": true,"show_recent_header": false}'; - } - location ~* "^/api/1.0/anonymous/search/recent" { - default_type application/json; - return 200 '{"recent_searches": [],"recent_suggestions": [],"limit": 5,"autosuggest_use_recent": true,"show_recent_header": false}'; - } - location ~* "^/search-queries/recent" { - default_type application/json; - return 200 '{"recent_searches": [],"recent_suggestions": [],"limit": 5,"autosuggest_use_recent": true,"show_recent_header": false}'; - } -<% if (ingress_annotations) {ingress_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true -<% if(grpc_host){println " grpc_hosts:\n - host: ${grpc_host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else if (grpc_hosts) {println " grpc_hosts:";for(host_arr in grpc_hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> -<% if(host){println " hosts:\n - host: ${host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else {println " hosts:";for(host_arr in hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> ingressClassName: ${ingress_class} - servicePort: http - enableWebsocket: ${enableWebsocket} - slowStart: - enabled: <% if (slowStartWindow) { print "true" } else { print "false" } %> - window: <% if (slowStartWindow) { print "${slowStartWindow}" } else { print "120s" } %> - aggression: <% if (slowStartAggression) { print "${slowStartAggression}" } else { print "1.0" } %> - minPercent: <% if (slowStartMinPercent) { print "${slowStartMinPercent}" } else { print "10" } %> -jmxconfig: - enabled: false -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} -nameOverride: "" -namespace: ${env_ns}-${app_name} -podDisruptionBudget: - enabled: <% if (pdbMaxUnavailable) { print "true" } else { print "false" } %> - maxUnavailable: ${pdbMaxUnavailable} - minAvailable: ${pdbMinAvailable} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: -<% if (service_annotations) { println " annotations:"; service_annotations.each{k,v -> println " ${k}: \"${v}\""};} else { println " annotations: null" } %> - enabled: true -<% if (addon_ports) { for (p in addon_ports) { println " addons:"; println " - name: ${p.name}"; println " targetPort: ${p.targetPort}"; println " type: ${p.type}"; }} else { println " addon_ports: []" } %> -<% if (grpc_port && app_port && (grpc_host || grpc_hosts)) { - println " ports:" - println " - name: http" - println " port: 80" - println " protocol: TCP" - println " targetPort: ${app_port}" - println " grpc_ports:" - println " - name: grpc" - println " port: 80" - println " protocol: TCP" - println " targetPort: ${grpc_port}" -} else { - println " ports:" - println " - name: http" - println " port: 80" - println " protocol: TCP" - println " targetPort: ${primary_port}" -} -%> - type: ClusterIP - -createContourGateway: <% if (createContourGateway) { print "${createContourGateway}" } else { print "false" } %> -contourResponseTimeout: ${contourResponseTimeout} - -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> diff --git a/resources/com/homelab/validate_configs.py b/resources/com/homelab/validate_configs.py deleted file mode 100644 index cc7c289..0000000 --- a/resources/com/homelab/validate_configs.py +++ /dev/null @@ -1,1233 +0,0 @@ -import re -import yamale, validators -from collections.abc import MutableMapping -from deepmerge import merger -from yamale.validators import DefaultValidators, Validator -import yaml -import copy -import os -from collections import Counter -import tempfile -import json -from urllib.parse import urlparse - -from detect_secrets import SecretsCollection -from detect_secrets.settings import transient_settings - -from deepmerge import Merger - -merger = Merger( - [(dict, ["merge"])], - ["override"], - ["override"] -) - -deployable_section_format = "" - -invalid_patterns = { - 'stg': ['int.homelab.int', 'prd.homelab.int'], - 'int': ['stg.homelab.int'], - 'prd': ['int.homelab.int', 'stg.homelab.int'] -} -valid_zk_patterns = { -'dev': ['dev.homelab.int:2181'], - 'stg': ['dev.homelab.int:2181'], - 'int': ['int.homelab.int:2181'], - 'prd': ['config.prd.homelab.int:2181','mlp.prd.homelab.int:2181','comms.prd.homelab.int:2181'] -} - -whitelists_for_preprod = ['consumer', 'scheduler', 'cron'] - -db_patterns = [ - # PostgreSQL - r'^postgresql://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # PostgreSQL (standard) - r'^jdbc:postgresql://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # PostgreSQL (JDBC) - - # MySQL - r'^mysql://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # MySQL (standard) - r'^jdbc:mysql://*', # MySQL (JDBC) with optional query parameters - - # MongoDB - r'^mongodb://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # MongoDB (standard) - r'^mongodb\+srv://(?:\S+):(?:\S+)@(?:\S+)(?:/\S+)?$', # MongoDB (SRV) - - # SQLite - r'^sqlite://(?:\S+)$', # SQLite - - # Redis - r'^redis://(?:\S+):\d+$' # Redis (host:port) -] - -config_files_props = [ - { - 'name': 'application-prd.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'prd' - }, - { - 'name': 'application-int.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'int' - }, - { - 'name': 'application-stg.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'stg' - }, - { - 'name': 'application-dev.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'dev' - }, - { - 'name': 'application-dyn-prd.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'prd' - }, - { - 'name': 'application-dyn-int.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'int' - }, - { - 'name': 'application-dyn-stg.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'stg' - }, - { - 'name': 'application-dyn-dev.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'dev' - } -] - - -class NonRequiredValidator(Validator): - """ Wrapper around existing validators to make fields optional. """ - - def __init__(self, base_validator, *args, **kwargs): - super().__init__(*args, **kwargs) - self.base_validator = base_validator - - def is_valid(self, value): - """ Allow missing values by returning True when value is None """ - if value is None: - return True - return self.base_validator.is_valid(value) - - def to_python(self, value): - return self.base_validator.to_python(value) - -class NumOrStringNum(Validator): - tag = 'num' - - def _is_valid(self, value): - if isinstance(value, (int, float)): - return True - if isinstance(value, str): - try: - float(value) # check if string can be parsed as number - return True - except ValueError: - return False - return False - - def _format(self, value): - """Converts a valid string representation of a number into int or float.""" - if isinstance(value, str): - try: - # convert to int if it's whole number, else float - f = float(value) - return int(f) if f.is_integer() else f - except ValueError: - return value - return value - -class JsonStr(Validator): - tag = 'json_str' # Custom tag to use in the schema - - def _is_valid(self, value): - # Check if value is already a dict or list (already parsed JSON) - if isinstance(value, (dict, list)): - return True - # Check if value is a string that can be parsed as JSON - if isinstance(value, str): - try: - json.loads(value) - return True - except (json.JSONDecodeError, TypeError): - return False - return False - - def _format(self, value): - """Converts a valid JSON string into a parsed JSON object.""" - if isinstance(value, str): - try: - return json.loads(value) - except (json.JSONDecodeError, TypeError): - pass - return value - -class IntOrStringInt(Validator): - tag = 'int' # Custom tag to use in the schema - - def _is_valid(self, value): - # Check if value is an integer - if isinstance(value, int): - return True - # Check if value is a string that can be converted to an integer - if isinstance(value, str): - try: - int(value) - return True - except ValueError: - return False - return False - - def _format(self, value): - """Converts a valid string representation of an integer into an integer.""" - if isinstance(value, str): - try: - return int(value) - except ValueError: - pass - return value - - -class BoolOrStringBool(Validator): - tag = 'bool' - - def _is_valid(self, value): - if isinstance(value, bool): - return True - if isinstance(value, str): - value = value.lower() - if value in ['true', 'false']: - return True - return False - - def _format(self, value): - """Converts a valid string representation of a boolean into a boolean.""" - if isinstance(value, str): - value = value.lower() - if value == 'true': - return True - elif value == 'false': - return False - return value - - -class StringCustom(Validator): - tag = 'str' - - def __init__(self, max=None, required=False): - super().__init__(required=required) - self.max_length = max - self.required = required - - def _is_valid(self, value): - errors = [] - - if self.required and value is None: - errors.append(self.fail("Value is required but missing.")) - - if not isinstance(value, (str, int, bool)): - errors.append(self.fail(f"Invalid type '{type(value).__name__}'. Expected str, int, or bool.")) - - if isinstance(value, str) and self.max_length is not None and len(value) > self.max_length: - errors.append(self.fail(f"Value exceeds maximum length of {self.max_length} characters.")) - - return errors # Return list of errors instead of True/False - - def fail(self, message): - """Override to define a custom fail message""" - return f"Validation Error: {message}" - - def validate(self, value): - """ - Override validate to return a list of validation errors. - """ - errors = self._is_valid(value) # Get errors from _is_valid - - # Validate constraints (if any exist) - for constraint in self._constraints_inst: - error = constraint.is_valid(value) - if error: - if isinstance(error, list): - errors.extend(error) - else: - errors.append(error) - - return errors # Return full list of validation errors - - def is_valid(self, value): - return not self.validate(value) # Returns True if no errors - - def _format(self, value): - if isinstance(value, int): - value = str(value) - if isinstance(value, str) and self.max_length is not None and len(value) > self.max_length: - return value[:self.max_length] - return value - - - -def is_db_url(key, value): - """ - Check if the given key-value pair is a valid database URL or name. - - Parameters: - key_value_pair (tuple): A tuple containing the key and value to check. - - Returns: - bool: True if the value is a valid database URL or name, False otherwise. - """ - - # Regex patterns for different database URL formats - db_patterns = [ - r'^(postgresql://|mysql://|sqlite://|mongodb://|oracle://|mssql://|cockroachdb://|redis://|cassandra://|neo4j://|jdbc:mysql://).+', - r'^[a-zA-Z0-9_]+$', # Just a database name (alphanumeric + underscores) - r'^[a-zA-Z0-9_]+\.([a-zA-Z0-9_]+)$' # Database name with an optional prefix (e.g., schema) - ] - - # Check if the value matches any of the patterns - for pattern in db_patterns: - if re.match(pattern, value): - return True - - return False - - -class Reader: - """ - This class provides methods to read the yml configuration file which has sections. - File structure should be as follows: - - default_Data: - - --- - spring: - profiles: sections_1 - sections_1_data - - --- - spring: - profiles: sections_2 - sections_2_data - - --- - spring: - profiles: sections_3 - sections_3_data - """ - - def __init__(self, config_file_path: str): - self.config_file_path = config_file_path - - def read_config_file(self): - """ - Read the config file and return the data in the form of dictionary. - """ - data = yamale.make_data(self.config_file_path) - return data - - # Function to process each YAML file - def read_without_sections(self): - with open(self.config_file_path, 'r') as file: - try: - data = yaml.safe_load(file) - return data - except yaml.YAMLError as exc: - print(f"Error reading {self.config_file_path}: {exc}", flush=True) - return None - - -class DictUtils: - @staticmethod - def sub_dict(keys: list[str], data: dict) -> dict: - """ - Function to extract the dictionary from the keys - :param keys: keys to extract - :param data: dictionary of data - :return: dictionary with the keys - """ - return {key: data[key] for key in keys} - - @staticmethod - def clean_up(data, keys): - """ - Delete the specified nested key and clean up empty parent keys. - - :param data: The dictionary to clean up. - :param keys: A list of keys specifying the path to the nested key. - """ - if not keys: - return - - current_key = keys[0] - if len(keys) == 1: - data.pop(current_key, None) - else: - next_level = data.get(current_key, None) - if next_level is not None and isinstance(next_level, dict): - DictUtils.clean_up(next_level, keys[1:]) - if not next_level: - data.pop(current_key, None) - - @staticmethod - def remove_common_keys(dict_of_dicts, common_data): - for key in dict_of_dicts: - for common_key in common_data.keys(): - if common_key in dict_of_dicts[key]: - del dict_of_dicts[key][common_key] - return dict_of_dicts - - @staticmethod - def find_common_values(dict_of_dicts): - dict_list = list(dict_of_dicts.values()) - common_values = dict_list[0].copy() - for d in dict_list[1:]: - common_values = {k: v for k, v in common_values.items() if k in d and d[k] == v} - return common_values - - @staticmethod - def remove_keys(dictionary: dict, keys: list): - for key in keys: - if key in dictionary: - del dictionary[key] - - @staticmethod - def extract_dict_with_chosen_keys(dictionary: dict, keys: list): - new_dict = dict() - for key in keys: - if key in dictionary: - new_dict[key] = dictionary[key] - return new_dict - - @staticmethod - def extract_common_data_outside(nested_dict: dict): - common_values = DictUtils.find_common_values(nested_dict) - dict_list = DictUtils.remove_common_keys(nested_dict, common_values) - # Assuming common_values and dict_list are defined - merged_dict = common_values.copy() # Start with keys and values from common_values - merged_dict.update(dict_list) # Adds keys and values from each dictionary in dict_list to merged_dict - return merged_dict - - @staticmethod - def extract_common_majority(dict_of_dicts): - dict_keys = list(dict_of_dicts.keys()) - - if not dict_keys: - return dict_of_dicts - - # Step 1: Identify common keys across all inner dictionaries - common_keys = set(dict_of_dicts[dict_keys[0]].keys()) - for key in dict_keys[1:]: - common_keys.intersection_update(dict_of_dicts[key].keys()) - - common_dict = {} - - for key in common_keys: - if key in ["spring~application~name", "spring~profiles", "spring~config~activate~on-profile","deployable-name"]: - continue - serialized_values = [json.dumps(dict_of_dicts[d][key], sort_keys=True) for d in dict_keys] - most_common_serialized_value, count = Counter(serialized_values).most_common(1)[0] - most_common_value = json.loads(most_common_serialized_value) - common_dict[key] = most_common_value - for d in set(dict_keys): - if json.dumps(dict_of_dicts[d][key], sort_keys=True) == most_common_serialized_value: - del dict_of_dicts[d][key] - dict_of_dicts["common"] = common_dict - return dict_of_dicts - - @staticmethod - def extract_common(dict_of_dicts, common_data: dict): - """ - Extract common data from the dictionary of dictionaries and if some keys are there which are common in - dict_of_dicts but not present in common_data then for them we will use the majority value - :param dict_of_dicts: dict: deployable -> config_key -> config_value - :param common_data: dict: config_key -> config_value - :return: - """ - for key, value in common_data.items(): - for deployable in dict_of_dicts.keys(): - if key in dict_of_dicts[deployable] and dict_of_dicts[deployable][key] == value: - dict_of_dicts[deployable].pop(key) - dict_of_dicts = DictUtils.extract_common_majority(dict_of_dicts) - dict_of_dicts["common"] = merger.merge(common_data, dict_of_dicts.get("common", {})) - return dict_of_dicts - - -class JsonFileUtils: - - @staticmethod - def get_keys_by_line_numbers(file_path: str, line_numbers: list[int]) -> list[str]: - keys = [] - with open(file_path, 'r') as file: - for i, line in enumerate(file, start=1): - if i in line_numbers: - # Extract the key from the line - key = line.strip().split(':')[0].replace('"', '').strip() - keys.append(key) - return keys - - @staticmethod - def write_dict_to_json_file(data: dict, file_path: str): - with open(file_path, "w") as json_file: - json.dump(data, json_file, indent=4) - - -class SecretFinder: - - def __init__(self, file_path: str, is_exclude_keyword_detector: bool = False): - self.file_path = file_path - self.secrets = SecretsCollection() - self.settings = { - "plugins_used": [ - { - "name": "ArtifactoryDetector" - }, - { - "name": "AWSKeyDetector" - }, - { - "name": "AzureStorageKeyDetector" - }, - # { - # "name": "Base64HighEntropyString", - # "limit": 4.5 - # }, - { - "name": "BasicAuthDetector" - }, - { - "name": "CloudantDetector" - }, - { - "name": "DiscordBotTokenDetector" - }, - { - "name": "GitHubTokenDetector" - }, - { - "name": "GitLabTokenDetector" - }, - # { - # "name": "HexHighEntropyString", - # "limit": 3.0 - # }, - { - "name": "IbmCloudIamDetector" - }, - { - "name": "IbmCosHmacDetector" - }, - { - "name": "IPPublicDetector" - }, - { - "name": "JwtTokenDetector" - }, - { - "name": "MailchimpDetector" - }, - { - "name": "NpmDetector" - }, - { - "name": "OpenAIDetector" - }, - { - "name": "PrivateKeyDetector" - }, - { - "name": "PypiTokenDetector" - }, - { - "name": "SendGridDetector" - }, - { - "name": "SoftlayerDetector" - }, - { - "name": "SquareOAuthDetector" - }, - { - "name": "StripeDetector" - }, - { - "name": "TelegramBotTokenDetector" - }, - { - "name": "TwilioKeyDetector" - } - ], - "filters_used": [ - { - "path": "detect_secrets.filters.allowlist.is_line_allowlisted" - }, - { - "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", - "min_level": 2 - }, - { - "path": "detect_secrets.filters.heuristic.is_indirect_reference" - }, - { - "path": "detect_secrets.filters.heuristic.is_likely_id_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_lock_file" - }, - { - "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_potential_uuid" - }, - { - "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" - }, - { - "path": "detect_secrets.filters.heuristic.is_sequential_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_swagger_file" - }, - { - "path": "detect_secrets.filters.heuristic.is_templated_secret" - } - ] - } - if not is_exclude_keyword_detector: - self.settings["plugins_used"].append({ - "name": "KeywordDetector", - "keyword_exclude": "" - } - ) - - def fetch_secrets(self) -> dict: - """ - Function to check for secretes in the filex - :return: line numbers of the secretes in the file - """ - with transient_settings(self.settings): - self.secrets.scan_file(self.file_path) - - return self.secrets.json().get(self.file_path, {}) - - -class SecretHelper: - def __init__(self): - pass - - @staticmethod - def extract_secrets_positions(secrets: dict) -> list[int]: - """ - Function to extract the line numbers of the secrets - :param secrets: secret find result from the secret finder - :return: line numbers of the secretes in the file - """ - line_numbers = set() - if len(secrets) > 0: - for secret in secrets: - line_numbers.add(secret['line_number']) - return sorted(list(line_numbers)) - - @staticmethod - def is_secret_present(file_path: str, is_exclude_keyword_detector: bool = False) -> bool: - """ - Function to check for secretes in the file - :param is_exclude_keyword_detector: if we want to exclude keyword detector - :param file_path: config file_path - :return: error_message and status of the check - """ - secrets = SecretFinder(file_path, is_exclude_keyword_detector).fetch_secrets() - if len(secrets) != 0: - print(json.dumps(secrets, indent=2), flush=True) - secrets_line_numbers = SecretHelper.extract_secrets_positions(secrets) - return len(secrets_line_numbers) > 0 - - @staticmethod - def fetch_secrets_keys_from_json(file_path: str) -> list[str]: - """ - Function to fetch the secret keys from the json file - :return: list of secret keys - """ - secrets = SecretFinder(file_path).fetch_secrets() - secrets_line_numbers = SecretHelper.extract_secrets_positions(secrets) - secret_keys = JsonFileUtils.get_keys_by_line_numbers(file_path, secrets_line_numbers) - return secret_keys - - @staticmethod - def filter_secrets_from_dict(data: dict, secret_keys: list[str]) -> dict: - """ - Function to filter the secrets from the dictionary - :param secret_keys: secret keys - :param data: dictionary of data - :return: dictionary without the secrets - """ - return DictUtils.sub_dict(secret_keys, data) - - @staticmethod - def find_secrets_in_json_files(directory_path: str) -> dict: - - file_to_secrets_dict = dict() - - for filename in os.listdir(directory_path): - if filename.endswith(".json"): - file_path = os.path.join(directory_path, filename) - secrets = SecretHelper.fetch_secrets_keys_from_json(file_path) - file_to_secrets_dict[filename] = secrets - - return file_to_secrets_dict - - -class ParsingHelper: - - @staticmethod - def get_default_section(reader: Reader): - return reader.read_config_file()[0][0] - - @staticmethod - def get_section_data(reader: Reader, section_name: str) -> dict: - data = reader.read_config_file() - default_section = data[0][0] - is_first = True - for section in data: - if is_first: - is_first = False - continue - if (section[0].get('spring', {}).get('profiles', None) == section_name or - section[0].get('spring', {}).get('config', {}).get("activate", {}).get("on-profile", - None) == section_name): - requested_section = default_section - requested_section['spring']['profiles'] = section[0].get('spring', {}).get('profiles', None) - requested_section['spring']['config']['activate']['on-profile'] = section[0].get('spring', {}).get( - 'config', {}).get("activate", {}).get("on-profile", None) - requested_section.update(section[0]) - return requested_section - if (section[0].get('deployable-name', None) == section_name): - requested_section = default_section - requested_section['deployable-name'] = section[0].get('deployable-name', None) - return None - - @staticmethod - def get_all_sections(reader: Reader, merge_on_default_section: bool = True, - update_section_data: bool = True) -> dict: - data = reader.read_config_file() - default_section = data[0][0] - sections = {"default": ParsingHelper.flatten_dict(default_section)} - is_first = True - for section in data: - if is_first: - is_first = False - continue - - current_section_name = ParsingHelper.get_current_section_name(section) - - if update_section_data: - ParsingHelper.remove_spring_section_header(section[0]) - - if current_section_name is not None: - current_section_data = copy.deepcopy(default_section) if merge_on_default_section else {} - current_section_data = merger.merge(ParsingHelper.flatten_dict(current_section_data), - copy.deepcopy(section[0])) - sections[current_section_name] = ParsingHelper.flatten_dict(current_section_data) - return sections - - @staticmethod - def remove_spring_section_header(section: dict, hack: bool = True): - if section and section.get('spring', {}).get('profiles', None) is not None and type( - section.get('spring', {}).get('profiles', None)) == str: - del section['spring']['profiles'] - - if hack: - # IMPORTANT: This is a hack to make the code work with the current schema - global deployable_section_format - deployable_section_format = "spring.profiles" - - if section and section.get('spring', {}).get('config', {}).get("activate", {}).get("on-profile", - None) is not None: - del section['spring']['config']['activate']['on-profile'] - if section and "spring~profiles" in section: - del section["spring~profiles"] - if section and "spring~config~activate~on-profile" in section: - del section["spring~config~activate~on-profile"] - - if section and section.get('deployable-name', None) is not None and type( - section.get('deployable-name', None)) == str: - del section['deployable-name'] - - if hack: - # IMPORTANT: This is a hack to make the code work with the current schema - deployable_section_format = "deployable-name" - - if section and section.get('deployable-name', None) is not None: - del section['deployable-name'] - if section and "deployable-name" in section: - del section["deployable-name"] - - @staticmethod - def get_current_section_name(section): - if section and isinstance(section, tuple) and len(section) > 0: - profiles = section[0].get('spring', {}).get('profiles', None) - if isinstance(profiles, str): - return profiles - on_profile = section[0].get('spring', {}).get('config', {}).get("activate", {}).get("on-profile", None) - if isinstance(on_profile, str): - return on_profile - if section and isinstance(section, tuple) and len(section) > 0: - return section[0].get('deployable-name', None) - return None - - @staticmethod - def flatten_dict(d, parent_key='', sep='~'): - items = [] - for k, v in d.items(): - new_key = f"{parent_key}{sep}{k}" if parent_key else k - if isinstance(v, dict): - items.extend(ParsingHelper.flatten_dict(v, new_key, sep=sep).items()) - else: - items.append((new_key, v)) - return dict(items) - - @staticmethod - def unflatten_dict(d, separator='~'): - result_dict = {} - - for key, value in d.items(): - parts = key.split(separator) - last_d_ref = None - d_ref = result_dict - for part in parts[:-1]: - if not isinstance(d_ref, dict): - last_d_ref[".".join(parts[list(parts).index(part):])] = value - break - else: - if part not in d_ref: - d_ref[part] = {} - last_d_ref = d_ref - d_ref = d_ref[part] - - if not isinstance(d_ref, dict): - last_d_ref[".".join(parts[-2:])] = value - else: - d_ref[parts[-1]] = value - - return result_dict - - @staticmethod - def dump_properties_into_string(data: dict, priority_keys_order: list = ["spring"]): - unflatten_data = ParsingHelper.unflatten_dict(data) - dump_str = "" - for config_key in priority_keys_order: - spring_data = unflatten_data.get(config_key) - if spring_data: - dump_str = dump_str + yaml.dump({config_key: spring_data}, default_flow_style=False) - del unflatten_data[config_key] - if not unflatten_data: - return dump_str - return dump_str + yaml.dump(unflatten_data, default_flow_style=False) - - @staticmethod - def write_to_application_file(env_deployable_dictionary: dict, config_data: dict, target_dir: str, prefix: str): - # if prefix is there then adding a seperator as - - if len(prefix) > 0: - prefix = f"-{prefix}" - - for env_name in env_deployable_dictionary.keys(): - - if env_name not in config_data: - continue - - env_specific_data = config_data[env_name].copy() - - os.makedirs(target_dir, exist_ok=True) - - with open(f'{target_dir}/application{prefix}-{env_name}.yml', 'w') as application_file: - - application_file.write(ParsingHelper.dump_properties_into_string(env_specific_data["common"])) - - for deployable_name in env_deployable_dictionary.get(env_name): - - if not config_data[env_name].get(deployable_name, {}): - continue - - application_file.write(f"\n\n---\n") - - env_deployable_data = dict(config_data[env_name][deployable_name]) - ParsingHelper.remove_spring_section_header(env_deployable_data, False) - env_deployable_data[deployable_section_format] = deployable_name - application_file.write( - ParsingHelper.dump_properties_into_string(env_deployable_data)) - - @staticmethod - def write_to_application_file_with_default_section(env_deployable_dictionary: dict, env_data: dict, prefix: str): - - for env_name in env_deployable_dictionary.keys(): - - with open(f'application{prefix}-{env_name}.yml', 'w') as application_file: - - default_section_data = env_data[env_name]["default"] - - application_file.write(ParsingHelper.dump_properties_into_string(default_section_data)) - - for deployable_name in env_deployable_dictionary.get(env_name): - - if not env_data[env_name].get(deployable_name, {}) or deployable_name == "default": - continue - - application_file.write(f"\n\n---\n") - - env_deployable_data = dict(env_data[env_name][deployable_name]) - env_deployable_data[deployable_section_format] = deployable_name - application_file.write(ParsingHelper.dump_properties_into_string(env_deployable_data)) - - -def is_url(value): - """ - Check if a value is a URL using validators library. - - :param value: Value to check. - :return: True if the value is a URL, False otherwise. - """ - return validators.url(value) - - -def read_yaml_data_with_sections(file_path: str, base_data: dict = {}): - Filereader = Reader(file_path) - file_data = ParsingHelper.get_all_sections(Filereader) - - # base data mai ek section hai file data mai many - # base data dyn data - # file data static data working fine in this - - # base data static data - # file data dyn data - - copy_data = copy.deepcopy(base_data) - - for key in file_data.keys(): - if key in base_data: - base_data[key] = merger.merge(file_data[key], copy_data[key]) - else: - base_data[key] = merger.merge(file_data[key], copy_data["default"]) - - # if "default" in file_data: - # del file_data["default"] - - for key in base_data.keys(): - if key != "default": - base_data[key] = flatten_dict(merger.merge(base_data[key], base_data["default"])) - - return base_data - - -def write_schema_to_temp_file(schema_dict): - flattened_schema = flatten_dict(normalise_keys(schema_dict)) - if 'spring.profiles' in flattened_schema: - del flattened_schema['spring.profiles'] - if 'spring.config.activate.on-profile' in flattened_schema: - del flattened_schema['spring.config.activate.on-profile'] - if 'spring.application.name' in flattened_schema: - del flattened_schema['spring.application.name'] - if 'deployable-name' in flattened_schema: - del flattened_schema['deployable-name'] - - with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file: - yaml.dump(flattened_schema, temp_file) - return temp_file.name - - -def normalise_keys(d, old_char='~', new_char='.'): - """ - Recursively replace old_char with new_char in the keys of the dictionary. - - :param d: The dictionary to process. - :param old_char: The character to replace. - :param new_char: The character to replace with. - :return: A new dictionary with corrected keys. - """ - if not isinstance(d, dict): - return d - - corrected_dict = {} - for k, v in d.items(): - new_key = str(k).replace(old_char, new_char) - if isinstance(v, dict): - corrected_dict[new_key] = normalise_keys(v, old_char, new_char) - else: - corrected_dict[new_key] = v - - return corrected_dict - - -def validate_config_file_against_schema(file_path: str, schema_file: str, configs_directory_path: str, - all_key_required: bool = True): - """ - Function to validate the config file against the schema - It checks following things - 1. check consistency of the config across all the files as schema will have the structure of the config_files - 2. type error if any - 3. rule against the value - :param file_path: file path of the config file - :param schema_file: file path of the schema file - :param all_key_required: config file should have all the keys present in the schema file - :return: True if validation succeeds, False otherwise - """ - temp_schema_path = os.path.join(configs_directory_path, schema_file) - with open(temp_schema_path, 'r') as temp_file: - schema_dict = yaml.safe_load(temp_file) - temp_schema_path_to_use = write_schema_to_temp_file(schema_dict) - try: - _validators = DefaultValidators.copy() - _validators['int'] = IntOrStringInt - _validators['str'] = StringCustom - _validators['bool'] = BoolOrStringBool - _validators['json_str'] = JsonStr - _validators['num'] = NumOrStringNum - - base_data = {} - if not all_key_required: - static_file_reader = Reader(file_path.replace("-dyn", "")) - base_data = ParsingHelper.get_all_sections(static_file_reader) - else: - dyn_file_path = re.sub(r'(-[^-]+$)', r'-dyn\1', file_path) - dyn_file_reader = Reader(dyn_file_path) - base_data = ParsingHelper.get_all_sections(dyn_file_reader) - - schema = yamale.make_schema(temp_schema_path_to_use, validators=_validators) - data = read_yaml_data_with_sections(file_path, base_data) - temp_dict = copy.deepcopy(schema.dict) - schema.dict = flatten_dict(temp_dict) - final_result = True - for section_name, section_data in data.items(): - if section_name == "default": - continue - if section_data is None: - print(f"Section data is empty for {file_path} against schema {schema_file} for section {section_name}", - flush=True) - continue - flattened_data = flatten_dict(normalise_keys(section_data)) - result = schema.validate(flattened_data, section_name, strict=False) - if not result.isValid(): - print( - f"\033[91mValidation failed for environment {file_path.split('-')[-1].split('.')[0]} against schema {schema_file} for section {section_name}\033[0m", - flush=True) - print(result, flush=True) - final_result = False - - return final_result - except yamale.YamaleError as e: - print("YAML validation failed:", flush=True) - print(e.message, flush=True) - return False - - -def validate_no_cross_environment_endpoint(file_path: str, env: str) -> bool: - """ - Function to validate that the config file does not contain cross-environment references. - :param file_path: Path to the config file. - :param env: Environment name (e.g., 'prd', 'int', 'stg'). - :return: True if no cross-environment references are found, False otherwise. - """ - - status = True - - with open(file_path, 'r') as file: - try: - data = list(yaml.safe_load_all(file)) # Use safe_load_all for multiple documents - - for section_content in data: - flattened_data = flatten_dict(section_content) - for key, value in flattened_data.items(): - # Check if any invalid patterns are found in the value for the environment - invalid_patterns_for_env = invalid_patterns.get(env, []) - if isinstance(value, str) and any( - pattern in value for pattern in invalid_patterns_for_env) and not is_db_url(key, value): - print(f"\033[91mInvalid value '{value}' found in {file_path} for environment {env}\033[0m", - flush=True) - status = False - - # Check for specific zookeeper connect string patterns - if 'zookeeper.connect-string' in key or 'zookeeper~connect-string' in key or 'zookeeper.server' in key or 'zookeeper~server' in key: - elements = value.split(',') - valid_patterns = valid_zk_patterns.get(env, []) - for element in elements: - if not any(pattern in element for pattern in valid_patterns): - print( - f"\033[91mInvalid value '{value}' found in {file_path} for environment {env}\033[0m", - flush=True) - status = False - return status - except yaml.YAMLError as exc: - print(f"Error reading {file_path}: {exc}", flush=True) - return False - - -def process_config_files_of_module(configs_directory_path: str): - """ - Function to process the config files of a module - :param configs_directory_path: path where for the given modules files are residing - :return: True if all check passed else false - """ - is_invalid = False - validation_status = True - module_name = os.path.basename(configs_directory_path) - for config_file in config_files_props: - config_file_path = os.path.join(configs_directory_path, config_file['name']) - if not os.path.exists(config_file_path) and '-dev' in config_file['name']: - continue - if not os.path.exists(config_file_path): - canSkip = False - if '-int' in config_file['name']: - for whitelists in whitelists_for_preprod: - if whitelists in module_name: - canSkip = True - break - if canSkip: - continue - print(f"Validation failed for {config_file_path}", flush=True) - print(f"File {config_file_path} does not exist.", flush=True) - validation_status = False - continue - if check_for_secretes(config_file_path): - validation_status = False - - if config_file['type'] == 'static': - if not validate_config_file_against_schema(config_file_path, - config_file['schema-file-name'], - configs_directory_path): - is_invalid = True - print(f"Validation failed for {config_file_path}", flush=True) - if not validate_no_cross_environment_endpoint(config_file_path, config_file['env']): - is_invalid = True - else: - if not validate_config_file_against_schema(config_file_path, - config_file['schema-file-name'], - configs_directory_path, - False): - is_invalid = True - if is_invalid: - validation_status = False - return validation_status - - -def check_for_secretes(file_path: str): - """ - Function to check for secretes in the file - :param file_path: config file_path - :return: error_message and status of the check - """ - - if SecretHelper.is_secret_present(file_path, True): - print("\033[91mFound secrets in the config file.\033[0m", flush=True) - return True - return False - - -def find_non_matching_dicts(dict_list, dict_a): - """ - Find dictionaries in a list where specific keys do not match the given pattern. - - :param dict_list: List of tuples (dict, str). - :param dict_a: Dictionary to flatten and search for URL values. - :return: True if all keys match the pattern in the given dictionaries, False otherwise. - """ - # Step 1: Flatten dict A - flattened_dict_a = flatten_dict(dict_a) - - # Step 2: Identify keys with URL values - url_keys = [key for key, value in flattened_dict_a.items() if is_url(value)] - - # Step 3: Filter dicts in the list where keys do not match the given pattern - all_keys_match = True - for key in url_keys: - key_match_found = False - for d, s in dict_list: - if key in d and matches_pattern(d[key]): - key_match_found = True - if not key_match_found: - print(f"Key is not in proper format, name is : {key}", flush=True) - all_keys_match = False - - return all_keys_match - - -def flatten_dict(d, parent_key='', sep='.'): - """ - Flatten a nested dictionary. - - :param d: Dictionary to flatten. - :param parent_key: String to use as a prefix for keys. - :param sep: Separator for nested keys. - :return: Flattened dictionary. - """ - items = [] - for k, v in d.items(): - new_key = f'{parent_key}{sep}{k}' if parent_key else k - if isinstance(v, MutableMapping): - items.extend(flatten_dict(v, new_key, sep=sep).items()) - else: - items.append((new_key, v)) - return dict(items) - - -def matches_pattern(value): - """ - Check if a value matches the pattern '{{stateless_service..}}'. - - :param value: Value to check. - :return: True if the value matches the pattern, False otherwise. - """ - pattern = re.compile(r'\{\{(?:stateless_service|stateful_service)\.[^.]+\.[^}]+\}\}') - return bool(pattern.match(value)) - - -def clean_up(data, keys): - """ - Delete the specified nested key and clean up empty parent keys. - - :param data: The dictionary to clean up. - :param keys: A list of keys specifying the path to the nested key. - """ - if not keys: - return - - current_key = keys[0] - if len(keys) == 1: - data.pop(current_key, None) - else: - next_level = data.get(current_key, None) - if next_level is not None and isinstance(next_level, dict): - clean_up(next_level, keys[1:]) - if not next_level: - data.pop(current_key, None) - - -def deep_validate(): - configs_path = os.path.join(os.getenv("REPO_ROOT", ""), "configs") - status = True - for root, dirs, files in os.walk(configs_path): - for dir_name in dirs: - if not process_config_files_of_module(os.path.join(root, dir_name)): - status = False - return status - - -if __name__ == "__main__": - if deep_validate(): - print("Validation successful.", flush=True) - os._exit(0) - else: - print("Validation failed.", flush=True) - os._exit(1) diff --git a/resources/com/homelab/validate_configs_v2.py b/resources/com/homelab/validate_configs_v2.py deleted file mode 100644 index ab13468..0000000 --- a/resources/com/homelab/validate_configs_v2.py +++ /dev/null @@ -1,1261 +0,0 @@ -import re -import yamale, validators -from collections.abc import MutableMapping -from deepmerge import merger -from yamale.validators import DefaultValidators, Validator -import yaml -import copy -import os -from collections import Counter -import tempfile -import json -from tabulate import tabulate - -from detect_secrets import SecretsCollection -from detect_secrets.settings import transient_settings - -from deepmerge import Merger - -merger = Merger( - [(dict, ["merge"])], - ["override"], - ["override"] -) - -deployable_section_format = "" - -invalid_patterns = { - 'stg': ['int.homelab.int', 'prd.homelab.int'], - 'int': ['stg.homelab.int'], - 'prd': ['int.homelab.int', 'stg.homelab.int'] -} -valid_zk_patterns = { -'dev': ['dev.homelab.int:2181', 'dev.homelab.int'], - 'stg': ['dev.homelab.int:2181', 'dev.homelab.int'], - 'int': ['int.homelab.int:2181', 'int.homelab.int'], - 'prd': ['config.prd.homelab.int:2181','mlp.prd.homelab.int:2181','comms.prd.homelab.int:2181', 'config.prd.homelab.int','mlp.prd.homelab.int','comms.prd.homelab.int'] -} - -whitelists_for_preprod = ['consumer', 'scheduler', 'cron'] - -db_patterns = [ - # PostgreSQL - r'^postgresql://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # PostgreSQL (standard) - r'^jdbc:postgresql://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # PostgreSQL (JDBC) - - # MySQL - r'^mysql://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # MySQL (standard) - r'^jdbc:mysql://*', # MySQL (JDBC) with optional query parameters - - # MongoDB - r'^mongodb://(?:\S+):(?:\S+)@(?:\S+):\d+/\S+$', # MongoDB (standard) - r'^mongodb\+srv://(?:\S+):(?:\S+)@(?:\S+)(?:/\S+)?$', # MongoDB (SRV) - - # SQLite - r'^sqlite://(?:\S+)$', # SQLite - - # Redis - r'^redis://(?:\S+):\d+$' # Redis (host:port) -] - -config_files_props = [ - { - 'name': 'application-prd.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'prd' - }, - { - 'name': 'application-int.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'int' - }, - { - 'name': 'application-stg.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'stg' - }, - { - 'name': 'application-dev.yml', - 'type': 'static', - 'schema-file-name': 'application-schema.yml', - 'env': 'dev' - }, - { - 'name': 'application-dyn-prd.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'prd' - }, - { - 'name': 'application-dyn-int.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'int' - }, - { - 'name': 'application-dyn-stg.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'stg' - }, - { - 'name': 'application-dyn-dev.yml', - 'type': 'dynamic', - 'schema-file-name': 'application-schema.yml', - 'env': 'dev' - } -] - - -class NonRequiredValidator(Validator): - """ Wrapper around existing validators to make fields optional. """ - - def __init__(self, base_validator, *args, **kwargs): - super().__init__(*args, **kwargs) - self.base_validator = base_validator - - def is_valid(self, value): - """ Allow missing values by returning True when value is None """ - if value is None: - return True - return self.base_validator.is_valid(value) - - def to_python(self, value): - return self.base_validator.to_python(value) - - -class IntOrStringInt(Validator): - tag = 'int' # Custom tag to use in the schema - - def _is_valid(self, value): - # Check if value is an integer - if isinstance(value, int): - return True - # Check if value is a string that can be converted to an integer - if isinstance(value, str): - try: - int(value) - return True - except ValueError: - return False - return False - - def _format(self, value): - """Converts a valid string representation of an integer into an integer.""" - if isinstance(value, str): - try: - return int(value) - except ValueError: - pass - return value - - -class BoolOrStringBool(Validator): - tag = 'bool' - - def _is_valid(self, value): - if isinstance(value, bool): - return True - if isinstance(value, str): - value = value.lower() - if value in ['true', 'false']: - return True - return False - - def _format(self, value): - """Converts a valid string representation of a boolean into a boolean.""" - if isinstance(value, str): - value = value.lower() - if value == 'true': - return True - elif value == 'false': - return False - return value - - -class JsonStr(Validator): - tag = 'json_str' # Custom tag to use in the schema - - def _is_valid(self, value): - # Check if value is already a dict or list (already parsed JSON) - if isinstance(value, (dict, list)): - return True - # Check if value is a string that can be parsed as JSON - if isinstance(value, str): - try: - json.loads(value) - return True - except (json.JSONDecodeError, TypeError): - return False - return False - - def _format(self, value): - """Converts a valid JSON string into a parsed JSON object.""" - if isinstance(value, str): - try: - return json.loads(value) - except (json.JSONDecodeError, TypeError): - pass - return value - -class NumOrStringNum(Validator): - tag = 'num' - - def _is_valid(self, value): - if isinstance(value, (int, float)): - return True - if isinstance(value, str): - try: - float(value) # check if string can be parsed as number - return True - except ValueError: - return False - return False - - def _format(self, value): - """Converts a valid string representation of a number into int or float.""" - if isinstance(value, str): - try: - # convert to int if it's whole number, else float - f = float(value) - return int(f) if f.is_integer() else f - except ValueError: - return value - return value - -class StringCustom(Validator): - tag = 'str' - - def __init__(self, max=None, required=False): - super().__init__(required=required) - self.max_length = max - self.required = required - - def _is_valid(self, value): - errors = [] - - if self.required and value is None: - errors.append(self.fail("Value is required but missing.")) - - if not isinstance(value, (str, int, bool)): - errors.append(self.fail(f"Invalid type '{type(value).__name__}'. Expected str, int, or bool.")) - - if isinstance(value, str) and self.max_length is not None and len(value) > self.max_length: - errors.append(self.fail(f"Value exceeds maximum length of {self.max_length} characters.")) - - return errors # Return list of errors instead of True/False - - def fail(self, message): - """Override to define a custom fail message""" - return f"Validation Error: {message}" - - def validate(self, value): - """ - Override validate to return a list of validation errors. - """ - errors = self._is_valid(value) # Get errors from _is_valid - - # Validate constraints (if any exist) - for constraint in self._constraints_inst: - error = constraint.is_valid(value) - if error: - if isinstance(error, list): - errors.extend(error) - else: - errors.append(error) - - return errors # Return full list of validation errors - - def is_valid(self, value): - return not self.validate(value) # Returns True if no errors - - def _format(self, value): - if isinstance(value, int): - value = str(value) - if isinstance(value, str) and self.max_length is not None and len(value) > self.max_length: - return value[:self.max_length] - return value - - - -def is_db_url(key, value): - """ - Check if the given key-value pair is a valid database URL or name. - - Parameters: - key_value_pair (tuple): A tuple containing the key and value to check. - - Returns: - bool: True if the value is a valid database URL or name, False otherwise. - """ - - # Regex patterns for different database URL formats - db_patterns = [ - r'^(postgresql://|mysql://|sqlite://|mongodb://|oracle://|mssql://|cockroachdb://|redis://|cassandra://|neo4j://|jdbc:mysql://).+', - r'^[a-zA-Z0-9_]+$', # Just a database name (alphanumeric + underscores) - r'^[a-zA-Z0-9_]+\.([a-zA-Z0-9_]+)$' # Database name with an optional prefix (e.g., schema) - ] - - # Check if the value matches any of the patterns - for pattern in db_patterns: - if re.match(pattern, value): - return True - - return False - - -class Reader: - """ - This class provides methods to read the yml configuration file which has sections. - File structure should be as follows: - - default_Data: - - --- - spring: - profiles: sections_1 - sections_1_data - - --- - spring: - profiles: sections_2 - sections_2_data - - --- - spring: - profiles: sections_3 - sections_3_data - """ - - def __init__(self, config_file_path: str): - self.config_file_path = config_file_path - - def read_config_file(self): - """ - Read the config file and return the data in the form of dictionary. - """ - data = yamale.make_data(self.config_file_path) - return data - - # Function to process each YAML file - def read_without_sections(self): - with open(self.config_file_path, 'r') as file: - try: - data = yaml.safe_load(file) - return data - except yaml.YAMLError as exc: - print(f"Error reading {self.config_file_path}: {exc}", flush=True) - return None - - -class DictUtils: - @staticmethod - def sub_dict(keys: list[str], data: dict) -> dict: - """ - Function to extract the dictionary from the keys - :param keys: keys to extract - :param data: dictionary of data - :return: dictionary with the keys - """ - return {key: data[key] for key in keys} - - @staticmethod - def clean_up(data, keys): - """ - Delete the specified nested key and clean up empty parent keys. - - :param data: The dictionary to clean up. - :param keys: A list of keys specifying the path to the nested key. - """ - if not keys: - return - - current_key = keys[0] - if len(keys) == 1: - data.pop(current_key, None) - else: - next_level = data.get(current_key, None) - if next_level is not None and isinstance(next_level, dict): - DictUtils.clean_up(next_level, keys[1:]) - if not next_level: - data.pop(current_key, None) - - @staticmethod - def remove_common_keys(dict_of_dicts, common_data): - for key in dict_of_dicts: - for common_key in common_data.keys(): - if common_key in dict_of_dicts[key]: - del dict_of_dicts[key][common_key] - return dict_of_dicts - - @staticmethod - def find_common_values(dict_of_dicts): - dict_list = list(dict_of_dicts.values()) - common_values = dict_list[0].copy() - for d in dict_list[1:]: - common_values = {k: v for k, v in common_values.items() if k in d and d[k] == v} - return common_values - - @staticmethod - def remove_keys(dictionary: dict, keys: list): - for key in keys: - if key in dictionary: - del dictionary[key] - - @staticmethod - def extract_dict_with_chosen_keys(dictionary: dict, keys: list): - new_dict = dict() - for key in keys: - if key in dictionary: - new_dict[key] = dictionary[key] - return new_dict - - @staticmethod - def extract_common_data_outside(nested_dict: dict): - common_values = DictUtils.find_common_values(nested_dict) - dict_list = DictUtils.remove_common_keys(nested_dict, common_values) - # Assuming common_values and dict_list are defined - merged_dict = common_values.copy() # Start with keys and values from common_values - merged_dict.update(dict_list) # Adds keys and values from each dictionary in dict_list to merged_dict - return merged_dict - - @staticmethod - def extract_common_majority(dict_of_dicts): - dict_keys = list(dict_of_dicts.keys()) - - if not dict_keys: - return dict_of_dicts - - # Step 1: Identify common keys across all inner dictionaries - common_keys = set(dict_of_dicts[dict_keys[0]].keys()) - for key in dict_keys[1:]: - common_keys.intersection_update(dict_of_dicts[key].keys()) - - common_dict = {} - - for key in common_keys: - if key in ["spring~application~name", "spring~profiles", "spring~config~activate~on-profile","deployable-name"]: - continue - serialized_values = [json.dumps(dict_of_dicts[d][key], sort_keys=True) for d in dict_keys] - most_common_serialized_value, count = Counter(serialized_values).most_common(1)[0] - most_common_value = json.loads(most_common_serialized_value) - common_dict[key] = most_common_value - for d in set(dict_keys): - if json.dumps(dict_of_dicts[d][key], sort_keys=True) == most_common_serialized_value: - del dict_of_dicts[d][key] - dict_of_dicts["common"] = common_dict - return dict_of_dicts - - @staticmethod - def extract_common(dict_of_dicts, common_data: dict): - """ - Extract common data from the dictionary of dictionaries and if some keys are there which are common in - dict_of_dicts but not present in common_data then for them we will use the majority value - :param dict_of_dicts: dict: deployable -> config_key -> config_value - :param common_data: dict: config_key -> config_value - :return: - """ - for key, value in common_data.items(): - for deployable in dict_of_dicts.keys(): - if key in dict_of_dicts[deployable] and dict_of_dicts[deployable][key] == value: - dict_of_dicts[deployable].pop(key) - dict_of_dicts = DictUtils.extract_common_majority(dict_of_dicts) - dict_of_dicts["common"] = merger.merge(common_data, dict_of_dicts.get("common", {})) - return dict_of_dicts - - -class JsonFileUtils: - - @staticmethod - def get_keys_by_line_numbers(file_path: str, line_numbers: list[int]) -> list[str]: - keys = [] - with open(file_path, 'r') as file: - for i, line in enumerate(file, start=1): - if i in line_numbers: - # Extract the key from the line - key = line.strip().split(':')[0].replace('"', '').strip() - keys.append(key) - return keys - - @staticmethod - def write_dict_to_json_file(data: dict, file_path: str): - with open(file_path, "w") as json_file: - json.dump(data, json_file, indent=4) - - -class SecretFinder: - - def __init__(self, file_path: str, is_exclude_keyword_detector: bool = False): - self.file_path = file_path - self.secrets = SecretsCollection() - self.settings = { - "plugins_used": [ - { - "name": "ArtifactoryDetector" - }, - { - "name": "AWSKeyDetector" - }, - { - "name": "AzureStorageKeyDetector" - }, - { - "name": "BasicAuthDetector" - }, - { - "name": "CloudantDetector" - }, - { - "name": "DiscordBotTokenDetector" - }, - { - "name": "GitHubTokenDetector" - }, - { - "name": "GitLabTokenDetector" - }, - { - "name": "IbmCloudIamDetector" - }, - { - "name": "IbmCosHmacDetector" - }, - { - "name": "IPPublicDetector" - }, - { - "name": "JwtTokenDetector" - }, - { - "name": "MailchimpDetector" - }, - { - "name": "NpmDetector" - }, - { - "name": "OpenAIDetector" - }, - { - "name": "PrivateKeyDetector" - }, - { - "name": "PypiTokenDetector" - }, - { - "name": "SendGridDetector" - }, - { - "name": "SoftlayerDetector" - }, - { - "name": "SquareOAuthDetector" - }, - { - "name": "StripeDetector" - }, - { - "name": "TelegramBotTokenDetector" - }, - { - "name": "TwilioKeyDetector" - } - ], - "filters_used": [ - { - "path": "detect_secrets.filters.allowlist.is_line_allowlisted" - }, - { - "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", - "min_level": 2 - }, - { - "path": "detect_secrets.filters.heuristic.is_indirect_reference" - }, - { - "path": "detect_secrets.filters.heuristic.is_likely_id_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_lock_file" - }, - { - "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_potential_uuid" - }, - { - "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" - }, - { - "path": "detect_secrets.filters.heuristic.is_sequential_string" - }, - { - "path": "detect_secrets.filters.heuristic.is_swagger_file" - }, - { - "path": "detect_secrets.filters.heuristic.is_templated_secret" - } - ] - } - if not is_exclude_keyword_detector: - self.settings["plugins_used"].append({ - "name": "KeywordDetector", - "keyword_exclude": "" - } - ) - - def fetch_secrets(self) -> dict: - """ - Function to check for secretes in the filex - :return: line numbers of the secretes in the file - """ - with transient_settings(self.settings): - self.secrets.scan_file(self.file_path) - - return self.secrets.json().get(self.file_path, {}) - - -class SecretHelper: - def __init__(self): - pass - - @staticmethod - def extract_secrets_positions(secrets: dict) -> list[int]: - """ - Function to extract the line numbers of the secrets - :param secrets: secret find result from the secret finder - :return: line numbers of the secretes in the file - """ - line_numbers = set() - if len(secrets) > 0: - for secret in secrets: - line_numbers.add(secret['line_number']) - return sorted(list(line_numbers)) - - @staticmethod - def is_secret_present(file_path: str, is_exclude_keyword_detector: bool = False) -> bool: - """ - Function to check for secretes in the file - :param is_exclude_keyword_detector: if we want to exclude keyword detector - :param file_path: config file_path - :return: error_message and status of the check - """ - secrets = SecretFinder(file_path, is_exclude_keyword_detector).fetch_secrets() - if len(secrets) != 0: - print(json.dumps(secrets, indent=2), flush=True) - secrets_line_numbers = SecretHelper.extract_secrets_positions(secrets) - return len(secrets_line_numbers) > 0 - - @staticmethod - def fetch_secrets_keys_from_json(file_path: str) -> list[str]: - """ - Function to fetch the secret keys from the json file - :return: list of secret keys - """ - secrets = SecretFinder(file_path).fetch_secrets() - secrets_line_numbers = SecretHelper.extract_secrets_positions(secrets) - secret_keys = JsonFileUtils.get_keys_by_line_numbers(file_path, secrets_line_numbers) - return secret_keys - - @staticmethod - def filter_secrets_from_dict(data: dict, secret_keys: list[str]) -> dict: - """ - Function to filter the secrets from the dictionary - :param secret_keys: secret keys - :param data: dictionary of data - :return: dictionary without the secrets - """ - return DictUtils.sub_dict(secret_keys, data) - - @staticmethod - def find_secrets_in_json_files(directory_path: str) -> dict: - - file_to_secrets_dict = dict() - - for filename in os.listdir(directory_path): - if filename.endswith(".json"): - file_path = os.path.join(directory_path, filename) - secrets = SecretHelper.fetch_secrets_keys_from_json(file_path) - file_to_secrets_dict[filename] = secrets - - return file_to_secrets_dict - - -class ParsingHelper: - - @staticmethod - def get_default_section(reader: Reader): - return reader.read_config_file()[0][0] - - @staticmethod - def get_section_data(reader: Reader, section_name: str) -> dict: - data = reader.read_config_file() - default_section = data[0][0] - is_first = True - for section in data: - if is_first: - is_first = False - continue - if (section[0].get('spring', {}).get('profiles', None) == section_name or - section[0].get('spring', {}).get('config', {}).get("activate", {}).get("on-profile", - None) == section_name): - requested_section = default_section - requested_section['spring']['profiles'] = section[0].get('spring', {}).get('profiles', None) - requested_section['spring']['config']['activate']['on-profile'] = section[0].get('spring', {}).get( - 'config', {}).get("activate", {}).get("on-profile", None) - requested_section.update(section[0]) - return requested_section - if (section[0].get('deployable-name', None) == section_name): - requested_section = default_section - requested_section['deployable-name'] = section[0].get('deployable-name', None) - return None - - @staticmethod - def get_all_sections(reader: Reader, merge_on_default_section: bool = True, - update_section_data: bool = True) -> dict: - data = reader.read_config_file() - default_section = data[0][0] - sections = {"default": ParsingHelper.flatten_dict(default_section)} - is_first = True - for section in data: - if is_first: - is_first = False - continue - - current_section_name = ParsingHelper.get_current_section_name(section) - - if current_section_name is not None: - current_section_data = copy.deepcopy(default_section) if merge_on_default_section else {} - current_section_data = merger.merge(ParsingHelper.flatten_dict(current_section_data), - copy.deepcopy(section[0])) - sections[current_section_name] = ParsingHelper.flatten_dict(current_section_data) - return sections - - @staticmethod - def remove_spring_section_header(section: dict, hack: bool = True): - if section and section.get('spring', {}).get('profiles', None) is not None and type( - section.get('spring', {}).get('profiles', None)) == str: - del section['spring']['profiles'] - - if hack: - # IMPORTANT: This is a hack to make the code work with the current schema - global deployable_section_format - deployable_section_format = "spring.profiles" - - if section and section.get('spring', {}).get('config', {}).get("activate", {}).get("on-profile", - None) is not None: - del section['spring']['config']['activate']['on-profile'] - if section and "spring~profiles" in section: - del section["spring~profiles"] - if section and "spring~config~activate~on-profile" in section: - del section["spring~config~activate~on-profile"] - - if section and section.get('deployable-name', None) is not None and type( - section.get('deployable-name', None)) == str: - del section['deployable-name'] - - if hack: - # IMPORTANT: This is a hack to make the code work with the current schema - deployable_section_format = "deployable-name" - - if section and section.get('deployable-name', None) is not None: - del section['deployable-name'] - if section and "deployable-name" in section: - del section["deployable-name"] - - @staticmethod - def get_current_section_name(section): - if section and isinstance(section, tuple) and len(section) > 0: - profiles = section[0].get('spring', {}).get('profiles', None) - if isinstance(profiles, str): - return profiles - on_profile = section[0].get('spring', {}).get('config', {}).get("activate", {}).get("on-profile", None) - if isinstance(on_profile, str): - return on_profile - if section and isinstance(section, tuple) and len(section) > 0: - return section[0].get('deployable-name', None) - return None - - @staticmethod - def flatten_dict(d, parent_key='', sep='~'): - items = [] - for k, v in d.items(): - new_key = f"{parent_key}{sep}{k}" if parent_key else k - if isinstance(v, dict): - items.extend(ParsingHelper.flatten_dict(v, new_key, sep=sep).items()) - else: - items.append((new_key, v)) - return dict(items) - - @staticmethod - def unflatten_dict(d, separator='~'): - result_dict = {} - - for key, value in d.items(): - parts = key.split(separator) - last_d_ref = None - d_ref = result_dict - for part in parts[:-1]: - if not isinstance(d_ref, dict): - last_d_ref[".".join(parts[list(parts).index(part):])] = value - break - else: - if part not in d_ref: - d_ref[part] = {} - last_d_ref = d_ref - d_ref = d_ref[part] - - if not isinstance(d_ref, dict): - last_d_ref[".".join(parts[-2:])] = value - else: - d_ref[parts[-1]] = value - - return result_dict - - @staticmethod - def dump_properties_into_string(data: dict, priority_keys_order: list = ["spring"]): - unflatten_data = ParsingHelper.unflatten_dict(data) - dump_str = "" - for config_key in priority_keys_order: - spring_data = unflatten_data.get(config_key) - if spring_data: - dump_str = dump_str + yaml.dump({config_key: spring_data}, default_flow_style=False) - del unflatten_data[config_key] - if not unflatten_data: - return dump_str - return dump_str + yaml.dump(unflatten_data, default_flow_style=False) - - @staticmethod - def write_to_application_file(env_deployable_dictionary: dict, config_data: dict, target_dir: str, prefix: str): - # if prefix is there then adding a seperator as - - if len(prefix) > 0: - prefix = f"-{prefix}" - - for env_name in env_deployable_dictionary.keys(): - - if env_name not in config_data: - continue - - env_specific_data = config_data[env_name].copy() - - os.makedirs(target_dir, exist_ok=True) - - with open(f'{target_dir}/application{prefix}-{env_name}.yml', 'w') as application_file: - - application_file.write(ParsingHelper.dump_properties_into_string(env_specific_data["common"])) - - for deployable_name in env_deployable_dictionary.get(env_name): - - if not config_data[env_name].get(deployable_name, {}): - continue - - application_file.write(f"\n\n---\n") - - env_deployable_data = dict(config_data[env_name][deployable_name]) - ParsingHelper.remove_spring_section_header(env_deployable_data, False) - env_deployable_data[deployable_section_format] = deployable_name - application_file.write( - ParsingHelper.dump_properties_into_string(env_deployable_data)) - - @staticmethod - def write_to_application_file_with_default_section(env_deployable_dictionary: dict, env_data: dict, prefix: str): - - for env_name in env_deployable_dictionary.keys(): - - with open(f'application{prefix}-{env_name}.yml', 'w') as application_file: - - default_section_data = env_data[env_name]["default"] - - application_file.write(ParsingHelper.dump_properties_into_string(default_section_data)) - - for deployable_name in env_deployable_dictionary.get(env_name): - - if not env_data[env_name].get(deployable_name, {}) or deployable_name == "default": - continue - - application_file.write(f"\n\n---\n") - - env_deployable_data = dict(env_data[env_name][deployable_name]) - env_deployable_data[deployable_section_format] = deployable_name - application_file.write(ParsingHelper.dump_properties_into_string(env_deployable_data)) - - -def is_url(value): - """ - Check if a value is a URL using validators library. - - :param value: Value to check. - :return: True if the value is a URL, False otherwise. - """ - return validators.url(value) - - -def read_yaml_data_with_sections(file_path: str, base_data: dict = {}): - Filereader = Reader(file_path) - file_data = ParsingHelper.get_all_sections(Filereader) - - # base data mai ek section hai file data mai many - # base data dyn data - # file data static data working fine in this - - # base data static data - # file data dyn data - - copy_data = copy.deepcopy(base_data) - - for key in file_data.keys(): - if key in base_data: - base_data[key] = merger.merge(file_data[key], copy_data[key]) - else: - base_data[key] = merger.merge(file_data[key], copy_data["default"]) - - # if "default" in file_data: - # del file_data["default"] - - for key in base_data.keys(): - if key != "default": - base_data[key] = flatten_dict(merger.merge(base_data[key], base_data["default"])) - - return base_data - - -def write_schema_to_temp_file(schema_dict): - flattened_schema = flatten_dict(normalise_keys(schema_dict)) - - with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file: - yaml.dump(flattened_schema, temp_file) - return temp_file.name - - -def normalise_keys(d, old_char='~', new_char='.'): - """ - Recursively replace old_char with new_char in the keys of the dictionary. - - :param d: The dictionary to process. - :param old_char: The character to replace. - :param new_char: The character to replace with. - :return: A new dictionary with corrected keys. - """ - if not isinstance(d, dict): - return d - - corrected_dict = {} - for k, v in d.items(): - new_key = str(k).replace(old_char, new_char) - if isinstance(v, dict): - corrected_dict[new_key] = normalise_keys(v, old_char, new_char) - else: - corrected_dict[new_key] = v - - return corrected_dict - - -def validate_config_file_against_schema(file_path: str, schema_file: str, configs_directory_path: str, - all_key_required: bool = True): - """ - Function to validate the config file against the schema - It checks following things - 1. check consistency of the config across all the files as schema will have the structure of the config_files - 2. type error if any - 3. rule against the value - :param file_path: file path of the config file - :param schema_file: file path of the schema file - :param all_key_required: config file should have all the keys present in the schema file - :return: True if validation succeeds, False otherwise - """ - final_result= True - temp_schema_path = os.path.join(configs_directory_path, schema_file) - with open(temp_schema_path, 'r') as temp_file: - schema_dict = yaml.safe_load(temp_file) - temp_schema_path_to_use = write_schema_to_temp_file(schema_dict) - try: - _validators = DefaultValidators.copy() - _validators['int'] = IntOrStringInt - _validators['str'] = StringCustom - _validators['bool'] = BoolOrStringBool - _validators['json_str'] = JsonStr - _validators['num'] = NumOrStringNum - - base_data = {} - if not all_key_required: - static_file_reader = Reader(file_path.replace("-dyn", "")) - base_data = ParsingHelper.get_all_sections(static_file_reader) - else: - dyn_file_path = re.sub(r'(-[^-]+$)', r'-dyn\1', file_path) - dyn_file_reader = Reader(dyn_file_path) - base_data = ParsingHelper.get_all_sections(dyn_file_reader) - - schema = yamale.make_schema(temp_schema_path_to_use, validators=_validators) - data = read_yaml_data_with_sections(file_path, base_data) - temp_dict = copy.deepcopy(schema.dict) - schema.dict = flatten_dict(temp_dict) - error_rows = [] - for section_name, section_data in data.items(): - if section_name == "default": - continue - if section_data is None: - error_rows.append([ - color_env(file_path.split('-')[-1].split('.')[0]), # Environment - section_name, - "", - f"Section data is empty for {file_path} against schema {schema_file} for section {section_name}" - ]) - continue - flattened_data = flatten_dict(normalise_keys(section_data)) - result = schema.validate(flattened_data, section_name, strict=True) - if not result.isValid(): - for err in result.errors: - # Try to extract the key and error message - # Example error: "spring.cloud.zookeeper.config.enabled: Unexpected element" - if ':' in err: - key, issue = err.split(':', 1) - else: - key, issue = "", err - error_rows.append([ - color_env(file_path.split('-')[-1].split('.')[0]), # Environment - section_name, - key.strip(), - issue.strip() - ]) - final_result = False - - if error_rows: - print("\n" + tabulate( - error_rows, - headers=["Environment", "Deployable Name", "Key", "Error/Issue"], - tablefmt="fancy_grid" - ) + "\n", flush=True) - return final_result - except yamale.YamaleError as e: - print("YAML validation failed:", flush=True) - print(e.message, flush=True) - return False - - -def validate_no_cross_environment_endpoint(file_path: str, env: str) -> bool: - """ - Function to validate that the config file does not contain cross-environment references. - :param file_path: Path to the config file. - :param env: Environment name (e.g., 'prd', 'int', 'stg'). - :return: True if no cross-environment references are found, False otherwise. - """ - status = True - error_rows = [] - - with open(file_path, 'r') as file: - try: - data = list(yaml.safe_load_all(file)) # Use safe_load_all for multiple documents - - for section_content in data: - flattened_data = flatten_dict(section_content) - for key, value in flattened_data.items(): - # Check if any invalid patterns are found in the value for the environment - invalid_patterns_for_env = invalid_patterns.get(env, []) - if isinstance(value, str) and any( - pattern in value for pattern in invalid_patterns_for_env) and not is_db_url(key, value): - error_rows.append([ - color_env(env), - key, - value, - "Invalid cross-environment value" - ]) - status = False - - # Check for specific zookeeper connect string patterns - if 'zookeeper.connect-string' in key or 'zookeeper~connect-string' in key or 'zookeeper.server' in key or 'zookeeper~server' in key: - elements = value.split(',') - valid_patterns = valid_zk_patterns.get(env, []) - for element in elements: - if not any(pattern in element for pattern in valid_patterns): - error_rows.append([ - color_env(env), - key, - element, - "Invalid zookeeper connect string" - ]) - status = False - - if error_rows: - print("\n" + tabulate( - error_rows, - headers=["Environment", "Key", "Value", "Error/Issue"], - tablefmt="fancy_grid" - ) + "\n", flush=True) - return status - except yaml.YAMLError as exc: - print(f"Error reading {file_path}: {exc}", flush=True) - return False - - -def process_config_files_of_module(configs_directory_path: str): - """ - Function to process the config files of a module - :param configs_directory_path: path where for the given modules files are residing - :return: True if all check passed else false - """ - is_invalid = False - validation_status = True - module_name = os.path.basename(configs_directory_path) - for config_file in config_files_props: - config_file_path = os.path.join(configs_directory_path, config_file['name']) - if not os.path.exists(config_file_path) and '-dev' in config_file['name']: - continue - if not os.path.exists(config_file_path): - canSkip = False - if '-int' in config_file['name']: - for whitelists in whitelists_for_preprod: - if whitelists in module_name: - canSkip = True - break - if canSkip: - continue - print(f"Validation failed for {config_file_path}", flush=True) - print(f"File {config_file_path} does not exist.", flush=True) - validation_status = False - continue - if check_for_secretes(config_file_path): - validation_status = False - - if config_file['type'] == 'static': - if not validate_config_file_against_schema(config_file_path, - config_file['schema-file-name'], - configs_directory_path): - is_invalid = True - if not validate_no_cross_environment_endpoint(config_file_path, config_file['env']): - is_invalid = True - else: - if not validate_config_file_against_schema(config_file_path, - config_file['schema-file-name'], - configs_directory_path, - False): - is_invalid = True - if is_invalid: - validation_status = False - return validation_status - - -def check_for_secretes(file_path: str): - """ - Function to check for secretes in the file - :param file_path: config file_path - :return: error_message and status of the check - """ - - if SecretHelper.is_secret_present(file_path, True): - print("\033[91mFound secrets in the config file.\033[0m", flush=True) - return True - return False - - -def find_non_matching_dicts(dict_list, dict_a): - """ - Find dictionaries in a list where specific keys do not match the given pattern. - - :param dict_list: List of tuples (dict, str). - :param dict_a: Dictionary to flatten and search for URL values. - :return: True if all keys match the pattern in the given dictionaries, False otherwise. - """ - # Step 1: Flatten dict A - flattened_dict_a = flatten_dict(dict_a) - - # Step 2: Identify keys with URL values - url_keys = [key for key, value in flattened_dict_a.items() if is_url(value)] - - # Step 3: Filter dicts in the list where keys do not match the given pattern - all_keys_match = True - for key in url_keys: - key_match_found = False - for d, s in dict_list: - if key in d and matches_pattern(d[key]): - key_match_found = True - if not key_match_found: - print(f"Key is not in proper format, name is : {key}", flush=True) - all_keys_match = False - - return all_keys_match - - -def flatten_dict(d, parent_key='', sep='.'): - """ - Flatten a nested dictionary. - - :param d: Dictionary to flatten. - :param parent_key: String to use as a prefix for keys. - :param sep: Separator for nested keys. - :return: Flattened dictionary. - """ - items = [] - for k, v in d.items(): - new_key = f'{parent_key}{sep}{k}' if parent_key else k - if isinstance(v, MutableMapping): - items.extend(flatten_dict(v, new_key, sep=sep).items()) - else: - items.append((new_key, v)) - return dict(items) - - -def matches_pattern(value): - """ - Check if a value matches the pattern '{{stateless_service..}}'. - - :param value: Value to check. - :return: True if the value matches the pattern, False otherwise. - """ - pattern = re.compile(r'\{\{(?:stateless_service|stateful_service)\.[^.]+\.[^}]+\}\}') - return bool(pattern.match(value)) - - -def clean_up(data, keys): - """ - Delete the specified nested key and clean up empty parent keys. - - :param data: The dictionary to clean up. - :param keys: A list of keys specifying the path to the nested key. - """ - if not keys: - return - - current_key = keys[0] - if len(keys) == 1: - data.pop(current_key, None) - else: - next_level = data.get(current_key, None) - if next_level is not None and isinstance(next_level, dict): - clean_up(next_level, keys[1:]) - if not next_level: - data.pop(current_key, None) - - -def deep_validate(): - configs_path = os.path.join(os.getenv("REPO_ROOT", ""), "configs") - status = True - if not os.path.exists(configs_path): - return True - for root, dirs, files in os.walk(configs_path): - for dir_name in dirs: - if not process_config_files_of_module(os.path.join(root, dir_name)): - status = False - return status - - -def color_env(env): - color_map = { - "dev": "\033[94m", # Blue - "stg": "\033[93m", # Yellow - "int": "\033[92m", # Green - "prd": "\033[91m", # Red - } - reset = "\033[0m" - return f"{color_map.get(env, '')}{env}{reset}" - - -if __name__ == "__main__": - if deep_validate(): - print("Validation successful.", flush=True) - os._exit(0) - else: - print("Validation failed.", flush=True) - os._exit(1) diff --git a/resources/com/homelab/values.yaml b/resources/com/homelab/values.yaml deleted file mode 100644 index 3f5f5f8..0000000 --- a/resources/com/homelab/values.yaml +++ /dev/null @@ -1,202 +0,0 @@ -<% if (deploymentStrategy == 'canary'){ print "canary:\n enabled: true\n slackChannel: ${canary.slackChannel}\n enableManualPromotion: ${enableManualPromotion}\n skipAnalysis: ${canary.skipAnalysis}\n service:\n port: 80\n targetPort: ${primary_port}\n" ; print " progressDeadlineSeconds: ${canary.progressDeadlineSeconds}\n minCanaryReplicas: ${minCanaryReplicas}\n maxCanaryReplicas: ${maxCanaryReplicas}\n analysisInterval: ${canary.analysisInterval}\n analysisThreshold: ${canary.analysisThreshold}\n analysisMaxWeight: ${canary.analysisMaxWeight}\n analysisStepWeight: ${canary.analysisStepWeight}\n analysisMetrics:\n thresholdRangeMin: ${canary.analysisMetrics.thresholdRangeMin}\n interval: ${canary.analysisMetrics.interval}\n" } else { print "canary:\n enabled: false\n" } %>repoName: ${repo_name} -cron: - enabled: false - serviceAccount: - enabled: false -applicationName: ${app_name} -autoscaling: - enabled: ${as_enabled} - maxReplicas: ${as_max} - minReplicas: ${as_min} - pollingInterval: ${as_poll} - scaledown: - policies: - - periodseconds: ${as_down_period} - type: Pods - value: ${as_down_pod_count} - selectpolicy: Min - stabilizationWindowSeconds: ${as_down_stable_window} - scaleup: - policies: - - periodseconds: ${as_up_period} - type: Pods - value: ${as_up_pod_count} - - periodseconds: ${as_up_period} - type: Percent - value: ${as_up_pod_percentage} - selectpolicy: Max - stabilizationWindowSeconds: ${as_up_stable_window} -<% if(!triggers){print " triggers:\n - metadata:\n value: \"${as_trigger_value}\"\n metricType: ${as_trigger_type}\n type: ${as_trigger_metric}"} else {println ' triggers:'; for(val in triggers){if(val instanceof Map){ val.each{k,v -> if (v instanceof Map) { println ' - '+k+':'; v.each{a,b -> if(b.isNumber()){println " ${a}: \"${b}\"" } else {println " ${a}: ${b}" }}} else {println" ${k}: ${v}"}}}}} %> -replicaCount: ${replica_count} -otel_enabled: ${otel_enabled} -metrics_mode: ${metrics_mode} -deployment: - affinity: {} - podDistributionSkew: ${podDistributionSkew} - args: -<% for (arg in deployment_args){ if (arg.contains("Xms") || arg.contains("Xmx")) {println ''} else {println ' - '+arg}} %> - <% if (appConfigEnabled) { %>- -Dspring.profiles.active=${environment},${app_name},dyn-${environment} <% } %> - <% if (appConfigEnabled) { %>- -Dspring.config.additional-location=/opt/config/application-${environment}.yml,/opt/config/application-dyn-${environment}.yml<% } %> - - -Xms${xms} - - -Xmx${xmx} - - -XX:ActiveProcessorCount=${activeProcessorCount} - - -javaagent:/opt/jmx_exporter.jar=8880:/jmx/jmx-config.yaml - <% if (telegraf_metrics) { %>- -Dtelegraf-metrics-enabled=true<% } else { %>- -Dtelegraf-metrics-enabled=false<% } %> - <% if (otel_enabled) { %> - - -javaagent:/opt/opentelemetry-javaagent.jar - - -Dotel.exporter.otlp.protocol=grpc - - -Dotel.logs.exporter=none - - -Dotel.metrics.exporter=none - - -Dotel.traces.exporter=otlp - - -Dotel.traces.sampler=traceidratio - <% if (environment != "stg") { %> - - -Dotel.traces.sampler.arg=${otel_traces_sampler_arg} - <% } %> - - -Dotel.resource.attributes=service.name=${app_name},application.name=${app_name},api.name=${app_name},cx.application.name=${app_name},cx.subsystem.name=${app_name} - <% } %> - - -jar - - <% print module=='module_less'?repo_name:module %>.jar - command: - - java - enabled: true - env: - - name: PRISMSDK_ENVIRONMENT - value: ${prismsdk_environment} - envFrom: - secretRef: ${app_name} - image: - pullPolicy: IfNotPresent - pullSecret: "" - repository: ${registry}/${environment}/${build_team}/<% print module=='module_less'?repo_name.toLowerCase():repo_name.toLowerCase()+'/'+module %> - tag: ${tag} -<% if(lifecycle) { println ' lifecycle:\n preStop:\n exec:\n command:'; for(val in lifecycle.preStop.exec.command){ println " - $val" }} else {print " lifecycle:\n preStop:\n exec:\n command:\n - /bin/bash\n - -c\n - kill -SIGQUIT 1 ; /bin/sleep 120\n "} %> - minReadySeconds: 10 - podAnnotations: - jmx.io/path: /metrics - jmx.io/port: "8880" - jmx.io/scrape: "true" - <% if (nodeSelector.contains("arm64") && (environment=="int" || environment=="prd")) { print 'telegraf.influxdata.com/image: 847438129436.dkr.ecr.ap-southeast-1.amazonaws.com/telegraf:1.24.4-arm64' } %> - <% if (appMetrics) { print 'prometheus.io/path: /actuator/prometheus' %> - <% print 'prometheus.io/port: "'+app_port+'"' %> - <% print 'prometheus.io/scrape: "true"'} %> -<% if (pod_annotations) {pod_annotations.each{k,v -> if(v instanceof String) { println " ${k}: '${v}'"} else { println " ${k}: ${v}" }};} else {print ''} %> - ports: - - containerPort: ${app_port} - name: http - protocol: TCP - - containerPort: 8880 - name: metric - protocol: TCP - probes: - liveness: - failureThreshold: ${liveness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${liveness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${liveness_success_threshold} - timeoutSeconds: ${liveness_timeout_seconds} - readiness: - failureThreshold: ${readiness_failure_threshold} - initialDelaySeconds: ${initialDelaySeconds} - path: ${health_check} - periodSeconds: ${readiness_period_seconds} - port: http - scheme: HTTP - successThreshold: ${readiness_success_threshold} - timeoutSeconds: ${readiness_timeout_seconds} - resources: - limits: - cpu: ${cpu_limit} - memory: ${memory_limit}i - requests: - cpu: ${cpu_request} - memory: ${memory_request}i - revisionHistoryLimit: 6 -<% if(serviceAccount){println ' serviceAccount:\n annotations:';serviceAccount.annotations.each{k,v -> println " ${k}: ${v}"};println " enabled: ${serviceAccount.enabled}"} else {print ' serviceAccount:\n annotations: null\n enabled: false'} %> - nodeSelector: - ${nodeSelector}: ${nodeSelectorValue} -<% if (hostAliases){println ' hostAliases:';for(arr in hostAliases){println ' - ip: '+arr.get("ip"); println ' hostnames:'; for (ele in arr.get("hostnames")) { println ' - '+ele } }} %> - tolerations: - - effect: NoSchedule - key: ${nodeSelector} - operator: Equal - value: ${nodeSelectorValue} - updateStrategy: - strategy: -<% if (deploymentStrategy == 'recreate') { print " type: Recreate"} else { print " type: RollingUpdate\n rollingUpdate:\n maxUnavailable: 0%\n maxSurge: ${maxSurge}%" } %> -externalSecret: - annotations: -<% if (external_secrets_annotations) {external_secrets_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true - path: homelab/${vault_env}/${bu}/${team}/${app_name} - version: ${tag} - name: ${env_ns}-${app_name} - target: ${app_name} -fullnameOverride: "" -ingress: - annotations: - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/ssl-redirect: "false" -<% if (ingress_annotations) {ingress_annotations.each{k,v -> println " ${k}: ${v}"};} else {print ''} %> - enabled: true -<% if(grpc_host){println " grpc_hosts:\n - host: ${grpc_host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else if (grpc_hosts) {println " grpc_hosts:";for(host_arr in grpc_hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> -<% if(host){println " hosts:\n - host: ${host}\n paths:\n - pathType: ImplementationSpecific\n path: /"} else {println " hosts:";for(host_arr in hosts){println " - host: ${host_arr.host}\n paths:";for(path_arr in host_arr.paths){println " - pathType: ${path_arr.pathType}\n path: ${path_arr.path}"; if(path_arr.targetService) { println " targetService: ${path_arr.targetService}" } }}} %> ingressClassName: ${ingress_class} - servicePort: http - enableWebsocket: ${enableWebsocket} - slowStart: - enabled: <% if (slowStartWindow) { print "true" } else { print "false" } %> - window: <% if (slowStartWindow) { print "${slowStartWindow}" } else { print "120s" } %> - aggression: <% if (slowStartAggression) { print "${slowStartAggression}" } else { print "1.0" } %> - minPercent: <% if (slowStartMinPercent) { print "${slowStartMinPercent}" } else { print "10" } %> -jmxconfig: - enabled: true -labels: - priority: <% print priority?:'p1' %> - priority_v2: <% print priority_v2?:'cp3' %> - primary_owner: ${primary_owner} - secondary_owner: ${secondary_owner} - env: ${environment_norm} - team: ${team_norm} - bu: ${bu_norm} - <% if (service_type_norm) { println "service_type: ${service_type_norm}" } %> - commit_id: ${commit_id} -nameOverride: "" -namespace: ${env_ns}-${app_name} -podDisruptionBudget: - enabled: <% if (pdbMaxUnavailable) { print "true" } else { print "false" } %> - maxUnavailable: ${pdbMaxUnavailable} - minAvailable: ${pdbMinAvailable} -podSecurityContext: - fsGroup: 65534 - runAsGroup: 65534 - runAsUser: 65534 -service: -<% if (service_annotations) { println " annotations:"; service_annotations.each{k,v -> println " ${k}: \"${v}\""};} else { println " annotations: null" } %> - enabled: true -<% if (addon_ports) { for (p in addon_ports) { println " addons:"; println " - name: ${p.name}"; println " targetPort: ${p.targetPort}"; println " type: ${p.type}"; }} else { println " addon_ports: []" } %> - ports: - - name: http - port: 80 - protocol: TCP - targetPort: ${primary_port} - type: ClusterIP - -createContourGateway: <% if (createContourGateway) { print "${createContourGateway}" } else { print "false" } %> -contourResponseTimeout: ${contourResponseTimeout} - - -appConfig: - enabled: <% if(appConfigEnabled) { print "true" } else { print "false"} %> - env: ${environment} -<% if(appConfigEnabled) {%> - staticAppConfig: - data: | -${staticAppConfigData.trim().replaceAll("(?m)^", " ")} - dynamicAppConfig: - data: | -${dynamicAppConfigData.trim().replaceAll("(?m)^", " ")} -<% } -%> - diff --git a/resources/com/homelab/values_properties.yaml b/resources/com/homelab/values_properties.yaml deleted file mode 100644 index ede22c1..0000000 --- a/resources/com/homelab/values_properties.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- - -as_poll: ${as_poll} -as_down_period: ${as_down_period} -as_up_period: ${as_up_period} -as_up_stable_window: ${as_up_stable_window} -ingress_class: ${ingress_class} -host: ${host} -hostAliases: false -nodeSelector: <% if(arch == 'arm64'){print environment=='dev' || environment=='ftr' ? bu+'-'+arch: environment == 'int' ? team+"-"+arch:nodeSelector } else { print environment == 'dev' || environment == 'ftr' ? bu: environment == 'int' ? bu+"-int" : nodeSelector} %> -triggers: - - metadata: - value: "${as_trigger_value}" - metricType: ${as_trigger_type} - type: ${as_trigger_metric} -canary: - progressDeadlineSeconds: 300 - analysisInterval: 120s - analysisThreshold: 5 - analysisMaxWeight: 5 - analysisStepWeight: 5 - analysisMetrics: - thresholdRangeMin: 0.99 - interval: 1m - skipAnalysis: false \ No newline at end of file diff --git a/resources/org/homelab/dev-pod.yaml b/resources/org/homelab/dev-pod.yaml deleted file mode 100644 index 6be3ae9..0000000 --- a/resources/org/homelab/dev-pod.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - bu: "infra" - team: "devops" - service: "jenkins-dev" - env: "dev" - priority: "p0" - type: "jenkins" - component: "jenkins-agent" -spec: - serviceAccountName: jenkins-dev-agent - containers: - - name: devops-tools - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/build-tools:lunar-v2.0.22 - imagePullPolicy: Always - resources: - requests: - memory: "6G" - cpu: "2" - limits: - memory: "1000G" - cpu: "100" - volumeMounts: - - mountPath: "/root" - name: "cache" - readOnly: false - env: - - name: TZ - value: Asia/Kolkata - - name: DOCKER_HOST - value: dind-dev-svc - command: - - cat - tty: true - nodeSelector: - dedicated: jenkins - tolerations: - - key: "dedicated" - operator: "Equal" - value: "jenkins" - effect: "NoSchedule" - volumes: - - name: cache - persistentVolumeClaim: - claimName: pvc-jenkins-agents-cache-dev diff --git a/resources/org/homelab/prd-pod.yaml b/resources/org/homelab/prd-pod.yaml deleted file mode 100644 index ada4c19..0000000 --- a/resources/org/homelab/prd-pod.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - bu: "infra" - team: "devops" - service: "jenkins-prd" - env: "prd" - priority: "p0" - type: "jenkins" - component: "jenkins-agent" -spec: - serviceAccountName: jenkins-prd-agent - containers: - - name: devops-tools - - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/build-tools:lunar-v2.0.22 - - imagePullPolicy: Always - resources: - requests: - memory: "6G" - cpu: "2" - limits: - memory: "1000G" - cpu: "100" - volumeMounts: - - mountPath: "/root" - name: "cache" - readOnly: false - env: - - name: TZ - value: Asia/Kolkata - - name: DOCKER_HOST - value: dind-prd-svc - command: - - cat - tty: true - nodeSelector: - dedicated: jenkins - tolerations: - - key: "dedicated" - operator: "Equal" - value: "jenkins" - effect: "NoSchedule" - volumes: - - name: cache - persistentVolumeClaim: - claimName: pvc-jenkins-agents-cache-prd-new diff --git a/resources/org/homelab/prd-sidecar-pod.yaml b/resources/org/homelab/prd-sidecar-pod.yaml deleted file mode 100644 index 5919a15..0000000 --- a/resources/org/homelab/prd-sidecar-pod.yaml +++ /dev/null @@ -1,68 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - bu: "infra" - team: "devops" - service: "jenkins-prd" - env: "prd" - priority: "p0" - type: "jenkins" - component: "jenkins-agent" -spec: - serviceAccountName: jenkins-prd-agent - containers: - - name: devops-tools - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/build-tools:lunar-v2.0.21 - imagePullPolicy: Always - resources: - requests: - memory: "6G" - cpu: "2" - limits: - memory: "1000G" - cpu: "100" - volumeMounts: - - mountPath: "/root" - name: "cache" - readOnly: false - env: - - name: TZ - value: Asia/Kolkata - - name: DOCKER_HOST - value: dind-prd-svc - command: - - cat - tty: true - - - name: dind - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/docker:28-dind - securityContext: - privileged: true - env: - - name: DOCKER_TLS_CERTDIR - value: "" - resources: - requests: - cpu: "1" - memory: "2Gi" - limits: - cpu: "1" - memory: "2Gi" - volumeMounts: - - name: dind-storage - mountPath: /var/lib/docker - nodeSelector: - dedicated: jenkins - tolerations: - - key: "dedicated" - operator: "Equal" - value: "jenkins" - effect: "NoSchedule" - volumes: - - name: cache - persistentVolumeClaim: - claimName: pvc-jenkins-agents-cache-prd-new - - name: dind-storage - emptyDir: {} - diff --git a/resources/org/homelab/stg-pod.yaml b/resources/org/homelab/stg-pod.yaml deleted file mode 100644 index 8732355..0000000 --- a/resources/org/homelab/stg-pod.yaml +++ /dev/null @@ -1,48 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - bu: "infra" - team: "devops" - service: "jenkins-stg" - env: "stg" - priority: "p0" - type: "jenkins" - component: "jenkins-agent" -spec: - serviceAccountName: jenkins-dev-agent - containers: - - name: devops-tools - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/build-tools:lunar-v2.0.22 - imagePullPolicy: Always - resources: - requests: - memory: "6G" - cpu: "2" - limits: - memory: "1000G" - cpu: "100" - volumeMounts: - - mountPath: "/root" - name: "cache" - readOnly: false - env: - - name: TZ - value: Asia/Kolkata - - name: DOCKER_HOST - value: dind-dev-new-svc.jenkins-new.svc.cluster.local - command: - - cat - tty: true - - nodeSelector: - dedicated: jenkins - tolerations: - - key: "dedicated" - operator: "Equal" - value: "jenkins" - effect: "NoSchedule" - volumes: - - name: cache - persistentVolumeClaim: - claimName: pvc-jenkins-agents-cache-dev diff --git a/resources/org/homelab/stg-sidecar-pod.yaml b/resources/org/homelab/stg-sidecar-pod.yaml deleted file mode 100644 index e42cb8a..0000000 --- a/resources/org/homelab/stg-sidecar-pod.yaml +++ /dev/null @@ -1,67 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - bu: "infra" - team: "devops" - service: "jenkins-stg" - env: "stg" - priority: "p0" - type: "jenkins" - component: "jenkins-agent" -spec: - serviceAccountName: jenkins-dev-agent - containers: - - name: devops-tools - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/build-tools:lunar-v2.0.21 - imagePullPolicy: Always - resources: - requests: - memory: "6G" - cpu: "2" - limits: - memory: "1000G" - cpu: "100" - volumeMounts: - - mountPath: "/root" - name: "cache" - readOnly: false - env: - - name: TZ - value: Asia/Kolkata - - name: DOCKER_HOST - value: dind-dev-new-svc.jenkins-new.svc.cluster.local - command: - - cat - tty: true - - - name: dind - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/docker:28-dind - securityContext: - privileged: true - env: - - name: DOCKER_TLS_CERTDIR - value: "" # Disable TLS so we can connect via HTTP on port 2375 - resources: - requests: - cpu: "1" - memory: "2Gi" - limits: - cpu: "1" - memory: "2Gi" - volumeMounts: - - name: dind-storage - mountPath: /var/lib/docker - nodeSelector: - dedicated: jenkins - tolerations: - - key: "dedicated" - operator: "Equal" - value: "jenkins" - effect: "NoSchedule" - volumes: - - name: cache - persistentVolumeClaim: - claimName: pvc-jenkins-agents-cache-dev - - name: dind-storage - emptyDir: {} diff --git a/resources/org/homelab/templates/maven-3.3-jdk-8.sh b/resources/org/homelab/templates/maven-3.3-jdk-8.sh deleted file mode 100644 index d211be6..0000000 --- a/resources/org/homelab/templates/maven-3.3-jdk-8.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -prepareDockerfileWithBuild(){ -cat << EOF > Dockerfile-${artifactId} -# This sample, non-production-ready template describes an Amazon EC2 instance and an Elastic Load Balancer. -# © 2020 Amazon Web Services, Inc. or its affiliates. All Rights Reserved. -# This AWS Content is provided subject to the terms of the AWS Customer Agreement available at -# http://aws.amazon.com/agreement or other written agreement between Customer and either -# Amazon Web Services, Inc. or Amazon Web Services EMEA SARL or both. -FROM 766380763301.dkr.ecr.ap-south-1.amazonaws.com/build/maven:3.3-jdk-8 as BUILD -WORKDIR /usr/src/app -COPY . /usr/src/app -RUN mvn -s sandbox-settings.xml clean install -DskipTests -RUN mkdir -p /var/log/${artifactId} && touch /var/log/${artifactId}/gc.log -FROM 766380763301.dkr.ecr.ap-south-1.amazonaws.com/build/java:8-jdk-slim-secure_v1.0 -ADD https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.15.0/jmx_prometheus_javaagent-0.15.0.jar /opt/jmx_exporter.jar -### Config added through configmap -# COPY config.yaml /opt/config.yaml -EXPOSE 8880 8010 -COPY --from=BUILD /usr/src/app/target/${artifactId}-${version}.jar /opt/target/${artifactId}.jar -COPY --from=BUILD /var/log/${artifactId} /var/log/${artifactId} -WORKDIR /opt/target -CMD ["${artifactId}.jar", "-javaagent:/opt/jmx_exporter.jar=8880:/opt/config/jmx-config.yaml", \ - "-XX:MinRAMPercentage=50.0", "-XX:MaxRAMPercentage=80.0", \ - "-XX:+UseParallelGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails", \ - "-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime", "-XX:+PrintHeapAtGC", \ - "-Xloggc:/var/log/${artifactId}/gc.log", \ - "-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=9000k", \ - "-Xms2G", "-Xmx2G"] -EOF - -} - -prepareDockerfileNoBuild(){ -cat << EOF > Dockerfile-${artifactId} -# This sample, non-production-ready template describes an Amazon EC2 instance and an Elastic Load Balancer. -# © 2020 Amazon Web Services, Inc. or its affiliates. All Rights Reserved. -# This AWS Content is provided subject to the terms of the AWS Customer Agreement available at -# http://aws.amazon.com/agreement or other written agreement between Customer and either -# Amazon Web Services, Inc. or Amazon Web Services EMEA SARL or both. -FROM 766380763301.dkr.ecr.ap-south-1.amazonaws.com/build/maven:3.3-jdk-8 as BUILD -#FROM asia-southeast1-docker.pkg.dev/supply-poc-351106/homelab-devops/maven:3.3-jdk-8 as BUILD -RUN mkdir -p /var/log/${artifactId} && touch /var/log/${artifactId}/gc.log -FROM 766380763301.dkr.ecr.ap-south-1.amazonaws.com/build/java:8-jdk-slim-secure_v1.0 -#FROM asia-southeast1-docker.pkg.dev/supply-poc-351106/homelab-devops/java:8 -ADD https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.15.0/jmx_prometheus_javaagent-0.15.0.jar /opt/jmx_exporter.jar -### Config added through configmap -# COPY config.yaml /opt/config.yaml -EXPOSE 8880 8010 -COPY ${artifactId}/target/*.jar /opt/target/${artifactId}.jar -COPY --from=BUILD /var/log/${artifactId} /var/log/${artifactId} -WORKDIR /opt/target -CMD ["${artifactId}.jar", "-javaagent:/opt/jmx_exporter.jar=8880:/opt/config/jmx-config.yaml", \ - "-XX:MinRAMPercentage=50.0", "-XX:MaxRAMPercentage=80.0", \ - "-XX:+UseParallelGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails", \ - "-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime", "-XX:+PrintHeapAtGC", \ - "-Xloggc:/var/log/${artifactId}/gc.log", \ - "-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=9000k", \ - "-Xms2G", "-Xmx2G"] -EOF - -} - -main(){ - if [[ $# == 4 ]];then - export artifactId=$1 - export version=$2 - export doBuild=$3 - export repoName=$4 - if [[ $doBuild == "YES" ]];then - rm -f Dockerfile-${artifactId} - prepareDockerfileWithBuild - else - rm -f Dockerfile-${artifactId} - prepareDockerfileNoBuild - fi - ls -latr - cat Dockerfile-${artifactId} - else - echo "Please provide required parameters" - exit 1 - fi -} - -main $@ \ No newline at end of file diff --git a/resources/org/homelab/templates/node-12.22.sh b/resources/org/homelab/templates/node-12.22.sh deleted file mode 100644 index 5b4499e..0000000 --- a/resources/org/homelab/templates/node-12.22.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -prepareDockerfile(){ -cat << EOF > Dockerfile-${artifactId} - -ARG ACCOUNT_ID=766380763301 - -FROM \${ACCOUNT_ID}.dkr.ecr.ap-southeast-1.amazonaws.com/build/node:12.22.1-slim as build-env - -WORKDIR /usr/src/app - -COPY package*.json ./ - -RUN npm install - -FROM \${ACCOUNT_ID}.dkr.ecr.ap-southeast-1.amazonaws.com/build/node:12.22.1-alpine - -# for health checks -# RUN apk add --update --no-cache curl=7.74.0-r1 - -USER node:1000 - -COPY --chown=node:1000 --from=build-env /usr/src/app /app - -WORKDIR /app - -EXPOSE 2020 - -COPY --chown=node:1000 . . - -CMD ["npm", "start"] - -EOF - -} - -main(){ - if [[ $# == 1 ]];then - export artifactId=$1 - rm -f Dockerfile-${artifactId} - prepareDockerfile - ls -latr - cat Dockerfile-${artifactId} - else - echo "Please provide required parameters" - exit 1 - fi -} - -main $@ - diff --git a/resources/org/homelab/toolchain-pod.yaml b/resources/org/homelab/toolchain-pod.yaml deleted file mode 100644 index d32c114..0000000 --- a/resources/org/homelab/toolchain-pod.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - bu: "infra" - team: "toolchain" - service: "jenkins-toolchain" - env: "stg" - priority: "p0" - type: "jenkins" - component: "jenkins-agent" -spec: - serviceAccountName: jenkins-toolchain-dev-agent - containers: - - name: devops-tools - image: asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/build-tools:lunar-v2.0.22 - imagePullPolicy: Always - resources: - requests: - memory: "6G" - cpu: "2" - limits: - memory: "1000G" - cpu: "100" - volumeMounts: - - mountPath: "/root" - name: "cache" - readOnly: false - env: - - name: TZ - value: Asia/Kolkata - - name: DOCKER_HOST - value: toolchain-dind-dev-svc.jenkins-toolchain.svc.cluster.local - command: - - cat - tty: true - nodeSelector: - dedicated: toolchain-jenkins - tolerations: - - key: "dedicated" - operator: "Equal" - value: "toolchain-jenkins" - effect: "NoSchedule" - volumes: - - name: cache - persistentVolumeClaim: - claimName: pvc-toolchain-jenkins-agents-cache-dev diff --git a/src/com/homelab/stages/automationTest.groovy b/src/com/homelab/stages/automationTest.groovy deleted file mode 100644 index fc2eb40..0000000 --- a/src/com/homelab/stages/automationTest.groovy +++ /dev/null @@ -1,68 +0,0 @@ -package com.homelab.stages - -import com.homelab.stages.deployJar - - -def run(Map config){ - // get required variables from config - def automation_repo_name = config.run_automation.repo_name - def branch = config.run_automation.branch - def deployObj = new deployJar() - - //deploy service(farmiso) on specified machine - deployObj.run(config) - - - checkoutAutomationRepo(automation_repo_name, branch) - - runAutomationSuite(automation_repo_name) - -} - -def checkoutAutomationRepo(String automation_repo_name, String branch){ - try{ - stage('Checkout automation repo'){ - sh "rm -rf ${automation_repo_name}; git clone git@github.com:Homelab/${automation_repo_name}.git -b ${branch}" - echo "automation repo cloned" - } - } - catch( Exception e) { - env.msg = "Error cloning automation repo. Please check console output for more details." - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -def runAutomationSuite(String automation_repo_name){ - try{ - stage('Run Automation Suite'){ - dir("$automation_repo_name"){ - run_cmd = "mvn test" //specific to Farmiso as of now. - sh "$run_cmd" - } - } - } - catch( Exception e) { - env.msg = "Error running automation suite. Please check console output for more details." - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - finally{ - //publish HTML test report - dir("$automation_repo_name"){ - archiveArtifacts artifacts: "test-output/*.*" - - - publishHTML (target: [ - allowMissing: false, - alwaysLinkToLastBuild: false, - keepAll: true, - reportDir: 'test-output', - reportFiles: 'index.html', - reportName: "Farmiso-Test-Automation-Report" - ]) - } - } -} diff --git a/src/com/homelab/stages/buildGo.groovy b/src/com/homelab/stages/buildGo.groovy deleted file mode 100644 index 14f5dff..0000000 --- a/src/com/homelab/stages/buildGo.groovy +++ /dev/null @@ -1,412 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams -import com.homelab.utilities.addSSHKey -import com.homelab.utilities.gitActions -import com.homelab.utilities.constructParam -import com.homelab.utilities.dockerUtilities - - -def buildDckr(Map config) { - env.GOPRIVATE = 'github.com/Homelab' - def buTeamMappingObj = new buTeamMapping() - def constructObj = new constructTemplate() - def dockerParamObj = new getDockerParams() - def addSshKeyObj = new addSSHKey() - def dockerUtilObj = new dockerUtilities() - - def team = buTeamMappingObj.get_team_initials(config.team) - def modules = config.modules ?: ['module_less'] - def repoName = config.repo_name - def dockerRepository = "${env.cicd_environment}/${team}/${repoName.toLowerCase()}" - def tag = dockerParamObj.getTag(repoName) - def dockerBindings = [:] - def version = config.dockerBuildVersion.split('-')[-1] - def constructParamObj = new constructParam() - def repoType = config.repo_type ?: 'microservice' - boolean skipSonarAndQualityGate = constructParamObj.skipSonarCheckForGo(config) - echo "skipSonarAndQualityGate: ${skipSonarAndQualityGate}" - String goVersion = config.goVersion ?: version - boolean shouldDeployArgo = (config.deployArgo ?: false).toBoolean() - boolean isConfigOnlyChange = false - - // Ensure goVersion is in x.x.x format - echo "goVersion: ${goVersion}" - if (goVersion.split('\\.').size() == 2) { - goVersion += '.0' - } - if (env.INFRA_ENV == 'toolchain') { - tag = dockerParamObj.getTag(repoName) - boolean allImagesExist = true - - for (module in modules) { - def moduleName = (module instanceof LinkedHashMap) ? module.keySet()[0] : module - def modulePath = (moduleName == 'module_less') ? dockerRepository : "${dockerRepository}/${moduleName}" - //TODO: building everything if one is missing -> create list of modules unbuild will only build those - if (!dockerUtilObj.imageExists(env.registry, modulePath, tag)) { - log.info("Toolchain: Image missing for ${moduleName} at ${modulePath}:${tag}. Proceeding with build.") - allImagesExist = false - break - } - } - if (allImagesExist) { - log.info("Toolchain: All images found in registry for tag ${tag}. Skipping build step.") - return [tag, shouldDeployArgo] - } - } - stage(stageName('Check Config Changes and Get Latest Image Tag')) { - (isConfigOnlyChange, shouldDeployArgo) = is_config_only_change_and_should_deploy_argo(repoName, shouldDeployArgo) - log.info("Is just application config change? $isConfigOnlyChange") - def param = new constructParam() - if (env.CHANGE_ID && repoType == 'microservice') { - def shouldValidateConfig = param.ValidateCacConfigForRepo(false, repoName) - if (shouldValidateConfig) { - log.info('************ Validate Config for CAC application.yml files ************') - dir("$repoName") { - writeFile file: 'validate_configs.py', text: libraryResource('com/homelab/validate_configs_v2.py') - sh 'python3 validate_configs.py' - } - } else { - log.info('************ Skipping Validation of CAC Config ************') - } - } else { - log.info("Skipping Validation of CAC Config for ${repoType} repo type") - log.info('************ Skipping Validation of CAC Config ************') - } - - if (isConfigOnlyChange) { - log.info('Skipping the build, since it is just application config change') - log.info('Getting latest image tag from GAR') - def firstModule = modules[0] - def firstModuleName = (firstModule instanceof LinkedHashMap) ? firstModule.keySet()[0] : firstModule - def moduleDockerRepository = (firstModuleName == 'module_less') ? dockerRepository : "${dockerRepository}/${firstModuleName}" - try { - def latest_image = sh( - script: "gcloud container images list-tags ${env.registry}/${moduleDockerRepository} --format='value(tags)' | sed '/^\$/d' | awk -F'-' '{print \$NF}' | sort | tail -1", - returnStdout: true - ).trim() - echo "latest_image: ${latest_image}" - if (latest_image == '') { - log.info("No existing images found for ${env.registry}/${moduleDockerRepository}. Using generated tag: ${tag}") - } else { - tag = sh( - script: "gcloud container images list-tags ${env.registry}/${moduleDockerRepository} --format='value(tags)' | tr ',' '\n' | grep ${latest_image}", - returnStdout: true - ).trim() - } - } catch (Exception e) { - env.msg = 'Error getting latest docker image from GAR' - env.error_msg_to_db = env.msg - log.error("${env.msg}\n${e.toString()}") - currentBuild.result = env.FAILURE - throw e - } - return [tag, shouldDeployArgo] - } - log.info('Proceeding with the build') - } - - stage(stageName('Scanning Sonar and Quality Gate')) { - dir(repoName) { - sonar_scan(repoName, skipSonarAndQualityGate, goVersion) - } - } - if (currentBuild.result == 'UNSTABLE') - { return [tag, false] } - - if (repoType != 'microservice') { - log.info("Skipping Docker build for ${repoType} repo type") - return [tag, shouldDeployArgo] - } - - stage(stageName('Building docker images')) { - try { - sh(script: 'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } catch (Exception e) { - env.msg = "[Failure] Can't login to gcloud docker registry." - env.error_msg_to_db = env.msg - log.error("${env.msg}. Error: ${e.toString()}") - currentBuild.result = env.FAILURE - throw e - } - dockerBindings['version'] = (version == 'go') ? '1.24.4' : version - dockerBindings['base_dir'] = config.base_dir ?: false - dockerBindings['build_registry'] = env.buildRegistry - dockerBindings['go_proxy'] = env.goProxyUrl - dockerBindings['repo_name'] = repoName - if (config.containsKey('copy_file')) { - String recursive = config.copy_file.recursive ? ' -r' : '' - dir(repoName) { - dir('copied_files') { - sh(script: "gsutil cp${recursive} ${config.copy_file.path} .") - } - } - dockerBindings['copy_file'] = true - dockerBindings['copy_target'] = config.copy_file.target ?: '/app/' - dockerBindings['base_dir'] = config.base_dir ?: false - } else { - dockerBindings['copy_file'] = false - } - - try { - dir(repoName) { - addSshKeyObj.create() - if (!fileExists('Dockerfile')) { - def moduleBuilds = [:] - for (m in modules) { - def moduleRef = m - def moduleName = (moduleRef instanceof LinkedHashMap) ? moduleRef.keySet()[0] : moduleRef - moduleBuilds["build-${moduleName}"] = { - def localBindings = dockerBindings.clone() - localBindings['module_property'] = (moduleRef instanceof LinkedHashMap) ? moduleRef[moduleName] : [:] - localBindings['kafka'] = (localBindings['module_property'].kafka) ? '-kafka' : '' - localBindings['module'] = moduleName - constructObj.renderTemplate(localBindings, 'go-Dockerfile', "Dockerfile-${moduleName}") - sh "cat Dockerfile-${moduleName}" - def moduleDockerRepository = (moduleRef == 'module_less') ? dockerRepository : "${dockerRepository}/${moduleName}" - sh(script: 'tar -cf only-mods.tar $(git ls-files "go.mod" "go.sum" "**/go.mod" "**/go.sum" 2>/dev/null || find . -name go.mod -o -name go.sum)') - sh(script: "set +x && docker build --tag ${env.registry}/${moduleDockerRepository}:${tag} -f Dockerfile-${moduleName} .") - if (env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') { - dockerUtilObj.retryDockerPush("docker push ${env.registry}/${moduleDockerRepository}:${tag}") - } else { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - } - sh(script: "rm -rf Dockerfile-${moduleName}") - } - } - parallel moduleBuilds - } else { - sh 'cat Dockerfile' - def moduleDockerRepository = dockerRepository // default for repo root - if (modules && modules[0] != 'module_less') { - def firstModule = modules[0] - def firstModuleName = (firstModule instanceof LinkedHashMap) ? firstModule.keySet()[0] : firstModule - moduleDockerRepository = dockerRepository + '/' + firstModuleName - } - def pushOrNot = (env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') ? '--push' : '' - if (!pushOrNot) { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - } - sh(script: "docker build --tag ${env.registry}/${moduleDockerRepository}:${tag} ${pushOrNot} .") - } - } - } catch (Exception e) { - env.msg = 'Error in building DockerFile Or Pushing To ECR' - env.error_msg_to_db = env.msg - log.error("${env.msg}. Error: ${e.toString()}") - currentBuild.result = env.FAILURE - throw e - } - return [tag, shouldDeployArgo] - } -} - -def is_config_only_change_and_should_deploy_argo(String repoName, boolean deployArgo) { - if (env.INFRA_ENV == 'toolchain') { - return [false, deployArgo] - } - def gitObj = new gitActions() - def configFiles = [] - boolean shouldDeployArgoResult = deployArgo - boolean isConfigOnlyChange = false - - def changedFiles = env.CHANGE_ID ? - gitObj.fetchDiffFilesForPullRequest(repoName, env.CHANGE_TARGET) : - gitObj.fetchDiffFilesForPushRequest(repoName) - - if (!changedFiles) { - return [isConfigOnlyChange, shouldDeployArgoResult] - } - - for (file in changedFiles.split('\n')) { - if (!file.startsWith('configs/')) { - isConfigOnlyChange = false - return [isConfigOnlyChange, shouldDeployArgoResult] - } - isConfigOnlyChange = true - configFiles << file - } - - if (configFiles && deployArgo && isConfigOnlyChange) { - def hasEnvironmentConfig = configFiles.any { it.contains(env.cicd_environment) } - if (!hasEnvironmentConfig) { - shouldDeployArgoResult = false - echo "Config changes don't contain environment: ${env.cicd_environment}" - } - } - return [isConfigOnlyChange, shouldDeployArgoResult] -} - -/** - * Function to execute sonar scan - * @param repoName: repository name - * @param skipSonarAndQualityGate: boolean parameter to skip sonar scan - * @param goVersion: Go version string - */ -def sonar_scan(String repoName, boolean skipSonarAndQualityGate, String goVersion) { - if (skipSonarAndQualityGate) { - log.info('Sonar scan is skipped. Marking this stage as passed.') - return - } - stage(stageName('Running sonar scan')) { - try { - withSonarQubeEnv(env.sonarEnv) { - echo "env.sonarEnv: ${env.sonarEnv}" - echo "env.BRANCH_NAME: ${env.BRANCH_NAME}" - def exclusions = "" - if (!fileExists('sonar-project.properties')) { - exclusions = " -Dsonar.exclusions=**/*_test.go,**/mock_*.go,**/mock.go,**/*.pb.go,**/*.proto,**/model.go" - }else{ - exclusions = " -Dproject.settings=`pwd`/sonar-project.properties" - } - - def scannerCommand = "sonar-scanner -Dsonar.projectKey=${repoName} -Dsonar.go.coverage.reportPaths=./cov.out -Dsonar.branch.name=${env.BRANCH_NAME} -Dsonar.ws.timeout=120 ${exclusions}" - echo "env.SONAR_HOST_URL: ${env.SONAR_HOST_URL}" - env.PATH = "/usr/local/sonar-scanner/sonar-scanner-5.0.1.3006-linux/bin:${env.PATH}" - def response = sh( - script: "curl -s -w '\n%{http_code}' -u ${env.SONAR_AUTH_TOKEN}: ${env.SONAR_HOST_URL}/api/navigation/component?component=${repoName}", - returnStdout: true - ).trim() - - def responseLines = response.split('\n') - def statusCode = responseLines[-1] - def responseBody = responseLines[0..-2].join('\n') - - log.info("SonarQube API Response Code: ${statusCode}") - log.info("SonarQube API Response Body: ${responseBody}") - - def projectExists = (statusCode == '200') - if (!projectExists && env.CHANGE_ID) { - log.info("Project ${repoName} does not exist. Creating it via a bootstrap scan on the target branch.") - - def targetBranch = env.CHANGE_TARGET ?: 'develop' - echo "targetBranch: ${targetBranch}" - sh "sonar-scanner \ - -Dsonar.projectKey=${repoName} \ - -Dsonar.projectName=${repoName} \ - -Dsonar.branch.name=${targetBranch} \ - -Dsonar.sources=. \ - -Dsonar.scm.disabled=true \ - -Dsonar.ws.timeout=120" - log.info("Bootstrap complete. Project created.") - }else{ - echo "Project ${repoName} already exists. Skipping bootstrap scan." - } - - if (env.CHANGE_ID) { - echo "env.CHANGE_ID: ${env.CHANGE_ID}" - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git fetch origin ${env.CHANGE_TARGET}:refs/remotes/origin/${env.CHANGE_TARGET}" - } - scannerCommand = "sonar-scanner -Dsonar.projectKey=${repoName} -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.repository=Homelab/${repoName} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET} -Dsonar.go.coverage.reportPaths=./cov.out -Dsonar.ws.timeout=120${exclusions}" - echo "scannerCommand: ${scannerCommand}" - } - downloadGoFromJFrog(goVersion) - sh(script: "GOPROXY=${env.goProxyUrl},direct && go mod tidy") - int testExitCode = sh(script: 'go test -short -coverprofile=./cov.out ./...', returnStatus: true) - if (testExitCode != 0) { - error('Go tests failed.') - } - echo "scannerCommand: ${scannerCommand}" - sh(script: scannerCommand) - } - } catch (Exception e) { - log.error("Error in running sonar scan: ${e}") - currentBuild.result = env.FAILURE - throw e - } - } - stage(stageName('Quality Gate')) { - try { - if (!env.CHANGE_ID || env.cicd_environment == 'int') { - log.info('Skipping quality gate check on Branches/Pre-Prod. Marking this stage as passed.') - } else { - timeout(time: 600, unit: 'SECONDS') { - def qg = waitForQualityGate() - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { - if (qg.status != 'OK') { - log.warning("Quality gate failed: ${qg.status}") - error "Stopping pipeline due to quality gate failure." - } - } - } - } - }catch (Exception e) { - echo "Error in quality gate: ${e.message}" - unstable('Quality Gate Failed !') - throw e - } - } - } - - -/** - * Function to download Go binary from JFrog - * @param goVersion: Go version string - */ -def downloadGoFromJFrog(String goVersion) { - try { - echo "env.cicd_environment: ${env.cicd_environment}" - - def jfrogUrl = (env.cicd_environment == 'prd' || env.cicd_environment == 'int') ? - 'https://jfrog-prd.homelabgcp.in' : - 'https://jfrog-dev.homelabgcp.in' - - def repo = 'devops-tools-local' - def goTarball = "go${goVersion}.linux-amd64.tar.gz" - def jfrogPath = "${jfrogUrl}/artifactory/${repo}/go/${goTarball}" - - def jfrogUser = '' - def jfrogPass = '' - def credentialId = (env.cicd_environment == 'prd' || env.cicd_environment == 'int') ? - 'jfrog-prd-credentials' : 'jfrog-stg-credentials' - echo "Using credential ID: ${credentialId}" - withCredentials([usernamePassword(credentialsId: credentialId, - usernameVariable: 'JFROG_USER', - passwordVariable: 'JFROG_PASS')]) { - jfrogUser = env.JFROG_USER - jfrogPass = env.JFROG_PASS - - log.info("Attempting to download Go ${goVersion} from JFrog: ${jfrogPath}") - - def downloadStatus = sh(script: """ - curl -u "${env.JFROG_USER}:${env.JFROG_PASS}" \ - -fLO "${jfrogPath}" \ - --fail --silent --show-error - """, returnStatus: true) - - if (downloadStatus == 0 && fileExists(goTarball)) { - log.info("Successfully downloaded Go ${goVersion} from JFrog") - } else { - echo("WARN: Go ${goVersion} not found in JFrog. Falling back to go.dev...") - sh(script: "curl -LO https://go.dev/dl/${goTarball}") - - log.info("Uploading downloaded Go ${goVersion} to JFrog for future use") - sh(script: """ - curl -u "${env.JFROG_USER}:${env.JFROG_PASS}" \ - -T "${goTarball}" \ - "${jfrogPath}" \ - --fail --silent --show-error || echo "Upload to JFrog failed, but continuing..." - """) - } - } - - sh(script: "tar -xvzf ${goTarball} -C /usr/local", returnStdout: true) - sh(script: "rm -Rf ${goTarball}") - env.PATH = "/usr/local/go/bin:${env.PATH}" - - // Verify installation - sh(script: "go version") - - } catch (Exception e) { - echo("ERROR: Failed to download Go from JFrog: ${e.message}") - - // Final fallback - sh(script: "curl -LO https://go.dev/dl/go${goVersion}.linux-amd64.tar.gz") - sh(script: "tar -xvzf go${goVersion}.linux-amd64.tar.gz -C /usr/local", returnStdout: true) - sh(script: "rm -Rf go${goVersion}.linux-amd64.tar.gz") - env.PATH = "/usr/local/go/bin:${env.PATH}" - } -} diff --git a/src/com/homelab/stages/buildGradle.groovy b/src/com/homelab/stages/buildGradle.groovy deleted file mode 100644 index 1916576..0000000 --- a/src/com/homelab/stages/buildGradle.groovy +++ /dev/null @@ -1,542 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams -import com.homelab.stages.checkOut - -/* -Function to define the flow of entire build, this function will call different stages related to maven build -*/ -def run(Map config) { - // get required variables from config - def checkoutObj = new checkOut() - def repo_name = config.repo_name - def args = config.build_args ?: '' - def skip_test = config.skip_test ?: false - def skip_sonar = config.skip_sonar ?: false - def push_to_jfrog = config.push_to_jfrog ?: false - def push_to_s3 = config.push_to_s3 ?: false - def branch_name = "${env.BRANCH_NAME}" - if (branch_name == 'gcp-main' || branch_name == 'gcp-master') { - push_to_jfrog = config.containsKey('push_to_jfrog') ? config.push_to_jfrog : false - } - def version = getVersion("${repo_name}") - - if (env.hot_fix) { - skip_test = true - skip_sonar = true - push_to_jfrog = false - } - - // call the stages - checkS3(repo_name, branch_name, version, push_to_s3) - build(repo_name, skip_test, args) - sonar_scan(repo_name, skip_sonar) - if (!env.CHANGE_ID) { - pushArtifactToJFrog(repo_name, push_to_jfrog) - pushArtifactToS3(repo_name, branch_name, push_to_s3) - } - else { - log.info('Artifact Push is disabled for Pull Requests') - } -} - -/* -Function to check if the artifact exist in s3 or not -input argument -*/ -def checkS3(String repo_name, String branch_name, String version, boolean push_to_s3) { - stage(stageName('Checking if artifact already exists')) { - if (env.CLOUD_PROVIDER == 'AWS') { - if (env.hot_fix) { - env.TAG = "v${version}-HOT" - } - else { - if (("${branch_name}" == 'master' || "${branch_name}" == 'main' || push_to_s3) && !env.CHANGE_ID) { - env.TAG = "v${version}" - log.info('########################## Checking if Artifact Already Exists. ###########################') - artifact_exists = sh(returnStdout: true, script: "aws s3 ls \"s3://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/\" 2>/dev/null || echo ''").trim() - log.info("${artifact_exists}") - if ( artifact_exists ) { - env.msg = "CI for this\n version - ${version}\n branch - ${branch_name}\n TAG - ${TAG}\nis already done. Please proceed with CD." - log.error(env.msg) - currentBuild.result = 'FAILURE' - throw new Exception(env.msg) - } - else { - log.info("Proceeding with building artifact for TAG - ${TAG}.") - } - } - else { - log.info('Skipping - Building artifact') - } - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (env.hot_fix) { - env.TAG = "v${version}-HOT" - } - else { - if (("${branch_name}" == 'master' || "${branch_name}" == 'main' || "${branch_name}" == 'gcp-main' || "${branch_name}" == 'gcp-master' || push_to_s3) && !env.CHANGE_ID) { - env.TAG = "v${version}" - log.info('########################## Checking if Artifact Already Exists. ###########################') - artifact_exists = sh(returnStdout: true, script: "gsutil ls \"gs://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/\" 2>/dev/null || echo ''").trim() - log.info("${artifact_exists}") - if ( artifact_exists ) { - env.msg = "CI for this\n version - ${version}\n branch - ${branch_name}\n TAG - ${TAG}\nis already done. Please proceed with CD." - log.error(env.msg) - currentBuild.result = 'FAILURE' - throw new Exception(env.msg) - } - else { - log.info("Proceeding with building artifact for TAG - ${TAG}.") - } - } - else { - log.info('Skipping - Building artifact.') - } - } - } - } -} - -/* -Function to build the maven package -input arguments: -repo_name: repository name for changing dirctory -skip_test: boolen value to skip unit tests -skip_sonar: boolen value to skip sonar quality gate -args: string parameter to provide additional arguments to build cmd, example: '-U' -*/ -def build(String repo_name, boolean skip_test, String args) { - try { - if (env.CLOUD_PROVIDER == 'AWS') { - stage(stageName('Building gradle package')) { - String build_cmd = 'gradle clean build' - dir("$repo_name") { - sh(script:"cp ~/.m2/settings.xml .;${build_cmd} ${args}") - } - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - stage(stageName('Building gradle package')) { - String build_cmd = 'gradle clean build' - dir("$repo_name") { - sh(script:"gradle -v;cp ~/.m2/settings.xml .;${build_cmd} ${args}") - } - } - } - } - catch ( Exception e) { - env.msg = "Error Building the maven package. Please check console output for more details - ${e}" - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} -/* -Fuction to execute sonar scan - Input Arguments: - repo_name: repository name - skip_sonar: boolena parameter to skip sonar scan -*/ -def sonar_scan(String repo_name, boolean skip_sonar) { - try { - stage(stageName('Running sonar scan')) { - if (!skip_sonar) { - dir("$repo_name") { - withSonarQubeEnv(env.sonarEnv) { - if (env.CHANGE_ID) { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "gradle sonar:sonar -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.repository=Homelab/${repo_name} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script: "gradle sonar:sonar -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.repository=Homelab/${repo_name} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET}") - } - } - else { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "gradle sonar:sonar -Dsonar.branch.name=${env.BRANCH_NAME}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script: "gradle sonar:sonar -Dsonar.branch.name=${env.BRANCH_NAME}") - } - } - } - } - } - else { - log.info('Skipping - Sonar Scan') - } - } - // if (!skip_sonar) { - // stage('Quality Gate') { - // timeout(time: 300, unit: 'SECONDS') { - // def qg = waitForQualityGate() - // catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { - // if (qg.status != 'OK') { - // error "stage failed due to quality gate failure: ${qg.status}" - // } - // } - // } - // } - // } - } - catch (Exception e) { - dir("$repo_name") { - withSonarQubeEnv(env.sonarEnv) { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: 'gradle sonar:sonar') - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script: 'gradle sonar:sonar') - } - } - } - } -} -/* -Function to get the version from build.gradle -input arguments: -repo_name: String parameter to change the directory where build.gradle is located -*/ -def getVersion(String repo_name) { - try { - dir("$repo_name") { - return sh(returnStdout: true, script: "grep version build.gradle | head -1 | cut -d \"'\" -f2").trim() - } - } - catch ( Exception e) { - env.msg = 'Error while getting the version from build.gradle . Please check console output for more details.' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Function to get the modules from build.gradle -input arguments: -repo_name: String parameter to change the directory where build.gradle is located -*/ -def getModules(String repo_name) { - try { - dir("$repo_name") { - modules = sh(returnStdout: true, script: 'xq -r .project.modules.module[] build.gradle 2>/dev/null || xq -r .project.modules.module build.gradle 2>/dev/null || echo empty').trim() - if (modules == 'empty' || modules == 'null') { - modules = 'module_less' - } - modules = modules.split('\n') as List - return modules - } - } - catch ( Exception e) { - env.msg = 'Error getting the modules from build.gradle . Please check console output for more details.' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Function to push artifacts to jfrog artifactory -input arguments: -repo_name: repository name -push_to_jfrog: boolen argument to push_to_jfrog -*/ -def pushArtifactToJFrog(String repo_name, boolean push_to_jfrog) { - try { - stage(stageName('Deploying to JFrog')) { - if (env.CLOUD_PROVIDER == 'AWS') { - branch_name = 'repo' - if (branch_name == 'master' || branch_name == 'main' || push_to_jfrog) { - dir("${repo_name}") { - profiles = sh(returnStdout:true, script: 'xq -r .project.profiles build.gradle ').trim() - block_dist = sh(returnStdout:true, script: 'xq -r .project.distributionManagement build.gradle ').trim() - // Check for profiles tag in the build.gradle - if (profiles != 'null') { - // Check if profiles has distiributionManagement defined - profile_dist = sh(returnStdout:true, script: 'xq -r .project.profiles.profile[].distributionManagement build.gradle 2>/dev/null || xq -r .project.profiles.profile.distributionManagement build.gradle 2>/dev/null || echo null').trim() - if (profile_dist != 'null' ) { - publish_repo = (branch_name == 'master' || branch_name == 'main' || branch_name == 'gcp-main' || branch_name == 'gcp-master') ? 'useProdRepo' : 'useTestRepo' - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh "gradle package deploy -DskipTests=true -D${publish_repo}=true" - } - else { - log.info('distributionManagement is not defined in the build.gradle . Skipping - Push to Jfrog Artifactory') - } - } - else if (block_dist != 'null') { - // Check if distributionManagement is defined without profiles - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh 'gradle package deploy -DskipTests=true' - } - else { - log.info('distributionManagement is not defined in the build.gradle . Skipping - Push to Jfrog Artifactory') - } - } - } - else { - log.info('########################### Skipping - Push to Jfrog Artifactory. ###########################') - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - branch_name = 'repo' - if (branch_name == 'master' || branch_name == 'main' || push_to_jfrog) { - dir("${repo_name}") { - profiles = sh(returnStdout:true, script: 'xq -r .project.profiles build.gradle ').trim() - block_dist = sh(returnStdout:true, script: 'xq -r .project.distributionManagement build.gradle ').trim() - // Check for profiles tag in the build.gradle - if (profiles != 'null') { - // Check if profiles has distiributionManagement defined - profile_dist = sh(returnStdout:true, script: 'xq -r .project.profiles.profile[].distributionManagement build.gradle 2>/dev/null || xq -r .project.profiles.profile.distributionManagement build.gradle 2>/dev/null || echo null').trim() - if (profile_dist != 'null' ) { - publish_repo = (branch_name == 'master' || branch_name == 'main' || branch_name == 'gcp-main' || branch_name == 'gcp-master') ? 'useProdRepo' : 'useTestRepo' - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh "gradle package deploy -DskipTests=true -D${publish_repo}=true" - echo 'execute gradle dependency' - sh 'gradle dependency:list > dependency_tree.txt' - echo ' uploading gradle dependency' - sh 'ls -ltr' - echo 'lets run copy command' - try { - sh "gsutil cp dependency_tree.txt 'gs://${env.objBucket}/common-dependencies/${repo_name}/'" - } - catch ( Exception e ) { - echo "Skipping - copying txt file to GCS - ${e}" - } - echo 'uploading gradle dependency' - } - else { - log.info('distributionManagement is not defined in the build.gradle . Skipping - Push to Jfrog Artifactory') - } - } - else if (block_dist != 'null') { - // Check if distributionManagement is defined without profiles - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh 'gradle package deploy -DskipTests=true' - } - else { - log.info('distributionManagement is not defined in the build.gradle . Skipping - Push to Jfrog Artifactory') - } - } - } - else { - log.info('########################### Skipping - Push to Jfrog Artifactory. ###########################') - } - } - } - } - catch ( Exception e ) { - env.msg = 'Error in pushing artifacts to jfrog . Please check console output for more details.' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Function to push artifacts to s3 -input arguments: -repo_name: repository name -push_to_s3: boolen argument to push to s3 -*/ -def pushArtifactToS3(String repo_name, String branch_name, boolean push_to_s3) { - try { - stage(stageName('Pushing artifacts to object storage')) { - if (env.CLOUD_PROVIDER == 'AWS') { - if ("${branch_name}" == 'master' || "${branch_name}" == 'main' || push_to_s3) { - def modules = getModules("${repo_name}") - log.info('}########################### Pushing artifacts to S3. ###########################') - j = 0 - for (module in modules) { - j += 1 - sh """ - echo "${j}. ${module}" - if [ -f ${repo_name}/${module}/target/*.jar ] - then - ls -al ${repo_name}/${module}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${TAG}" - aws s3 cp ${repo_name}/${module}/target/*.jar "s3://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" - echo "Listing ${TAG} artifacts -" - aws s3 ls "s3://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" || echo Nothing to display here. - elif [ -f ${repo_name}/target/*.jar ] && [ ${module} = 'module_less' ] - then - ls -al ${repo_name}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${TAG}" - aws s3 cp ${repo_name}/target/*.jar "s3://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" - echo "Uploading Archived Code to - ${repo_name}/${branch_name}/${TAG}" - echo "Listing ${TAG} artifacts -" - aws s3 ls "s3://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" || echo Nothing to display here. - else - echo "No jar file found." - fi - """ - } - log.info("TAG for CD - ${TAG}") - } - else { - log.info("Skipping - Artifact push. As it is not supported for ${env.BRANCH_NAME}") - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if ("${branch_name}" == 'master' || "${branch_name}" == 'main' || "${branch_name}" == 'gcp-main' || branch_name == 'gcp-master' || push_to_s3) { - def modules = getModules("${repo_name}") - log.info('}########################### Pushing artifacts to S3. ###########################') - j = 0 - for (module in modules) { - j += 1 - sh """ - echo "${j}. ${module}" - if [ -f ${repo_name}/${module}/target/*.jar ] - then - ls -al ${repo_name}/${module}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${TAG}" - gsutil cp ${repo_name}/${module}/target/*.jar "gs://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" - echo "Listing ${TAG} artifacts -" - gsutil ls "gs://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" || echo Nothing to display here. - elif [ -f ${repo_name}/target/*.jar ] && [ ${module} = 'module_less' ] - then - ls -al ${repo_name}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${TAG}" - gsutil cp ${repo_name}/target/*.jar "gs://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" - echo "Uploading Archived Code to - ${repo_name}/${branch_name}/${TAG}" - echo "Listing ${TAG} artifacts -" - gsutil ls "gs://${env.objBucket}/${repo_name}/${branch_name}/${TAG}/${module}/" || echo Nothing to display here. - else - echo "No jar file found." - fi - """ - } - log.info("TAG for CD - ${TAG}") - } - else { - log.info("Skipping - Artifact push. As it is not supported for ${env.BRANCH_NAME}") - } - } - } - } - catch ( Exception e ) { - env.msg = "Error in pushing artifacts to s3 bucket. Please check console output for more details - ${e}" - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Fuction for java version -*/ -def getDockerBuildVersion(String java_version) { - switch (java_version) { - case 'gradle': return '8-jdk-slim-secure-multiarch_v3.0' - } -} - -/* -Fuction to build maven docker repo -*/ - -def buildDckr(Map config) { - def btObj = new buTeamMapping() - def dparam_obj = new getDockerParams() - def constructObj = new constructTemplate() - - def team = btObj.get_team_initials(config.team) - def repo_name = config.repo_name - def tag = dparam_obj.getTag(repo_name) - def skip_test = config.skip_test ?: false - def modules = getModules("${repo_name}") - def excludedModules = config.excludedModules ?: [] - def deployArgo = config.deployArgo ?: false - def repoType = config.repo_type ?: 'microservice' - def docker_repo = "${env.cicd_environment}/${team}/${repo_name.toLowerCase()}" - env.JAVA_HOME = '/usr/lib/jvm/java-8-openjdk-amd64/' - env.PATH = "/opt/gradle/gradle-5.6.1/bin:${env.PATH}" - - def docker_bindings = [ - 'repo_name': repo_name, - 'buildRegistry': env.buildRegistry - ] - if (config.containsKey('copy_file')) { - // if (env.CLOUD_PROVIDER == "AWS"){ - // def recursive = config.copy_file.recursive ? ' --recursive' : '' - // } - // else if (env.CLOUD_PROVIDER == "GCP"){ - // def recursive = config.copy_file.recursive ? ' -r' : '' - // } - def recursive = config.copy_file.recursive ? ' --recursive' : '' - dir(repo_name) { - dir('copied_files') { - sh(script:"aws s3 cp${recursive} ${config.copy_file.path} .") - } - } - docker_bindings['copy_file'] = true - docker_bindings['copy_target'] = config.copy_file.target ?: '/opt/target' - } - else { - docker_bindings['copy_file'] = false - } - - if (modules != 'module_less') { - excludedModules.each { modules.removeElement(it) } - } - else { - modules = ['module_less'] - } - docker_bindings['arch'] = config.arch - def java_version = config.dockerBuildVersion - dockerBuildVersion = getDockerBuildVersion(java_version) - docker_bindings['dockerBuildVersion'] = dockerBuildVersion - run(config) - if (docker_bindings.copy_file) { - dir(repo_name) { - dir('copied_files') { - if (env.CLOUD_PROVIDER == 'AWS') { - // delete any old data - sh(script:'rm -rf *') - sh(script:"aws s3 cp${docker_bindings.recursive} ${docker_bindings.copy_file_path} .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - // delete any old data - docker_bindings['recursive'] = '-r' - sh(script:'rm -rf *') - sh(script:"gsutil cp ${docker_bindings.recursive} ${docker_bindings.copy_file_path}* .") - } - } - } - } - stage(stageName('Building docker images')) { - // Login to docker - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr get-login-password --region ${env.region} | docker login --username AWS --password-stdin ${env.registry}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script:'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } - dir(repo_name) { - for (module in modules) { - docker_bindings['module'] = module - constructObj.renderTemplate(docker_bindings,'java-Dockerfile','Dockerfile-' + module) - def module_repo = (module == 'module_less') ? docker_repo : docker_repo + '/' + module - if ( module == 'module_less') { - sh(script: 'mkdir target;cp build/libs/*jar target') - } - else { - sh(script: "mkdir -p ${module}/target;cp build/libs/*jar ${module}/target") - } - if (env.cicd_environment != 'ftr' && repoType == 'microservice') { - sh(script: "ls target; docker build --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module} . && docker push ${env.registry}/${module_repo}:${tag}") - } - else { - log.info("Skipping Docker Build - ${env.cicd_environment} env") - log.info("Skipping Docker Build - ${repoType} repo type") - } - //Remove dockerfile - sh(script: "rm -rf Dockerfile-${module}") - } - } - return [tag , deployArgo] - } -} diff --git a/src/com/homelab/stages/buildMaven.groovy b/src/com/homelab/stages/buildMaven.groovy deleted file mode 100644 index 5cc405a..0000000 --- a/src/com/homelab/stages/buildMaven.groovy +++ /dev/null @@ -1,1059 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams -import com.homelab.stages.checkOut -import com.homelab.utilities.gitActions -import com.homelab.utilities.constructParam -import com.homelab.utilities.dockerUtilities -/* -Function to define the flow of entire build, this function will call different stages related to maven build -*/ -def run(Map config) { - // get required variables from config - def checkoutObj = new checkOut() - def repo_name = config.repo_name - def args = config.build_args ?: '' - def skip_test = config.skip_test ?: false - def skip_sonar = config.skip_sonar ?: false - def push_to_jfrog = config.push_to_jfrog ?: false - def push_to_s3 = config.push_to_s3 ?: false - def skip_s3_check = config.skip_s3_check ?: false - def deployArgo = config.deployArgo ?: false - def branch_name = "${env.BRANCH_NAME}" - def appConfigChanges = false - def continue_build = true - def repoType = config.repo_type ?: 'microservice' - - if (branch_name == 'gcp-main' || branch_name == 'gcp-master') { - // push_to_jfrog = false - push_to_jfrog = config.containsKey('push_to_jfrog') ? config.push_to_jfrog : true - } - def version = getVersion("${repo_name}") - env.TAG = "v${version}" - def java_version = config.dockerBuildVersion - - echo "${env.req_java} ${java_version}" - - if (env.INFRA_ENV == 'toolchain') { - skip_s3_check = true - skip_sonar = true - push_to_jfrog = false - push_to_s3 = false - } - - if (env.hot_fix) { - skip_test = true - skip_sonar = true - push_to_jfrog = false - } - - if (config.enable_submodule) { - checkoutObj.chekoutSubmodule(repo_name) - } - // call the stages - echo "Artifact Check - ${skip_s3_check}" - if (env.hot_fix) { - env.TAG = "v${version}-HOT" - } - else if (skip_s3_check) { - log.info('Skipping - Checking if artifact already exists.') - } - else { - echo 'Checking if artifact already exists.' - continue_build = checkS3(repo_name, branch_name, env.objBucket, version, push_to_s3) - } - - - if (continue_build) { - //meanwhile latest commit check is not fixed creating build for appConfig changes also - (appConfigChanges , deployArgo) = AppConfigChangeCheck(repo_name, deployArgo.toBoolean()) - log.info("Is just application config change ? $appConfigChanges") - - //if appConfigChanges then skip the build and artifact push just run quality gate - if (!appConfigChanges){ - echo "proceeding with the build" - build(repo_name, skip_test, args,repoType) - //check for mq version - checkMQVersion(repo_name) - } - - sonar_scan(repo_name, skip_sonar,appConfigChanges) - - if (!appConfigChanges){ - if (!env.CHANGE_ID) { - pushArtifactToJFrog(repo_name, push_to_jfrog) - pushArtifactToS3(repo_name, branch_name, env.objBucket, push_to_s3) - // Set a flag to indicate S3 push was successful for cleanup purposes - env.S3_PUSH_SUCCESS = 'true' - } - else { - log.info('Artifact Push is disabled for Pull Requests') - } - } - continue_build = !appConfigChanges - } - return [continue_build , deployArgo] -} - -/* -Function to check if the artifact exist in s3 or not -input argument -*/ -def checkS3(String repo_name, String branch_name, String bucket_name, String version, boolean push_to_s3) { - stage(stageName('Checking if Jar already exists in GCS for current version')) { - if (env.CLOUD_PROVIDER == 'AWS') { - if (("${branch_name}" == 'master' || "${branch_name}" == 'main' || push_to_s3) && !env.CHANGE_ID) { - log.info('########################## Checking if Artifact Already Exists. ###########################') - artifact_exists = sh(returnStdout: true, script: "aws s3 ls \"s3://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/\" 2>/dev/null || echo ''").trim() - log.info("${artifact_exists}") - if ( artifact_exists ) { - env.msg = "CI for this\n version - ${version}\n branch - ${branch_name}\n TAG - ${env.TAG}\nis already done. Please proceed with CD." - log.error(env.msg) - return false - } - else { - log.info("Proceeding with building artifact for TAG - ${env.TAG}.") - } - } - else { - log.info('Skipping - Building artifact') - } - return true - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (("${branch_name}" == 'master' || "${branch_name}" == 'main' || "${branch_name}" == 'gcp-main' || "${branch_name}" == 'gcp-master' || push_to_s3) && !env.CHANGE_ID) { - log.info('########################## Checking if Artifact Already Exists. ###########################') - artifact_exists = sh(returnStdout: true, script: "gsutil ls \"gs://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/\" 2>/dev/null || echo ''").trim() - log.info("${artifact_exists}") - if ( artifact_exists ) { - env.msg = "CI for this\n version - ${version}\n branch - ${branch_name}\n TAG - ${env.TAG}\nis already done. Please proceed with CD." - log.error(env.msg) - return false - } - else { - log.info("Proceeding with building artifact for TAG - ${env.TAG}.") - } - } - else { - log.info('Skipping - Building artifact.') - } - return true - } - } -} - - -//check the application Config Change in github -//true when only changes are present in configs dir of repo -def AppConfigChangeCheck(String repo_name, boolean deployArgo){ - if (env.INFRA_ENV == 'toolchain') { - return [false , deployArgo] - } - def gitObj = new gitActions() - def configFiles = [] - def appConfigChanges = false - // Get changed files - def changedFiles = env.CHANGE_ID ? - gitObj.fetchDiffFilesForPullRequest(repo_name, env.CHANGE_TARGET) : - gitObj.fetchDiffFilesForPushRequest(repo_name) - - if (!changedFiles) { - return [appConfigChanges , deployArgo] - } - - // Process changed files - changedFiles.split('\n').each { file -> - if (!file.startsWith('configs/')) { - appConfigChanges = false - return [appConfigChanges , deployArgo] // Return from function if any non-config file - } - appConfigChanges = true - configFiles.add(file) - } - - // If we only have config files, check if they contain cicd_environment - if (configFiles && deployArgo && appConfigChanges) { - def hasEnvironmentConfig = configFiles.any { it.contains(env.cicd_environment) } - // Only set should_auto_cd to false if no config contains cicd_environment - if (!hasEnvironmentConfig) { - deployArgo = false - echo "Config changes don't contain environment: ${env.cicd_environment}" - } - } - return [appConfigChanges , deployArgo] -} - - -/* -Function to build the maven package -input arguments: -repo_name: repository name for changing dirctory -skip_test: boolen value to skip unit tests -skip_sonar: boolen value to skip sonar quality gate -args: string parameter to provide additional arguments to build cmd, example: '-U' -*/ -def build(String repo_name, boolean skip_test, String args, String repoType) { - def validation_output = "" - try { - stage(stageName('Running CaC validation script')) { - def param = new constructParam() - if (env.CHANGE_ID && repoType == 'microservice') { - def ValidateConfig = param.ValidateCacConfigForRepo(false,repo_name) - if (ValidateConfig) { - log.info('************ Validate Config for CAC application.yml files ************') - dir("$repo_name") { - writeFile file: 'validate_configs.py', text: libraryResource('com/homelab/validate_configs.py') - //Capture output separately, then check status - validation_output = sh( - script: 'python3 validate_configs.py 2>&1 || true', // || true prevents immediate failure - returnStdout: true - ).trim() - //Check if validation actually failed by examining output or running again - def exit_status = sh( - script: 'python3 validate_configs.py 2>/dev/null', - returnStatus: true - ) - - if (exit_status != 0) { - // If the script failed, manually throw an exception to enter the catch block - throw new Exception("Config validation failed with exit code ${exit_status}. See output for details.") - } - } - } else { - log.info('************ Skipping Validation of CAC Config ************') - } - } else { - log.info("Skipping Validation of CAC Config for ${repoType} repo type") - log.info('************ Skipping Validation of CAC Config ************') - } - } - - if (env.CLOUD_PROVIDER == 'AWS') { - stage(stageName('Building maven package')) { - String build_cmd = 'mvn clean install' - if (skip_test) { - args += ' -DskipTests' - } - dir("$repo_name") { - sh(script:"cp ~/.m2/settings.xml .;${build_cmd} ${args}") - } - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - stage(stageName('Building maven package')) { - String build_cmd = "JAVA_HOME=${env.req_java} mvn clean install" - if (skip_test) { - args = args + ' -DskipTests' - } - dir("$repo_name") { - full_script = "JAVA_HOME=${env.req_java} mvn -v; cp ~/.m2/settings.xml .; ${build_cmd} ${args}" - - def result = runCommandWithOutput(full_script) - - def maven_build_output = result.output - def exit_status = result.status - echo '$exit_status' - if (exit_status != 0) { - throw new Exception("Maven build failed with exit code ${exit_status}. See output for details.") - } - } - } - } - } - catch ( Exception e) { - def is_validation_error = !validation_output.isEmpty() && !validation_output.contains("Validation successful.") - def has_maven_build_output = false - def detailed_error_msg = has_maven_build_output && !is_validation_error ? - "--- Maven Build Failure Details ---\n" + maven_build_output : - (is_validation_error ? - validation_output : - e.toString()) - env.msg = "Error Building the maven package. If validate script error then please refer to doc - https://homelab.atlassian.net/wiki/spaces/EW/pages/3915972744/Config+Schema+Validation+Common+Errors+And+Fixes . Also Please check console output for more details - ${e}" - env.error_msg_to_db = detailed_error_msg - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} -/* -Fuction to execute sonar scan - Input Arguments: - repo_name: repository name - skip_sonar: boolena parameter to skip sonar scan -*/ -def sonar_scan(String repo_name, boolean skip_sonar , boolean appConfigChanges) { - try { - stage(stageName('Running sonar scan')) { - if (!skip_sonar && !appConfigChanges) { - dir("$repo_name") { - withSonarQubeEnv(env.sonarEnv) { - if (env.CHANGE_ID) { - if (env.CLOUD_PROVIDER == 'AWS') { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git fetch origin ${env.CHANGE_TARGET}:refs/remotes/origin/${env.CHANGE_TARGET}" - } - sh(script: "mvn sonar:sonar -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.repository=Homelab/${repo_name} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET} -Dsonar.ws.timeout=120") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git fetch origin ${env.CHANGE_TARGET}:refs/remotes/origin/${env.CHANGE_TARGET}" - } - sh(script: "mvn sonar:sonar -Dsonar.pullrequest.provider=GitHub -Dsonar.projectName=${repo_name} -Dsonar.pullrequest.github.repository=Homelab/${repo_name} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET} -Dsonar.ws.timeout=120") - } - } - else { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "mvn sonar:sonar -Dsonar.branch.name=${env.BRANCH_NAME} -Dsonar.ws.timeout=120") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script: "mvn sonar:sonar -Dsonar.branch.name=${env.BRANCH_NAME} -Dsonar.projectName=${repo_name} -Dsonar.ws.timeout=120") - } - } - } - } - } - else { - log.info('Skipping - Sonar Scan') - } - } - stage(stageName('Quality Gate')){ - if (appConfigChanges){ - log.info("Skipping the quality gate check as Application config change ") - } - else if (skip_sonar){ - log.info("Sonar scan is skipped. Marking this stage as passed.") - } - else if (!env.CHANGE_ID || env.cicd_environment == 'int'){ - log.info("Skipping quality gate check on Branches/Pre-Prod. Marking this stage as passed.") - } - else{ - def timeoutValue = (env.INFRA_ENV == 'prd') ? 600 : 360 - timeout(time: timeoutValue, unit: 'SECONDS') { - def qg = waitForQualityGate() - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE'){ - if (qg.status != 'OK') { - error "stage failed due to quality gate failure: ${qg.status}" - } - } - } - } - } - } - catch (Exception e) { - dir("$repo_name") { - withSonarQubeEnv(env.sonarEnv) { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: 'mvn sonar:sonar -Dsonar.ws.timeout=120') - } - else if (env.CLOUD_PROVIDER == 'GCP') { - log.info("Sonar Scan or Quality Gate has failed. Continuing with the build.") - env.error_msg_to_db = 'Sonar Scan or Quality Gate has failed. Continuing with the build.' - env.msg = 'Sonar Scan or Quality Gate has failed. Continuing with the build.' - } - } - } - } -} -/* -Function to get the version from pom.xml -input arguments: -repo_name: String parameter to change the directory where pom.xml is located -*/ -def getVersion(String repo_name) { - try { - dir("$repo_name") { - def pom_version = sh(returnStdout: true, script: 'xq -r .project.version pom.xml').trim() - if (pom_version.contains('null')) { - env.msg = 'Error while getting the version from pom.xml. Please check console output for more details.' - env.error_msg_to_db = 'Error Getting the version from pom.xml' - log.error(env.msg) - currentBuild.result = env.FAILURE - sh 'exit 1' - } - else { - return pom_version - } - } - } - catch ( Exception e) { - env.msg = 'Error while getting the version from pom.xml. Please check console output for more details.' - env.error_msg_to_db = 'Error Getting the version from pom.xml' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Function to get the modules from pom.xml -input arguments: -repo_name: String parameter to change the directory where pom.xml is located -*/ -def getModules(String repo_name) { - try { - dir("$repo_name") { - modules = sh(returnStdout: true, script: 'xq -r .project.modules.module[] pom.xml 2>/dev/null || xq -r .project.modules.module pom.xml 2>/dev/null || echo empty').trim() - if (modules == 'empty' || modules == 'null') { - modules = 'module_less' - } - modules = modules.split('\n') as List - return modules - } - } - catch ( Exception e) { - env.msg = 'Error getting the modules from pom.xml. Please check console output for more details.' - env.error_msg_to_db = 'Error getting the modules from pom.xml' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Function to push artifacts to jfrog artifactory -input arguments: -repo_name: repository name -push_to_jfrog: boolen argument to push_to_jfrog -*/ -def pushArtifactToJFrog(String repo_name, boolean push_to_jfrog) { - try { - stage(stageName('Deploying to JFrog')) { - if (env.CLOUD_PROVIDER == 'AWS') { - branch_name = "${env.BRANCH_NAME}" - if (branch_name == 'master' || branch_name == 'main' || push_to_jfrog) { - dir("${repo_name}") { - profiles = sh(returnStdout:true, script: 'xq -r .project.profiles pom.xml').trim() - block_dist = sh(returnStdout:true, script: 'xq -r .project.distributionManagement pom.xml').trim() - // Check for profiles tag in the pom.xml - if (profiles != 'null') { - // Check if profiles has distiributionManagement defined - profile_dist = sh(returnStdout:true, script: 'xq -r .project.profiles.profile[].distributionManagement pom.xml 2>/dev/null || xq -r .project.profiles.profile.distributionManagement pom.xml 2>/dev/null || echo null').trim() - if (profile_dist != 'null' ) { - publish_repo = (branch_name == 'master' || branch_name == 'main' || branch_name == 'gcp-main' || branch_name == 'gcp-master') ? 'useProdRepo' : 'useTestRepo' - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh "mvn package deploy -DskipTests=true -D${publish_repo}=true" - } - else { - log.info('distributionManagement is not defined in the pom.xml. Skipping - Push to Jfrog Artifactory') - } - } - else if (block_dist != 'null') { - // Check if distributionManagement is defined without profiles - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh 'mvn package deploy -DskipTests=true' - } - else { - log.info('distributionManagement is not defined in the pom.xml. Skipping - Push to Jfrog Artifactory') - } - } - } - else { - log.info('########################### Skipping - Push to Jfrog Artifactory. ###########################') - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - branch_name = "${env.BRANCH_NAME}" - if (branch_name == 'master' || branch_name == 'main' || push_to_jfrog) { - dir("${repo_name}") { - profiles = sh(returnStdout:true, script: 'xq -r .project.profiles pom.xml').trim() - block_dist = sh(returnStdout:true, script: 'xq -r .project.distributionManagement pom.xml').trim() - // Check for profiles tag in the pom.xml - if (profiles != 'null') { - // Check if profiles has distiributionManagement defined - profile_dist = sh(returnStdout:true, script: 'xq -r .project.profiles.profile[].distributionManagement pom.xml 2>/dev/null || xq -r .project.profiles.profile.distributionManagement pom.xml 2>/dev/null || echo null').trim() - if (profile_dist != 'null' ) { - publish_repo = (branch_name == 'master' || branch_name == 'main' || branch_name == 'gcp-main' || branch_name == 'gcp-master') ? 'useProdRepo' : 'useTestRepo' - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh "JAVA_HOME=${env.req_java} mvn package deploy -DskipTests=true -D${publish_repo}=true" - } - else { - log.info('distributionManagement is not defined in the pom.xml. Skipping - Push to Jfrog Artifactory') - } - } - else if (block_dist != 'null') { - // Check if distributionManagement is defined without profiles - log.info('########################### Pushing artifact to Jfrog. ###########################') - sh "JAVA_HOME=${env.req_java} mvn package deploy -DskipTests=true" - } - else { - log.info('distributionManagement is not defined in the pom.xml. Skipping - Push to Jfrog Artifactory') - } - } - } - else { - log.info('########################### Skipping - Push to Jfrog Artifactory. ###########################') - } - } - } - } - catch ( Exception e ) { - env.msg = 'Error in pushing artifacts to jfrog . Please check console output for more details.' - env.error_msg_to_db = 'Error in Pushing Artifacts To Jfrog' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -/* -Function to push artifacts to s3 -input arguments: -repo_name: repository name -push_to_s3: boolen argument to push to s3 -*/ -def pushArtifactToS3(String repo_name, String branch_name, String bucket_name, boolean push_to_s3) { - try { - stage(stageName('Pushing artifacts to GCS bucket')) { - if (env.CLOUD_PROVIDER == 'AWS') { - if ("${branch_name}" == 'master' || "${branch_name}" == 'main' || push_to_s3) { - def modules = getModules("${repo_name}") - log.info('}########################### Pushing artifacts to S3. ###########################') - j = 0 - for (module in modules) { - j += 1 - sh """ - echo "${j}. ${module}" - if [ -f ${repo_name}/${module}/target/*.jar ] - then - ls -al ${repo_name}/${module}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${env.TAG}" - aws s3 cp ${repo_name}/${module}/target/*.jar "s3://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" - echo "Listing ${env.TAG} artifacts -" - aws s3 ls "s3://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" || echo Nothing to display here. - elif [ -f ${repo_name}/target/*.jar ] && [ ${module} = 'module_less' ] - then - ls -al ${repo_name}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${env.TAG}" - aws s3 cp ${repo_name}/target/*.jar "s3://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" - echo "Uploading Archived Code to - ${repo_name}/${branch_name}/${env.TAG}" - echo "Listing ${env.TAG} artifacts -" - aws s3 ls "s3://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" || echo Nothing to display here. - else - echo "No jar file found." - fi - """ - } - log.info("TAG for CD - ${env.TAG}") - } - else { - log.info("Skipping - Artifact push. As it is not supported for ${env.BRANCH_NAME}") - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if ("${branch_name}" == 'master' || "${branch_name}" == 'main' || "${branch_name}" == 'gcp-main' || branch_name == 'gcp-master' || push_to_s3) { - def modules = getModules("${repo_name}") - log.info('}########################### Pushing artifacts to S3. ###########################') - j = 0 - for (module in modules) { - j += 1 - sh """ - echo "${j}. ${module}" - if [ -f ${repo_name}/${module}/target/*.jar ] - then - ls -al ${repo_name}/${module}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${env.TAG}" - gsutil cp ${repo_name}/${module}/target/*.jar "gs://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" - echo "Listing ${env.TAG} artifacts -" - gsutil ls "gs://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" || echo Nothing to display here. - elif [ -f ${repo_name}/target/*.jar ] && [ ${module} = 'module_less' ] - then - ls -al ${repo_name}/target/*.jar - echo "Uploading artifacts to - ${repo_name}/${branch_name}/${env.TAG}" - gsutil cp ${repo_name}/target/*.jar "gs://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" - echo "Uploading Archived Code to - ${repo_name}/${branch_name}/${env.TAG}" - echo "Listing ${env.TAG} artifacts -" - gsutil ls "gs://${env.objBucket}/${repo_name}/${branch_name}/${env.TAG}/${module}/" || echo Nothing to display here. - else - echo "No jar file found." - fi - """ - } - log.info("TAG for CD - ${env.TAG}") - } - else { - log.info("Skipping - Artifact push. As it is not supported for ${env.BRANCH_NAME}") - } - } - } - } - catch ( Exception e ) { - env.msg = "Error in pushing artifacts to s3 bucket. Please check console output for more details - ${e}" - env.error_msg_to_db = 'Error in Pushing Artifacts to S3 Buckets' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -def stageDocker(def repo_name, def region, def registry, def docker_repo, def docker_bindings, def modules, def tag, def tagShort, def build_docker, def team) { - def constructObj = new constructTemplate() - def dockerUtilObj = new dockerUtilities() - if (docker_bindings.copy_file) { - dir(repo_name) { - dir('copied_files') { - if (env.CLOUD_PROVIDER == 'AWS') { - // delete any old data - sh(script:'rm -rf *') - sh(script:"aws s3 cp${docker_bindings.recursive} ${docker_bindings.copy_file_path} .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - // delete any old data - docker_bindings['recursive'] = '-r' - sh(script:'rm -rf *') - sh(script:"gsutil cp ${docker_bindings.recursive} ${docker_bindings.copy_file_path}* .") - } - } - } - } - stage(stageName('Building docker images')) { - if (build_docker) { - // Login to docker - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr get-login-password --region ${env.region} | docker login --username AWS --password-stdin ${env.registry}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script:'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } - } - catch (Exception e) { - env.msg = 'Error in Docker login' - env.error_msg_to_db = 'Error in Docker login' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - dir(repo_name) { - try { - for (module in modules) { - docker_bindings['module'] = module - constructObj.renderTemplate(docker_bindings, 'java-Dockerfile', 'Dockerfile-' + module) - module_repo = (module == 'module_less') ? docker_repo : docker_repo + '/' + module - // Check and craete ECR - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr describe-repositories --region ${env.region} --repository-names ${module_repo} || aws ecr create-repository --region ${env.region} --repository-name ${module_repo}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - echo 'Skipping - Registry Creation in GCP.' - } - } - catch (Exception e) { - env.msg = "Error in creating ECR repository ${module_repo}" - env.error_msg_to_db = "Error in creating ECR repository ${module_repo}" - currentBuild.result = env.FAILURE - throw e - } - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module} --push .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - // sleep(10000) - sh(script: "docker build --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module} .") - if (env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') { - dockerUtilObj.retryDockerPush("docker push ${env.registry}/${module_repo}:${tag}") - } else { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - } - } - - //Remove dockerfile - sh(script: "rm -rf Dockerfile-${module}") - } - } - catch (Exception e) { - env.msg = 'Error in building DockerFile Or Pushing To ECR' - env.error_msg_to_db = env.msg - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - } - } - else { - log.info('Getting latest image tag from ECR') - mod_docker_repo = (modules[0] == 'module_less') ? docker_repo : docker_repo + '/' + modules[0] - try { - if (env.CLOUD_PROVIDER == 'AWS') { - tag = sh(script: "aws ecr describe-images --output json --repository-name ${mod_docker_repo} --query 'sort_by(imageDetails,& imagePushedAt)[].imageTags[]|[-1]'", returnStdout: true).replaceAll("[\n\"]", '') - } - else if (env.CLOUD_PROVIDER == 'GCP') { - latest_image = sh(script: "gcloud container images list-tags ${env.registry}/${mod_docker_repo} --format='value(tags)' | sed '/^\$/d' | awk -F'-' '{print \$NF}' | sort | tail -1", returnStdout: true).trim() - tag = sh(script: "gcloud container images list-tags ${env.registry}/${mod_docker_repo} --format='value(tags)' | grep ${latest_image}", returnStdout: true).trim() - // tag = sh(script: "gcloud container images list-tags ${env.registry}/${mod_docker_repo} --format='value(tags)' | grep -i '${tagShort}' | sort | tail -1", returnStdout: true).trim() - } - } - catch (Exception e) { - env.msg = 'Error getting latest image from ECR' - env.error_msg_to_db = env.msg - log.error(env.msg + '\n' + e.toString()) - currentBuild.result = env.FAILURE - throw e - } - } - return tag - } -} - -def getDockerBuildVersion(String java_version) { - switch (java_version) { - case 'maven-graalvm': return 'slim-jdk8-graalvm' - case 'maven-graalvm-11': return '11-jdk-slim-graalvm' - case 'maven-3.3-jdk-11': return '11-java' - case 'maven-3.3-jdk-8': return '8-jdk-slim-secure-multiarch_v3.0' - case 'maven-3.3-jdk-17': return '17-jdk-slim-secure-multiarch_v2.0' - case 'maven-3.9-jdk-25': return '25-jdk-noble-amd64' - } -} - -def getReqJavaPath(String java_version) { - switch (java_version) { - case 'maven-3.9-jdk-25': return '/usr/lib/jvm/java-25-openjdk-amd64/' - case 'maven-3.3-jdk-17': return '/usr/lib/jvm/java-17-openjdk-amd64/' - case 'maven-3.3-jdk-11': return '/usr/lib/jvm/java-17-openjdk-amd64/' - case 'maven-graalvm': return '/usr/lib/jvm/java-8-openjdk-amd64/' - case 'maven-graalvm-11': return '/usr/lib/jvm/java-17-openjdk-amd64/' - default: return '/usr/lib/jvm/java-8-openjdk-amd64/' - } -} -/* -Fuction to build maven docker repo -*/ - -def buildDckr(Map config) { - def btObj = new buTeamMapping() - def dparam_obj = new getDockerParams() - - def team = btObj.get_team_initials(config.team) - def repo_name = config.repo_name - def skip_test = config.skip_test ?: false - def modules = getModules("${repo_name}") - def excludedModules = config.excludedModules ?: [] - def docker_repo = "${env.cicd_environment}/${team}/${repo_name.toLowerCase()}" - def module_repo = '' - def tag = dparam_obj.getTag(repo_name) - def tagShort = dparam_obj.getTagShort(repo_name) - def deployArgo = config.deployArgo ?: false - def build_docker - def java_version = config.dockerBuildVersion - def repoType = config.repo_type ?: 'microservice' - env.req_java = '/usr/lib/jvm/java-8-openjdk-amd64/' - boolean allImagesExist = true - if (env.INFRA_ENV == 'toolchain') { - def dockerUtilObj = new dockerUtilities() - for (module in modules) { - def moduleName = (module instanceof LinkedHashMap) ? module.keySet()[0] : module - def modulePath = (moduleName == 'module_less') ? docker_repo : "${docker_repo}/${moduleName}" - if (excludedModules.contains(moduleName)) { - continue - } - if (!dockerUtilObj.imageExists(env.registry, modulePath, tag)) { - log.info("Toolchain: Image missing for ${moduleName} at ${modulePath}:${tag}. Proceeding with build.") - allImagesExist = false - break - } - } - if (allImagesExist) { - log.info("Toolchain: All images found in registry for tag ${tag}. Skipping build step.") - return [tag, deployArgo] - } - } - - def docker_bindings = [ - 'repo_name': repo_name, - 'buildRegistry': env.buildRegistry, - 'CLOUD_PROVIDER': env.CLOUD_PROVIDER - ] - if (config.containsKey('copy_file')) { - // if (env.CLOUD_PROVIDER == "AWS"){ - // def recursive = config.copy_file.recursive ? ' --recursive' : '' - // } - // else if (env.CLOUD_PROVIDER == "GCP"){ - // def recursive = config.copy_file.recursive ? ' -r' : '' - // } - def recursive = config.copy_file.recursive ? ' --recursive' : '' - docker_bindings['copy_file'] = true - docker_bindings['recursive'] = recursive - docker_bindings['copy_file_path'] = config.copy_file.path - docker_bindings['copy_target'] = config.copy_file.target ?: '/opt/target' - } - else { - docker_bindings['copy_file'] = false - } - - if (modules != 'module_less') { - excludedModules.each { modules.removeElement(it) } - } - else { - modules = ['module_less'] - } - docker_bindings['arch'] = config.arch - def dockerBuildVersion = '' - - - dockerBuildVersion = getDockerBuildVersion(java_version) - docker_bindings['dockerBuildVersion'] = dockerBuildVersion - if (java_version != 'maven-3.3-jdk-8' && java_version != 'maven-graalvm') { - try { - if (env.CLOUD_PROVIDER == 'AWS') { - node(java_version) { - def checkObj = new checkOut() - checkObj.run(['repo_name': repo_name]) - (build_docker , deployArgo) = run(config) - tag = stageDocker(repo_name, env.region, env.registry, docker_repo, docker_bindings, modules, tag, tagShort, build_docker, team) - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - - env.req_java = getReqJavaPath(java_version) - echo "CICD - Using JAVA_HOME from config.dockerBuildVersion (${java_version}): ${env.req_java}" - // sleep(32764572) - - (build_docker , deployArgo) = run(config) - if ((env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') && repoType == 'microservice') { - log.info("1. Building Docker image for ${modules}") - tag = stageDocker(repo_name, env.region, env.registry, docker_repo, docker_bindings, modules, tag, tagShort, build_docker, team) - } - else { - log.info("1 Skipping Docker build for ${env.cicd_environment} env") - log.info("1 Skipping Docker build for ${repoType} repo type") - } - } - // If we reach here, the entire pipeline succeeded - env.PIPELINE_SUCCESS = 'true' - } catch (Exception pipelineException) { - // Any exception - handled or unhandled - will reach here if re-thrown - throw pipelineException - } finally { - // Cleanup if S3 succeeded but pipeline didn't complete successfully - if (env.S3_PUSH_SUCCESS == 'true' && env.PIPELINE_SUCCESS != 'true') { - log.error("Pipeline did not complete successfully after S3 push. Cleaning up S3 artifacts.") - cleanupGCSArtifacts(repo_name, "${env.BRANCH_NAME}", env.objBucket) - } - } - } - else { - try { - (build_docker , deployArgo) = run(config) - if ((env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') && repoType == 'microservice' ) { - tag = stageDocker(repo_name, env.region, env.registry, docker_repo, docker_bindings, modules, tag, tagShort, build_docker, team) - } - else { - log.info("2 Skipping Docker build for ${env.cicd_environment} env") - log.info("2 Skipping Docker build for ${repoType} repo type") - } - // If we reach here, the entire pipeline succeeded - env.PIPELINE_SUCCESS = 'true' - } catch (Exception pipelineException) { - // Any exception - handled or unhandled - will reach here if re-thrown - throw pipelineException - } finally { - // Cleanup if S3 succeeded but pipeline didn't complete successfully - if (env.S3_PUSH_SUCCESS == 'true' && env.PIPELINE_SUCCESS != 'true') { - log.error("Pipeline did not complete successfully after S3 push. Cleaning up S3 artifacts.") - cleanupGCSArtifacts(repo_name, "${env.BRANCH_NAME}", env.objBucket) - } - } - } - return [tag , deployArgo] -} - -def checkMQVersion(String repo_name) { - try { - if (env.CLOUD_PROVIDER == 'AWS') { - stage(stageName('Building maven package')) { - String build_cmd = 'mvn clean install' - if (skip_test) { - args += ' -DskipTests' - } - dir("$repo_name") { - sh(script:"cp ~/.m2/settings.xml .;${build_cmd} ${args}") - } - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - stage(stageName('Checking MQ version dependency')) { - String build_cmd = "JAVA_HOME=${env.req_java} mvn dependency:tree" - dir("$repo_name") { - sh "pwd" - sh(script: "JAVA_HOME=${env.req_java} mvn -v; cp ~/.m2/settings.xml .; ${build_cmd} > dependencies.txt") - log.info("dependency tree Built success") - def content = readFile('dependencies.txt') - //log.info("reading content of file") - def lines = content.readLines() - //log.info("searching for Building lines") - def buildingLines = lines.findIndexValues { it.contains("Building") } - buildingLines << lines.size() - //log.info("Lines found ${buildingLines}") - - def checkBuildVersions = { startIndex, endIndex, appName -> - - //log.info("Function for check versions") - log.info("Checking ${appName} :") - if (startIndex < 0 || endIndex > lines.size() || startIndex > endIndex) { - error "Invalid range: startIndex = ${startIndex}, endIndex = ${endIndex}" - } - def relevantLines = lines[startIndex..(endIndex - 1)] - - //echo "Building lines found at indices: ${buildingLines}" - def mqLine = relevantLines.find { it.contains("mq-client-blocking") } - if (!mqLine) { - echo "mq-client-blocking not found in the module. Skipping further checks." - return - } - def mqVersionFull = mqLine.split(':')[-2]?.trim() - echo "mq-client-blocking version is: ${mqVersionFull}" - def mqVersion = sh(script: "echo \"${mqVersionFull}\" | awk -F 'RELEASE' '{print \$1}' | sed 's/\\.\$//'", returnStdout: true).trim() - echo "Processed MQ version is: ${mqVersion}" - - //def mqVersionFull = relevantLines.find { it.contains("mq-client-blocking") }?.split(':')[-2]?.trim() - //add code to remove the RELEASE keyword - DONE - //lexicographically or integer wise - interger wise - //echo "mq version is : ${mqVersionFull}" - //def mqVersion = sh(script: "echo \"${mqVersionFull}\" | awk -F 'RELEASE' '{print \$1}' | sed 's/\\.\$//'", returnStdout: true).trim() - - // def springKafkaVersion = relevantLines.find { it.contains("org.springframework.kafka") }?.split(':')[-2]?.trim() - // echo "SPF kafka ${springKafkaVersion}" - // def apacheKafkaVersion = relevantLines.find { it.contains("org.apache.kafka:kafka-clients") }?.split(':')[-2]?.trim() - // echo "apache kafka ${apacheKafkaVersion}" - - - def springKafkaLine = relevantLines.find { it.contains("org.springframework.kafka:spring-kafka:jar") } - def springKafkaVersion = null - if (springKafkaLine) { - springKafkaVersion = springKafkaLine.split(':')[-2]?.trim() - echo "springframework kafka version: ${springKafkaVersion}" - } else { - echo "org.springframework.kafka not found in the module" - } - - def apacheKafkaLine = relevantLines.find { it.contains("org.apache.kafka:kafka-clients:jar") } - def apacheKafkaVersion = null - if (apacheKafkaLine) { - apacheKafkaVersion = apacheKafkaLine.split(':')[-2]?.trim() - echo "Apache kafka version: ${apacheKafkaVersion}" - } else { - echo "org.apache.kafka:kafka-clients not found in the module." - } - - if (mqVersion) { - echo "mq-client-blocking version: ${mqVersionFull} ( ${mqVersion} )" - if (isVersionMQ(mqVersion, "3.0.18")) { - if (springKafkaVersion) { - //echo "org.springframework.kafka version: ${springKafkaVersion}" - if (!isVersionMQ(springKafkaVersion, "2.9.13")) { - echo "org.springframework.kafka version: ${springKafkaVersion}" - error "org.springframework.kafka version is lower than 2.9.13. Build failed." - } - } - if (apacheKafkaVersion) { - //echo "org.apache.kafka version: ${apacheKafkaVersion}" - if (!isVersionMQ(apacheKafkaVersion,"3.6.0")) { - echo "org.apache.kafka version: ${apacheKafkaVersion}" - error "org.apache.kafka version is lower than 3.6.0. Build failed." - } - } - echo "org.springframework.kafka version: ${springKafkaVersion} || org.apache.kafka version: ${apacheKafkaVersion}" - } - } else { - echo "mq-client-blocking is missing or has a version lower than 3.0.18. Skipping checks." - } - } - log.info("Starting version checks") - for (int i = 0; i < buildingLines.size() - 1; i++) { - //log.info("starting Loop") - def startIndex = (int) buildingLines[i] - def endIndex = (int) buildingLines[i + 1] - def appNameLine = lines[startIndex] - def appName = appNameLine.split("Building")[-1]?.trim() - checkBuildVersions(startIndex, endIndex, appName) - } - } - } - } - -} - catch ( Exception e) { - env.msg = "Error checking the MQ version. Please check console output for more details - ${e}" - env.error_msg_to_db = 'Error checking the MQ version' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -boolean isVersionMQ(String v1, String v2) { - def v1Parts = v1.tokenize('.') - def v2Parts = v2.tokenize('.') - - for (int i = 0; i < Math.max(v1Parts.size(), v2Parts.size()); i++) { - int v1Part = i < v1Parts.size() ? v1Parts[i].toInteger() : 0 - int v2Part = i < v2Parts.size() ? v2Parts[i].toInteger() : 0 - if (v1Part != v2Part) { - return v1Part > v2Part - } - } - return true -} - -/* -Function to clean up artifacts from GCS in case of failure -input arguments: -repo_name: repository name -branch_name: branch name -bucket_name: bucket name -*/ -def cleanupGCSArtifacts(String repo_name, String branch_name, String bucket_name) { - try { - log.info("########################## Cleaning up artifacts from GCS due to failure. ##########################") - def artifactPath = "gs://${bucket_name}/${repo_name}/${branch_name}/${env.TAG}/" - - // Check if artifacts exist before attempting cleanup - def artifactsExist = sh(returnStdout: true, script: "gsutil ls \"${artifactPath}\" 2>/dev/null || echo ''").trim() - - if (artifactsExist) { - log.info("Removing artifacts from: ${artifactPath}") - sh(script: "gsutil -m rm -r \"${artifactPath}\"") - log.info("Successfully cleaned up artifacts from GCS.") - } else { - log.info("No artifacts found to clean up.") - } - } catch (Exception cleanupException) { - log.warn("Warning: Failed to clean up artifacts from GCS - ${cleanupException.getMessage()}") - // Don't throw the cleanup exception to avoid masking the original failure - } -} - - - -def runCommandWithOutput(script_cmd) { - def exitCodeFile = ".maven_exit_code" - def outputFile = ".maven_build_output" - sh( - script: """#!/bin/bash - set +e - ${script_cmd} 2>&1 | tee ${outputFile} - echo \${PIPESTATUS[0]} > ${exitCodeFile} - exit 0 - """ - ) - - def exitCode = sh( - script: "cat ${exitCodeFile}", - returnStdout: true - ).trim() as int - - def output = sh( - script: "cat ${outputFile}", - returnStdout: true - ).trim() - - return [ - output: output, - status: exitCode - ] -} - - diff --git a/src/com/homelab/stages/buildNode.groovy b/src/com/homelab/stages/buildNode.groovy deleted file mode 100644 index 4348cb1..0000000 --- a/src/com/homelab/stages/buildNode.groovy +++ /dev/null @@ -1,568 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams -import com.homelab.utilities.addSSHKey -import com.homelab.utilities.dockerUtilities -import com.homelab.utilities.getYamlParameter - -def removePackageLock() { - sh(script: 'rm -rf package-lock.json') -} - -def buildNode(def build_cmd) { - try { - if (fileExists('package-lock.json')) { - sh(script: 'npm ci') - } - else { - sh(script: 'npm install') - } - sh(script: build_cmd) - } - catch ( Exception e ) { - env.msg = 'Error in building node packages . Please check console output for more details.' - env.error_msg_to_db = 'Error Building Node Packages' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -def getArtifactId(String repo_name) { - dir("$repo_name") { - if (fileExists('package.json')) { - return sh(returnStdout: true, script: 'jq -r .name package.json').trim() - } - } -} - -def getAwsSecret(def secret_name, def destination_file, def team, def bu) { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws secretsmanager get-secret-value --secret-id ${secret_name} --query SecretString --output text > ${destination_file}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - getVaultSecret("vault kv get -format=json homelab/${env.cicd_environment}/${bu}/${team}/${secret_name} | jq -r .data.data > ${destination_file}") - } -} - -def getNpmRc(def secret_name, def team, def bu) { - def npmrc_file = secret_name + '-npmrc-' + env.cicd_environment - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws secretsmanager get-secret-value --secret-id ${npmrc_file} --query SecretString --output text| jq -r .HOMELAB_NPMRC_SECRET > .npmrc") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - getVaultSecret("vault kv get -format=json homelab/${env.cicd_environment}/${bu}/${team}/${npmrc_file} | jq -r .data.data.HOMELAB_NPMRC_SECRET > .npmrc") - } -} - -def getPemFile(def secret_name, def team, def bu) { - def pem_secret_name = secret_name + '-secrets-' + env.cicd_environment - if (env.BUILD_ENV == 'stage') { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws secretsmanager get-secret-value --secret-id ${pem_secret_name} --query SecretString --output text| jq -r .public_secret_dev > 1_public_secret_dev.pem") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - getVaultSecret("vault kv get -format=json homelab/${env.cicd_environment}/${bu}/${team}/${pem_secret_name} | jq -r .data.data.public_secret_dev > 1_public_secret_dev.pem") - } - sh(script:"cat 1_public_secret_dev.pem | sed -e 's/-----BEGIN PUBLIC KEY-----/& \\n/' -e 's/-----END PUBLIC KEY-----/\\n-----END PUBLIC KEY-----/g' > public_secret_dev.pem") - } - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws secretsmanager get-secret-value --secret-id ${pem_secret_name} --query SecretString --output text| jq -r .public_secret_prod > 1_public_secret_prod.pem") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - getVaultSecret("vault kv get -format=json homelab/${env.cicd_environment}/${bu}/${team}/${pem_secret_name} | jq -r .data.data.public_secret_prod > 1_public_secret_prod.pem") - } - - sh(script:"cat 1_public_secret_prod.pem | sed -e 's/-----BEGIN PUBLIC KEY-----/& \\n/' -e 's/-----END PUBLIC KEY-----/\\n-----END PUBLIC KEY-----/g' > public_secret_prod.pem") -} - -def getEnvFile(def secret_name, def team, def bu, boolean useCacPath = false) { - def env_file = secret_name + '-env-' + env.cicd_environment - def gcp_env_file = secret_name - def destination_file = '.env' - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws secretsmanager get-secret-value --secret-id ${env_file} | jq --raw-output '.SecretString' | jq '.' | jq -r 'to_entries|map(\"\\(.key)=\\(.value|tostring)\")|.[]' > .env") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - def vault_path = useCacPath - ? "homelab/${env.cicd_environment}-cac/${bu}/${team}/${gcp_env_file}-client" - : "homelab/${env.cicd_environment}/${bu}/${team}/${gcp_env_file}" - getVaultSecret("vault kv get -format=json ${vault_path} | jq -r '.data.data | to_entries|map(\"\\(.key)=\\(.value|tostring)\")|.[]' > .env") - } -} - -/** - * Read appConfigEnabled from deployment yaml(s) in repo (deployments/.yaml). - * Uses config.deployment_order for deployment names, or repo_name if not set. - * Aligns with deploy/Helm which reads appConfigEnabled from the same files. - */ -def isAppConfigEnabledFromDeployments(String repo_name, Map config) { - def deploymentNames = (config.deployment_order instanceof List && !config.deployment_order.isEmpty()) - ? config.deployment_order - : [repo_name] - def yamlObj = new getYamlParameter() - for (def deployment in deploymentNames) { - try { - def depYaml = yamlObj.getParam(repo_name, "deployments/${deployment}.yaml") - def enabled = depYaml?.appConfigEnabled - if (enabled == true || enabled?.toString()?.equalsIgnoreCase('true')) { - return true - } - } catch (Exception e) { - log.debug("No appConfigEnabled in ${repo_name}/deployments/${deployment}.yaml or file missing: ${e.message}") - continue - } - } - return false -} - -def getManifestJson(def secret_name, def team, def bu) { - def manifest_file = secret_name + '-manifest-' + env.cicd_environment - def destination_file = 'public/manifest.json' - getAwsSecret(manifest_file, destination_file, team, bu) -} - -def generateExcludePattern(String excludeS3Files) { - def files = excludeS3Files.split(/\s*,\s*/) // Split the string by comma and trim whitespace - return '|' + files.join('|') // Join file names with '|' as an "or" operator in regex -} - -def shouldSkipPm2Metrics(Map config) { - return config.skip_pm2_metrics?.toString()?.toBoolean() ?: false -} - -/* -Fuction to build maven docker repo -*/ - -def getCommitSHA(String repo_name) { - dir(repo_name) { - return sh(returnStdout: true, script: 'git log -1 --format=%H').trim() - } -} - -def buildDckr(Map config) { - // // Block all Node.js builds - throwing exception - // throw new Exception("Node.js builds are currently blocked, As a precautionary measure. Due to some packages that got hacked.") - def btObj = new buTeamMapping() - def constructObj = new constructTemplate() - def dparam_obj = new getDockerParams() - def addSSHKey = new addSSHKey() - def dockerUtilObj = new dockerUtilities() - - def team = btObj.get_team_initials(config.team) - def bu = btObj.get_bu_initials(config.bu) - def repo_name = config.repo_name - // CAC: read appConfigEnabled from deployment folder (deployments/.yaml), same source as deploy/Helm - def useCacPath = isAppConfigEnabledFromDeployments(repo_name, config) - def build_cmd = config.build_cmd ?: 'npm run build' - // Resolve secret_name from deployment YAML app_name. - // For multi-deployment repos, env/secret files are kept in sync so the first valid app_name is used. - def secret_name = repo_name - def keep_package_lock = config.keep_package_lock != null ? config.keep_package_lock : true - def skip_npmrc = config.skip_npmrc != null ? config.skip_npmrc : true - if (config.secret_name) { - secret_name = config.secret_name - } else { - try { - def yamlObj = new getYamlParameter() - def deploymentsPath = "${repo_name}/deployments" - def yamlFilesOutput = sh( - script: "ls ${deploymentsPath}/*.yaml 2>/dev/null | xargs -r -n1 basename", - returnStdout: true - ).trim() - if (yamlFilesOutput) { - def yamlFiles = yamlFilesOutput.split('\n').collect { it.trim() }.findAll { it } - for (yamlFile in yamlFiles) { - def appConfig = yamlObj.getParam(deploymentsPath, yamlFile) - def appName = appConfig?.app_name?.toString()?.trim() - if (appName) { - echo "Resolved secret_name to ${appName} from ${deploymentsPath}/${yamlFile}" - secret_name = appName - break - } - } - } - if (secret_name == repo_name) { - echo "No app_name found in deployment yamls; using repo_name: ${repo_name}" - } - } catch (Exception e) { - echo "Error reading deployment yaml: ${e.getMessage()}; using repo_name: ${repo_name}" - } - } - def require_mainfest = config.require_mainfest ?: false - def pbac_enabled = config.pbac_enabled ?: false - def pbac_scope_name = config.pbac_scope_name ?: '' - def npm_install_arg = config.npm_install_arg ?: '' - if (!npm_install_arg) { - if (fileExists("${repo_name}/pnpm-lock.yaml") || build_cmd.contains('pnpm')) { - npm_install_arg = 'npm install -g pnpm@10.33.0 && pnpm install --frozen-lockfile' - } - else if (!keep_package_lock || !fileExists("${repo_name}/package-lock.json")) { - npm_install_arg = 'npm install' - } - // else: package-lock.json exists + keep_package_lock=true → Dockerfile uses `npm ci` - } - def require_pemfiles = config.require_pemfiles ?: false - def deployArgo = config.deployArgo ?: false - def artifactId = getArtifactId("${repo_name}") - // def region = dparam_obj.getRegion(env.cicd_environment) - // def registry = dparam_obj.getRegistry(env.cicd_environment) - def docker_repo = "${env.cicd_environment}/${team}/${repo_name.toLowerCase()}" - def push_to_s3 = config.push_to_s3 ?: false - def s3_path = config.s3_path ?: "homelab-${env.BUILD_ENV}-artifacts/${repo_name}/${env.BRANCH_NAME}" - if (env.INFRA_ENV == 'toolchain' && env.TOOLCHAIN_ENV) { - docker_repo = "${env.cicd_environment}/${env.TOOLCHAIN_ENV}/${team}/${repo_name.toLowerCase()}" - log.info("Toolchain: Overriding docker_repo to ${docker_repo}") - echo "docker_repo: ${docker_repo}" - - if (config.s3_path && config.push_to_s3) { - echo" {env.cicd_environment} should be stg due ot ovveride" - s3_path = s3_path.replaceFirst("/${env.cicd_environment}/", "/${env.TOOLCHAIN_ENV}/") - log.info("TOOLCHAIN OVERRIDE:") - log.info(" Original S3 Path: ${config.s3_path}") - log.info(" New Toolchain S3 Path: ${s3_path}") - log.info(" Toolchain Env ID: ${env.TOOLCHAIN_ENV}") - - } else if (!config.push_to_s3) { - log.info("Toolchain: push_to_s3 is false, skipping s3_path override as it wont be pushed") - } else if (!config.s3_path && config.push_to_s3) { - log.info("Toolchain: s3_path is not set but push_to_s3 is true, skipping s3_path override as it wont be pushed") - error("CRITICAL: 's3_path' is missing in config.yaml for the ${env.cicd_environment} environment. Toolchain builds require an s3_path.") - } - } - if (env.TOOLCHAIN_ENV) { - log.info("TOOLCHAIN_ENV: ${env.TOOLCHAIN_ENV}") - }else{ - log.info("TOOLCHAIN_ENV: not set") - } - def local_path = config.local_path ?: 'build/' - def acl = config.acl ? ' --acl ' + config.acl : '' - def custom_pm2_metrics = config.custom_pm2_metrics ?: false - def skip_pm2_metrics = shouldSkipPm2Metrics(config) - def include_s3_files = '' - def exclude_s3_files = '' - if (!s3_path.endsWith('/')) { - s3_path += '/' - } - if (!local_path.endsWith('/')) { - local_path += '/' - } - if (env.CLOUD_PROVIDER == 'AWS') { - if (config.include_s3_files) { - include_s3_files = ' --exclude "*"' - def include_file_map = config.include_s3_files.split(',') - for (pattern in include_file_map) { - include_s3_files += ' --include "'+pattern+'"' - } - } - exclude_s3_files = config.exclude_s3_files ? ' --exclude "'+config.exclude_s3_files+'"' : '' - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (config.include_s3_files) { - def includeFileMap = config.include_s3_files.split(',') - def file_types = '' - def i = 0 - includeFileMap.each { pattern -> - if ( i == 0 ) { - file_types = "${pattern.replaceAll('\\*.', '')}" - } - else { - file_types += "|${pattern.replaceAll('\\*.', '')}" - } - i++ - } - if ( file_types == '*' ) { - include_s3_files = '^(?!.*\\.*$)' - } - else { - include_s3_files = "^(?!.*\\.(${file_types})\$)" - } - echo "include_s3_files is ${include_s3_files}" - } - else { - include_s3_files = '^(?!.*\\.*$)' - } - - exclude_s3_files = config.exclude_s3_files != null && config.exclude_s3_files.trim()? generateExcludePattern(config.exclude_s3_files.trim()) : '' - - } - def phantomjs = config.phantomjs ?: false - def version = config.dockerBuildVersion.split('-')[-1] - def tag = dparam_obj.getTag(repo_name) - // TODO: create value binding for dockerfile render - - def skip_sonar = config.skip_sonar ?: false - if (env.hot_fix || env.INFRA_ENV == 'toolchain') { - skip_sonar = true - } - def testCMD = config.testCMD ?: 'test-report' - def scmType = 'branch' - def commit_sha = getCommitSHA(config.repo_name) - def docker_bindings = [ - 'buildRegistry': env.buildRegistry, - 'version': version, - 'build_cmd': build_cmd, - 'push_to_s3': push_to_s3, - 's3_path': s3_path, - 'local_path': local_path, - 'include_s3_files': include_s3_files, - 'exclude_s3_files': exclude_s3_files, - 'acl': acl, - 'phantomjs': phantomjs, - 'skip_sonar': skip_sonar, - 'testCMD': testCMD, - 'CLOUD_PROVIDER': env.CLOUD_PROVIDER, - 'npm_install_arg': npm_install_arg - ] - docker_bindings['arch'] = config.arch - docker_bindings['pbac_enabled'] = pbac_enabled - docker_bindings['pbac_scope_name'] = pbac_scope_name ?: '' - // Map BUILD_ENV to pbac format (stg/int/prd) - def pbac_env = env.cicd_environment - docker_bindings['pbac_env'] = pbac_env - docker_bindings['useCacPath'] = useCacPath - - // Validate pbac configuration - if (pbac_enabled && !pbac_scope_name) { - error('pbac_scope_name is required when pbac_enabled is true in config.yaml') - } - - stage(stageName('Creating build files')) { - dir(repo_name) { - if (!keep_package_lock) { - removePackageLock() - } - if (!skip_npmrc) { - getNpmRc(secret_name, team, bu) - } - - getEnvFile(secret_name, team, bu, useCacPath) - - if (require_pemfiles) { - getPemFile(secret_name, team, bu) - } - if (require_mainfest) { - getManifestJson(secret_name, team, bu) - } - def npm_registry = '' - if (fileExists('.npmrc')) { - npm_registry = sh( - returnStdout: true, - script: "grep '^@homelab:registry=' .npmrc | head -1 | cut -d'=' -f2 | tr -d '\\r\\n'" - ).trim() - echo "Detected npm registry: ${npm_registry ?: 'default (none found)'}" - } else { - echo ".npmrc not found, skipping registry extraction" - } - docker_bindings['npm_registry'] = npm_registry - } - } - - if (skip_sonar){ - stage(stageName('Checking quality gate')){ - log.info("Skipping the quality gate check as sonar scan is skipped or this is a hotfix ") - } - } - stage(stageName('Building docker images')) { - // Login to docker - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr get-login-password --region ${env.region} | docker login --username AWS --password-stdin ${env.registry}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script:'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } - } - catch (Exception e) { - env.msg = 'Error in Docker login' - env.error_msg_to_db = 'Error in Docker login' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr describe-repositories --region ${env.region} --repository-names ${docker_repo} || aws ecr create-repository --region ${env.region} --repository-name ${docker_repo}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - echo 'Skipping - Registry Creation in GCP.' - } - } - catch (Exception e) { - env.msg = "Error in creating ECR repository ${docker_repo}" - env.error_msg_to_db = "Error in creating ECR repository ${docker_repo}" - currentBuild.result = 'FAILURE' - } - try { - dir(repo_name) { - addSSHKey.create() - withCredentials([usernamePassword(credentialsId: "svc-devops-homelab-token", usernameVariable: 'GIT_USER', passwordVariable: 'GIT_TOKEN')]) { - sh """ - echo "GITHUB_TOKEN=${GIT_TOKEN}" >> .env - echo "GIT_COMMIT_SHA='${commit_sha}'" >> .env - """ - } - withCredentials([string(credentialsId: env.sonarToken, variable: 'TOKEN')]) { - sh """ - echo "SONAR_HOST_URL='${env.sonarURL}'" >> .env - echo "SONAR_TOKEN='${TOKEN}'" >> .env - echo "SONAR_WS_TIMEOUT=120" >> .env - """ - if (env.CHANGE_ID) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git fetch origin ${env.CHANGE_TARGET}:refs/remotes/origin/${env.CHANGE_TARGET}" - } - sh """ - echo "SONAR_CHANGE_ID='${env.CHANGE_ID}'" >> .env - echo "SONAR_CHANGE_BRANCH='${env.CHANGE_BRANCH}'" >> .env - echo "SONAR_CHANGE_TARGET='${env.CHANGE_TARGET}'" >> .env - """ - scmType = 'pr' - } - else { - sh """ - echo "SONAR_BRANCH_NAME='${env.BRANCH_NAME}'" >> .env - """ - } - echo 'Make sure to update script section in package.json for Sonar Analysis to be successfull.' - docker_bindings['scmType'] = scmType - } - if (!fileExists('Dockerfile')) { - constructObj.renderTemplate(docker_bindings, 'node-Dockerfile', 'Dockerfile-' + artifactId) - sh "cat Dockerfile-${artifactId}" - if (push_to_s3) { - if (env.CLOUD_PROVIDER == 'AWS') { - sh """ - echo 'FROM amazon/aws-cli:2.2.0 as push_env' >> Dockerfile-${artifactId} - echo 'COPY --from=build-env /usr/src/app /app' >> Dockerfile-${artifactId} - echo 'RUN ls -al && aws s3 cp /app/${local_path} s3://${s3_path}${include_s3_files}${exclude_s3_files} --recursive --cache-control max-age=31536000,public${acl}' >> Dockerfile-${artifactId} - """ - } - else if (env.CLOUD_PROVIDER == 'GCP' ) { - sh """ - echo 'FROM asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/google-cloud-sdk:458.0.0-alpine as push_env' >> Dockerfile-${artifactId} - echo 'COPY --from=build-env /usr/src/app /app' >> Dockerfile-${artifactId} - # echo 'RUN ls -al /app && find /app/${local_path} -type f \\( -name "*.js" -o -name "*.map" \\) && find /app/${local_path} -type f \\( -name "*.js" -o -name "*.map" \\) | wc -l && gsutil -m cp -r /app/${local_path}* gs://${s3_path}' >> Dockerfile-${artifactId} - echo "RUN ls -al /app && gsutil -m rsync -r -x \'${include_s3_files} ${exclude_s3_files}\' /app/${local_path} gs://${s3_path}" >> Dockerfile-${artifactId} - echo "RUN if [ -f /app/${local_path}index.html ]; then gsutil cp /app/${local_path}index.html gs://${s3_path}; fi" >> Dockerfile-${artifactId} - """ - } - } - - if (phantomjs) { - sh """ - echo 'FROM build-env' >> Dockerfile-${artifactId} - echo 'RUN apt-get update && apt-get install -y libfontconfig' >> Dockerfile-${artifactId} - echo 'WORKDIR /usr/src/app' >> Dockerfile-${artifactId} - echo 'RUN npm install pm2 -g' >> Dockerfile-${artifactId} - echo 'RUN pm2 install pm2-metrics' >> Dockerfile-${artifactId} - if [ "${custom_pm2_metrics}" = "true" ]; then - echo 'RUN pm2 install pm2-prom-module' >> Dockerfile-${artifactId} - echo 'RUN pm2 set pm2-prom-module:port 9200' >> Dockerfile-${artifactId} - echo 'RUN pm2 restart pm2-prom-module' >> Dockerfile-${artifactId} - fi - echo 'RUN rm -rf /root/.ssh/id_rsa && apt-get remove -y git openssh-client bzip2' >> Dockerfile-${artifactId} - """ - } - else { - sh """ - echo 'FROM ${env.buildRegistry}/build/node:${version}-alpine-secure-multiarch_v1.0' >> Dockerfile-${artifactId} - echo 'RUN npm install pm2 -g' >> Dockerfile-${artifactId} - if [ "${skip_pm2_metrics}" != "true" ]; then - echo 'RUN pm2 install pm2-metrics' >> Dockerfile-${artifactId} - fi - if [ "${custom_pm2_metrics}" = "true" ]; then - echo 'RUN pm2 install pm2-prom-module' >> Dockerfile-${artifactId} - echo 'RUN pm2 set pm2-prom-module:port 9200' >> Dockerfile-${artifactId} - echo 'RUN pm2 restart pm2-prom-module' >> Dockerfile-${artifactId} - fi - echo 'WORKDIR /app' >> Dockerfile-${artifactId} - echo 'COPY --from=build-env /usr/src/app /app' >> Dockerfile-${artifactId} - """ - } - if (docker_bindings['useCacPath']) { - sh "echo 'RUN truncate -s 0 .env' >> Dockerfile-${artifactId}" - } - sh "cat Dockerfile-${artifactId}" - def imageList = "${env.buildRegistry}/build/node:${version}-alpine-secure-multiarch_v1.0 " + - "asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin/devops/google-cloud-sdk:458.0.0-alpine " + - "${env.buildRegistry}/build/node:${version}-slim-secure-multiarch_v2.0" - - sh """ - max_attempts=5 - images=\"$imageList\" - - for image in \$images; do - attempt=1 - until docker pull \"\$image\"; do - if [ \$attempt -eq \$max_attempts ]; then - echo \"Failed to pull \$image after \$attempt attempts.\" - exit 1 - fi - echo \"Pull failed for \$image, retrying in 2 seconds... (Attempt \$attempt/\$max_attempts)\" - attempt=\$((attempt + 1)) - sleep 2 - done - done - """ - if (env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') { - def docker_cmd = "export DOCKER_BUILDKIT=0;docker build --tag ${env.registry}/${docker_repo}:${tag} -f Dockerfile-${artifactId} . " - sh(script: docker_cmd) - dockerUtilObj.retryDockerPush("docker push ${env.registry}/${docker_repo}:${tag}") - } - else { - def docker_cmd = "export DOCKER_BUILDKIT=0;docker build --tag ${env.registry}/${docker_repo}:${tag} -f Dockerfile-${artifactId} ." - sh(script: docker_cmd) - } - //Remove dockerfile - sh(script: "rm -rf Dockerfile-${artifactId}") - } - else { - if (env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') { - sh(script: "export DOCKER_BUILDKIT=0;docker build --tag ${env.registry}/${docker_repo}:${tag} . ") - dockerUtilObj.retryDockerPush("docker push ${env.registry}/${docker_repo}:${tag}") - } - else { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - sh(script: "export DOCKER_BUILDKIT=0;docker build --tag ${env.registry}/${docker_repo}:${tag} .") - } - } - } - } - catch (Exception e) { - env.msg = 'Error in building DockerFile Or Pushing To ECR' - env.error_msg_to_db = env.msg - log.error(env.msg + '\n' + e.toString()) - currentBuild.result = env.FAILURE - throw e - } - return [tag , deployArgo] - } -} - - -def getVaultSecret(String vault_cmd) { - log.info("Fetching secrets from Vault with vault-${env.cicd_environment}-token with CMD - ${vault_cmd}") - if (env.INFRA_ENV == 'toolchain' && env.TOOLCHAIN_ENV) { - boolean skipToolchainOverride = vault_cmd.contains("npmrc") || vault_cmd.contains("-secrets-") - if (!skipToolchainOverride) { - vault_cmd = vault_cmd.replace("homelab/${env.cicd_environment}-cac/", "homelab/toolchain/${env.TOOLCHAIN_ENV}/${env.cicd_environment}-cac/") - vault_cmd = vault_cmd.replace("homelab/${env.cicd_environment}/", "homelab/toolchain/${env.TOOLCHAIN_ENV}/${env.cicd_environment}/") - log.info("TOOLCHAIN OVERRIDE (Application Config):") - log.info(" New Toolchain Vault CMD: ${vault_cmd}") - } else { - log.info("TOOLCHAIN: Reading from standard (non-toolchain) Vault path for npmrc / pem secret.") - } - } - - withCredentials([string(credentialsId: "${env.vaultToken}", variable: 'TOKEN')]) { - env.VAULT_ADDR = "${env.vaultURL}" - env.VAULT_TOKEN = "${TOKEN}" - sh(script:"${vault_cmd}") - env.VAULT_TOKEN = 'empty' - env.VAULT_ADDR = env.VAULT_TOKEN - } -} diff --git a/src/com/homelab/stages/buildObjHelper.groovy b/src/com/homelab/stages/buildObjHelper.groovy deleted file mode 100644 index 5695c8b..0000000 --- a/src/com/homelab/stages/buildObjHelper.groovy +++ /dev/null @@ -1,42 +0,0 @@ -package com.homelab.stages - -def run(String build_tool){ - try { - switch (build_tool) { - case 'maven': - return new buildMaven() - break; - case 'docker': - return new buildDocker() - break; - case ~/^maven-.*/: - return new buildMaven() - break; - case ~/^python-.*/: - return new buildPython() - break; - case ~/^node-.*/: - return new buildNode() - break; - case ~/^rust.*/: - return new buildRust() - break; - case ~/^go.*/: - return new buildGo() - break; - case 'gradle': - return new buildGradle() - break; - case 'php': - return new buildPhp() - default: - return defaultBuild() - } - } - catch (Exception e) { - env.msg = 'Error in selecting the build tool (check the spell) Error: ' + e.toString() - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} diff --git a/src/com/homelab/stages/buildPhp.groovy b/src/com/homelab/stages/buildPhp.groovy deleted file mode 100644 index 19e4c8a..0000000 --- a/src/com/homelab/stages/buildPhp.groovy +++ /dev/null @@ -1,104 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams - -def buildDckr(Map config){ - def btObj = new buTeamMapping() - def constructObj = new constructTemplate() - def dparam_obj = new getDockerParams() - - def team = btObj.get_team_initials(config.team) - def repo_name = config.repo_name - def deployArgo = config.deployArgo ?: false - // def region = dparam_obj.getRegion(env.cicd_environment) - // def registry = dparam_obj.getRegistry(env.cicd_environment) - def docker_repo = "${env.cicd_environment}/${team}/${repo_name.toLowerCase()}" - def tag = dparam_obj.getTag(repo_name) - def docker_bindings = [ - "repo_name": repo_name, - 'buildRegistry': env.buildRegistry - - ] - if (config.containsKey("copy_file")){ - if (env.CLOUD_PROVIDER == 'AWS'){ - def recursive = config.copy_file.recursive ? " --recursive" : "" - dir(repo_name){ - dir('copied_files'){ - sh(script:"aws s3 cp${recursive} ${config.copy_file.path} .") - } - } - } else if (env.CLOUD_PROVIDER == 'GCP'){ - def recursive = config.copy_file.recursive ? " -r" : "" - dir(repo_name){ - dir('copied_files'){ - sh(script:"gsutil cp${recursive} ${config.copy_file.path} .") - } - } - } - docker_bindings["copy_file"] = true - docker_bindings["copy_target"] = config.copy_file.target ?: "/opt/target" - } - else{ - docker_bindings["copy_file"] = false - } - stage(stageName('Building docker images')){ - // Login to docker - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr get-login-password --region ${env.region} | docker login --username AWS --password-stdin ${env.registry}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script:'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } - } - catch(Exception e) { - env.msg = "Error in Docker login" - env.error_msg_to_db = "Error in Docker login" - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - try{ - if (env.CLOUD_PROVIDER == "AWS"){ - sh(script:"aws ecr describe-repositories --region ${env.region} --repository-names ${docker_repo} || aws ecr create-repository --region ${env.region} --repository-name ${docker_repo}") - } - else if (env.CLOUD_PROVIDER == 'GCP'){ - echo "Skipping - Registry Creation in GCP." - } - } - catch(Exception e){ - env.msg = "Error in creating ECR repository ${docker_repo}" - env.error_msg_to_db = "Error in creating ECR repository ${docker_repo}" - currentBuild.result = "FAILURE" - } - try { - dir(repo_name){ - if(!fileExists("Dockerfile")){ - constructObj.renderTemplate(docker_bindings,'php-Dockerfile','Dockerfile-php') - if (env.cicd_environment != 'ftr') { - sh(script: "docker build --tag ${env.registry}/${docker_repo}:${tag} -f Dockerfile-php . && docker push ${env.registry}/${docker_repo}:${tag}") - } - else { - log.info("Skipping Docker builds for PHP in ${env.cicd_environment} env") - } - - //Remove dockerfile - sh(script: "rm -rf Dockerfile-php") - } - else { - sh(script: "docker build --tag ${env.registry}/${docker_repo}:${tag} . && docker push ${env.registry}/${docker_repo}:${tag}") - } - } - } - catch (Exception e) { - env.msg = "Error in building DockerFile Or Pushing To artifact registry" - env.error_msg_to_db = "Error in building DockerFile Or Pushing To image registry" - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - } - return [tag , deployArgo] -} diff --git a/src/com/homelab/stages/buildPython.groovy b/src/com/homelab/stages/buildPython.groovy deleted file mode 100644 index 1b37ce3..0000000 --- a/src/com/homelab/stages/buildPython.groovy +++ /dev/null @@ -1,221 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams -import com.homelab.utilities.addSSHKey -import com.homelab.utilities.dockerUtilities -/* -Function to get the version from pom.xml -input arguments: -repo_name: String parameter to change the directory where pom.xml is located -*/ -// def getArtifactId(String repo_name){ -// dir("$repo_name"){ -// if (fileExists("package.json")) { -// return sh(returnStdout: true, script: 'jq -r .name package.json').trim() -// } -// } -// } - -// def getAwsSecret(def secret_name, def destination_file){ -// sh (script:"aws secretsmanager get-secret-value --secret-id ${secret_name} --query SecretString --output text > ${destination_file}") -// } - -// def getCommitid(String repo_name){ -// dir("$repo_name"){ -// return sh(returnStdout: true, script: 'git log -1 --format=%h').trim() -// } -// } - -// def getNpmRc(def secret_name){ -// def npmrc_file = secret_name + "-npmrc" -// sh (script:"aws secretsmanager get-secret-value --secret-id ${npmrc_file} --query SecretString --output text| jq -r .HOMELAB_NPMRC_SECRET > .npmrc") -// } - -// def getPemFile(def secret_name){ -// def pem_secret_name = secret_name + "-secrets" -// if(env.BUILD_ENV == 'stage'){ -// sh (script:"aws secretsmanager get-secret-value --secret-id ${pem_secret_name} --query SecretString --output text| jq -r .public_secret_dev > 1_public_secret_dev.pem") -// sh (script:"cat 1_public_secret_dev.pem | sed -e 's/-----BEGIN PUBLIC KEY-----/& \\n/' -e 's/-----END PUBLIC KEY-----/\\n-----END PUBLIC KEY-----/g' > public_secret_dev.pem") -// } -// sh (script:"aws secretsmanager get-secret-value --secret-id ${pem_secret_name} --query SecretString --output text| jq -r .public_secret_prod > 1_public_secret_prod.pem") -// sh (script:"cat 1_public_secret_prod.pem | sed -e 's/-----BEGIN PUBLIC KEY-----/& \\n/' -e 's/-----END PUBLIC KEY-----/\\n-----END PUBLIC KEY-----/g' > public_secret_prod.pem") -// } - -// def getEnvFile(def secret_name){ -// def env_file = secret_name + "-env" -// def destination_file = ".env" -// sh(script:"aws secretsmanager get-secret-value --secret-id ${env_file} | jq --raw-output '.SecretString' | jq '.' | jq -r 'to_entries|map(\"\\(.key)=\\(.value|tostring)\")|.[]' > .env") -// } - -// def getManifestJson(def secret_name){ -// def manifest_file = secret_name+"-manifest" -// def destination_file = "public/manifest.json" -// getAwsSecret(manifest_file,destination_file) -// } -/* -Fuction to build maven docker repo -*/ - -def buildDckr(Map config) { - def btObj = new buTeamMapping() - def constructObj = new constructTemplate() - def dparam_obj = new getDockerParams() - def addSSHKey = new addSSHKey() - - def team = btObj.get_team_initials(config.team) - def repo_name = config.repo_name - def deployArgo = config.deployArgo ?: false - def modules_requirements_file = config.modules_requirements_file ?: 'requirements.txt' - def docker_repo = "${env.cicd_environment}/${team}/${repo_name.toLowerCase()}" - def tag = dparam_obj.getTag(repo_name) - if (env.INFRA_ENV == 'toolchain') { - def dockerUtilObj = new dockerUtilities() - if (dockerUtilObj.imageExists(env.registry, docker_repo, tag)) { - log.info("Toolchain: Image found in registry for ${docker_repo}:${tag}. Skipping build step.") - return [tag, deployArgo] - } else { - log.info("Toolchain: Image missing for ${docker_repo}:${tag}. Proceeding with build.") - } - } - // TODO: create value binding for dockerfile render - def docker_bindings = [ - 'buildRegistry': env.buildRegistry, - 'modules_requirements_file': modules_requirements_file, - 'arch': config.arch, - 'buildRegistry': env.buildRegistry - ] - docker_bindings['arch'] = config.arch - // stage("Create build files"){ - // dir(repo_name){ - // getNpmRc(secret_name) - // getEnvFile(secret_name) - // if (require_pemfiles){getPemFile(secret_name)} - // if (require_mainfest){getManifestJson(secret_name)} - // } - // } - stage(stageName('Building docker images')) { - // Login to docker - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr get-login-password --region ${env.region} | docker login --username AWS --password-stdin ${env.registry}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script:'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } - } - catch (Exception e) { - env.msg = 'Error in Docker login' - env.error_msg_to_db = 'Error in Docker login' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - if (config.containsKey('copy_file')) { - def recursive = config.copy_file.recursive ? ' --recursive' : '' - docker_bindings['copy_file_path'] = config.copy_file.path - dir(repo_name) { - dir('copied_files') { - if (env.CLOUD_PROVIDER == 'AWS') { - // delete any old data - sh(script:'rm -rf *') - sh(script:"aws s3 cp${recursive} ${config.copy_file.path} .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - // delete any old data - docker_bindings['recursive'] = '-r' - sh(script:'rm -rf *') - sh(script:"gsutil cp ${docker_bindings.recursive} ${docker_bindings.copy_file_path} .") - } - } - } - docker_bindings['copy_file'] = true - docker_bindings['copy_target'] = config.copy_file.target ?: '/app/' - } - else { - docker_bindings['copy_file'] = false - } - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr describe-repositories --region ${env.region} --repository-names ${docker_repo} || aws ecr create-repository --region ${env.region} --repository-name ${docker_repo}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - echo 'Skipping - Registry Creation in GCP.' - } - } - catch (Exception e) { - env.msg = "Error in creating ECR repository ${docker_repo}" - env.error_msg_to_db = "Error in creating ECR repository ${docker_repo}" - currentBuild.result = env.FAILURE - } - try { - dir(repo_name) { - addSSHKey.create() - if (!fileExists('Dockerfile')) { - // print(config) - constructObj.renderTemplate(docker_bindings,config.dockerBuildVersion+'-Dockerfile','Dockerfile-'+repo_name) - sh "cat Dockerfile-${repo_name}" - // withCredentials([string(credentialsId: 'homelab-github-ssh-prv-key', variable: 'SSH_PRIVATE_KEY_S')]) { - // withCredentials(bindings: [sshUserPrivateKey(credentialsId: 'homelab-ssh-github-key', \ - // keyFileVariable: 'SSH_PRIVATE_KEY', \ - // passphraseVariable: '', \ - // usernameVariable: '')]) { - //withCredentials([string(credentialsId: 'git_private_key', variable: 'gitkey')]) { - // sh """ - // set +x - // docker build --tag ${env.registry}/${docker_repo}:${tag} --build-arg SSH_PRIVATE_KEY="\$(cat ~/.ssh/id_github_jenkins)" -f "Dockerfile-${repo_name}" . && docker push ${env.registry}/${docker_repo}:${tag} - // set -x - // """ - if (env.CLOUD_PROVIDER == 'AWS') { - sh """ - set +x - docker build --tag ${env.registry}/${docker_repo}:${tag} --build-arg SSH_PRIVATE_KEY="\$(cat ~/.ssh/id_github_jenkins)" -f "Dockerfile-${repo_name}" . - docker push ${env.registry}/${docker_repo}:${tag} - set -x - """ - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (env.cicd_environment != 'ftr' || env.INFRA_ENV == 'toolchain') { - sh """ - set +x - docker build --tag ${env.registry}/${docker_repo}:${tag} --build-arg SSH_PRIVATE_KEY="\$(cat ~/.ssh/id_github_jenkins)" -f "Dockerfile-${repo_name}" . - docker push ${env.registry}/${docker_repo}:${tag} - set -x - """ - } - else { - log.info("Skipping Docker builds for Python in ${env.cicd_environment} env") - } - } - // } - // } - //Remove dockerfile - sh(script: "rm -rf Dockerfile-${repo_name}") - } - else { - sh "cat Dockerfile" - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${docker_repo}:${tag} --push .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh """ - set +x - docker build --tag ${env.registry}/${docker_repo}:${tag} --build-arg SSH_PRIVATE_KEY="\$(cat ~/.ssh/id_github_jenkins)" -f "Dockerfile" . - docker push ${env.registry}/${docker_repo}:${tag} - set -x - """ - } - } - } - } - catch (Exception e) { - env.msg = "Error in building DockerFile Or Pushing To ECR. For Full Error Details - ${e}" - env.error_msg_to_db = 'Error in building DockerFile Or Pushing To ECR' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - return [tag , deployArgo] - } -} diff --git a/src/com/homelab/stages/buildRust.groovy b/src/com/homelab/stages/buildRust.groovy deleted file mode 100644 index bd7a0b1..0000000 --- a/src/com/homelab/stages/buildRust.groovy +++ /dev/null @@ -1,315 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.getDockerParams -import com.homelab.utilities.addSSHKey - - - -def buildDckr(Map config) { - env.RUSTPRIVATE = 'github.com/Homelab' - def btObj = new buTeamMapping() - def constructObj = new constructTemplate() - def dparam_obj = new getDockerParams() - def addSSHKey = new addSSHKey() - def deployArgo = config.deployArgo ?: true - - def team = btObj.get_team_initials(config.team) - def modules = config.modules ?: ['module_less'] - def repo_name = config.repo_name - def docker_repo = "${env.cicd_environment}/${team}/${repo_name.toLowerCase()}" - def tag = dparam_obj.getTag(repo_name) - def buildx = config.containsKey('buildx') ? config.buildx : true - def docker_bindings = [:] - def skip_sonar = config.skip_sonar ?: false - def version = config.dockerBuildVersion.split('-')[-1] - def repoType = config.repo_type ?: 'microservice' - docker_bindings['version'] = version - docker_bindings['base_dir'] = config.base_dir ?: false - docker_bindings['buildRegistry'] = env.buildRegistry - docker_bindings['build_packages'] = getSystemPackages(config, 'build_packages') - docker_bindings['runtime_packages'] = getSystemPackages(config, 'runtime_packages') - stage('Build docker images') { - - - // Login to docker - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr get-login-password --region ${env.region} | docker login --username AWS --password-stdin ${env.registry}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - - sh(script:'gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet') - } - } - catch (Exception e) { - env.msg = 'Error in Docker login' - env.error_msg_to_db = env.msg - log.error(env.msg + '. Error: ' + e.toString()) - currentBuild.result = env.FAILURE - throw e - } - if (config.containsKey('copy_file')) { - def recursive = config.copy_file.recursive ? ' --recursive' : '' - dir(repo_name) { - dir('copied_files') { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws s3 cp${recursive} ${config.copy_file.path} .") - } else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script:"gsutil cp${recursive} ${config.copy_file.path} .") - } - } - } - docker_bindings['copy_file'] = true - docker_bindings['copy_target'] = config.copy_file.target ?: '/app/' - docker_bindings['base_dir'] = config.base_dir ?: false - } - else { - docker_bindings['copy_file'] = false - } - - try { - dir(repo_name) { - sonar_scan(repo_name, skip_sonar, version ) - - if (repoType != 'microservice') { - log.info("Skipping Docker build for ${repoType} repo type") - return [tag, deployArgo] - } - - addSSHKey.create() - if (!fileExists('Dockerfile')) { - for (module in modules) { - def module_name = (module instanceof LinkedHashMap) ? module.keySet()[0] : module - docker_bindings['module_property'] = (module instanceof LinkedHashMap) ? module[module_name] : [ : ] - docker_bindings['module'] = module_name - docker_bindings['binary_name'] = (module == 'module_less') ? repo_name : module_name - constructObj.renderTemplate(docker_bindings, 'rust-Dockerfile', 'Dockerfile-' + module_name) - sh "cat Dockerfile-${module_name}" - module_repo = (module == 'module_less') ? docker_repo : docker_repo + '/' + module_name - try { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script:"aws ecr describe-repositories --region ${env.region} --repository-names ${module_repo} || aws ecr create-repository --region ${env.region} --repository-name ${module_repo}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - echo 'Skipping - Registry Creation in GCP.' - } - } - catch (Exception e) { - env.msg = "Error in creating ECR repository ${module_repo}" - env.error_msg_to_db = "Error in creating ECR repository ${module_repo}" - currentBuild.result = 'FAILURE' - } - if (buildx) { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "set +x && docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module_name} --push .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (env.cicd_environment != 'ftr') { - sh(script: "set +x && docker build --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module_name} . && docker push ${env.registry}/${module_repo}:${tag}") - } - else { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - sh(script: "set +x && docker build --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module_name} .") - } - } - } - else { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "set +x && docker build --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module_name} . && docker push ${env.registry}/${module_repo}:${tag}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (env.cicd_environment != 'ftr') { - sh(script: """ - set +x - # Setup buildx for ARM64 builds - docker buildx rm mybuilder || true - docker buildx create --name mybuilder --driver docker-container --bootstrap - docker buildx use mybuilder - - # Build for ARM64 - docker buildx build --platform linux/arm64 --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module_name} --push . - """) - } - else { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - sh(script: "set +x && docker build --tag ${env.registry}/${module_repo}:${tag} -f Dockerfile-${module_name} .") - } - } - } - //Remove dockerfile - sh(script: "rm -rf Dockerfile-${module_name}") - } - } - else { - sh "cat Dockerfile" - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${docker_repo}:${tag} --push .") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script: "docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${docker_repo}:${tag} --push .") - if (env.cicd_environment != 'ftr') { - sh(script: "docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${docker_repo}:${tag} --push .") - } - else { - log.info("Skipping Docker Push - ${env.cicd_environment} env") - sh(script: "docker buildx build --platform linux/arm64,linux/amd64 --tag ${env.registry}/${docker_repo}:${tag} .") - } - } - } - } - } - catch (Exception e) { - env.msg = 'Error in building DockerFile Or Pushing To ECR' - env.error_msg_to_db = env.msg - log.error(env.msg + '. Error: ' + e.toString()) - currentBuild.result = env.FAILURE - throw e - } - return [tag, deployArgo] - } -} - -// Services may need native libraries that are not part of a shared base image -// (for example, database client headers used by a crate's build script). The -// keys are language-neutral for future reuse; currently only the Rust builder -// consumes them. Restrict values to Debian package names so config data cannot -// alter the rendered Dockerfile instruction. -def getSystemPackages(Map config, String key) { - def configuredPackages = config[key] - if (configuredPackages == null) { - return [] - } - if (!(configuredPackages instanceof List)) { - throw new IllegalArgumentException("${key} must be a YAML list of system package names") - } - - def packages = configuredPackages.collect { packageName -> packageName?.toString()?.trim() } - if (packages.any { packageName -> !packageName || !(packageName ==~ /^[a-z0-9][a-z0-9+.-]*$/) }) { - throw new IllegalArgumentException("${key} contains an invalid system package name") - } - return packages.unique() -} - - -/* -Fuction to execute sonar scan - Input Arguments: - repo_name: repository name - skip_sonar: boolena parameter to skip sonar scan -*/ - -def sonar_scan(String repo_name, boolean skip_sonar , String version ) { - try { - stage('Run sonar scan') { - if (!skip_sonar) { - - withSonarQubeEnv('sonarqube-test') { - if (env.CHANGE_ID) { - - if (env.CLOUD_PROVIDER == 'AWS') { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git fetch origin ${env.CHANGE_TARGET}:refs/remotes/origin/${env.CHANGE_TARGET}" - } - sh(script: "mvn sonar:sonar -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.repository=Homelab/${repo_name} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git fetch origin ${env.CHANGE_TARGET}:refs/remotes/origin/${env.CHANGE_TARGET}" - } - - sh(script: "curl -LO https://go.dev/dl/go${go_version}.linux-amd64.tar.gz") - sh(script: "tar -xvzf go${go_version}.linux-amd64.tar.gz -C /usr/local",returnStdout: true) - sh(script:"rm -Rf go${go_version}.linux-amd64.tar.gz") - // Set Go environment variables - env.PATH = "/usr/local/go/bin:${env.PATH}" - - // Run Go mod tidy and tests - sh (script: "go mod tidy") - int testExitCode = sh(script: "go test -short -coverprofile=./cov.out ./...", returnStatus: true) - if (testExitCode != 0) { - sh(script: "echo Go tests failed, but the pipeline will continue.") - } - - // Download and extract Sonar Scanner - sh(script: "curl -LO https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip") - sh(script: "unzip -o sonar-scanner-cli-5.0.1.3006-linux.zip -d /usr/local/sonar-scanner") - sh(script: "rm -Rf sonar-scanner-cli-5.0.1.3006-linux.zip") - env.PATH = "/usr/local/sonar-scanner/sonar-scanner-5.0.1.3006-linux/bin:${env.PATH}" - - // Run Sonar Scanner - sh(script:"sonar-scanner -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.repository=Homelab/${repo_name} -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} -Dsonar.pullrequest.base=${env.CHANGE_TARGET} -Dsonar.go.coverage.reportPaths=./cov.out ") - } - } - else { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: "mvn sonar:sonar -Dsonar.branch.name=${env.BRANCH_NAME}") - } - else if (env.CLOUD_PROVIDER == 'GCP') { - - sh(script: "curl -LO https://go.dev/dl/go${go_version}.linux-amd64.tar.gz") - sh(script: "tar -xvzf go${go_version}.linux-amd64.tar.gz -C /usr/local",returnStdout: true) - sh(script:"rm -Rf go${go_version}.linux-amd64.tar.gz") - // Set Go environment variables - env.PATH = "/usr/local/go/bin:${env.PATH}" - - // Run Go mod tidy and tests - sh (script: "go mod tidy") - int testExitCode = sh(script: "go test -short -coverprofile=./cov.out ./...", returnStatus: true) - if (testExitCode != 0) { - sh(script: "echo Go tests failed, but the pipeline will continue.") - } - - // Download and extract Sonar Scanner - sh(script: "curl -LO https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip") - sh(script: "unzip -o sonar-scanner-cli-5.0.1.3006-linux.zip -d /usr/local/sonar-scanner") - sh(script: "rm -Rf sonar-scanner-cli-5.0.1.3006-linux.zip") - env.PATH = "/usr/local/sonar-scanner/sonar-scanner-5.0.1.3006-linux/bin:${env.PATH}" - - // Run Sonar Scanner - sh(script:"sonar-scanner -Dsonar.go.coverage.reportPaths=./cov.out -Dproject.settings=`pwd`/sonar-project.properties -Dsonar.branch.name=${env.BRANCH_NAME}") - - - } - } - } - - } - else { - log.info('Skipping - Sonar Scan') - } - } - stage("Quality Gate"){ - - if (skip_sonar){ - log.info("Sonar scan is skipped. Marking this stage as passed.") - } - else if (!env.CHANGE_ID){ - log.info("Skipping quality gate check on Branches. Marking this stage as passed.") - } - else{ - timeout(time: 600, unit: 'SECONDS') { - def qg = waitForQualityGate() - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE'){ - if (qg.status != 'OK') { - log.warn("Quality gate failed: ${qg.status}, but continuing pipeline execution.") - } - } - } - } - } - } - catch (Exception e) { - dir("$repo_name") { - withSonarQubeEnv('sonarqube-test') { - if (env.CLOUD_PROVIDER == 'AWS') { - sh(script: 'mvn sonar:sonar') - } - else if (env.CLOUD_PROVIDER == 'GCP') { - sh(script: "Quality Gate Failed !") - } - } - } - } -} diff --git a/src/com/homelab/stages/deployArgoCD.groovy b/src/com/homelab/stages/deployArgoCD.groovy deleted file mode 100644 index be51982..0000000 --- a/src/com/homelab/stages/deployArgoCD.groovy +++ /dev/null @@ -1,906 +0,0 @@ -package com.homelab.stages - -import com.cwctravel.hudson.plugins.extended_choice_parameter.ExtendedChoiceParameterDefinition - -import com.homelab.utilities.getYamlParameter -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.gitActions -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.constructParam -import com.homelab.utilities.getDockerParams -import com.homelab.utilities.nodePoolSelection - -def run(String repo_name, def deployment_order, def tag, def build_team, def dockerBuildVersion, String notify_channel) { - def userInput = '' - // def helm_repo_name = 'devops-helm-charts' - // def argo_repo_name = 'devops-argo-config' - - def gitObj = new gitActions() - def constructParam = new constructParam() - def yamlObj = new getYamlParameter() - def branch_name = env.BRANCH_NAME - def branch_param_map = ['(master|main|gcp-main|gcp-master|farmiso-main)': ['branch':'main', 'envrn':'prd'], - '(develop|gcp-dev)':['branch':'develop', 'envrn':'stg']] - - if (env.CHANGE_ID) { - branch_param_map = [ - '(develop|gcp-dev)':['branch':'feature', 'envrn':'ftr'], - '(master|main|gcp-main|gcp-master|farmiso-main)':['branch':'pre-prod', 'envrn':'int']] - branch_name = env.CHANGE_TARGET - } - def branch_param = branch_param_map.collectEntries { key, value -> branch_name.matches(key) ? value : [ : ] } - if (branch_param == [:]) { - branch_param = ['branch':'feature', 'envrn':'ftr'] - } - def helm_branch_name = branch_param.branch - def argo_branch_name = branch_param.branch - def envrn = branch_param.envrn - if (env.CHANGE_ID && env.CHANGE_BRANCH != "develop" && env.CHANGE_TARGET == "main" ) { - def allowedNonDevelopPrDeploymentToInt = constructParam.allowedNonDevelopPrDeploymentToIntRepos(repo_name) - if (!allowedNonDevelopPrDeploymentToInt) { - log.error("******** ONLY DEVELOP BRANCH PR ALLOWED FOR PRE-PROD ENV DEPLOYMENT ********") - return - } - } - log.info('Please provide apps to deploy') - timeout(unit: 'SECONDS', time: 300) { - userInput = wait_for_user_input(deployment_order) - } - - // change deployment order according to user input - - if (userInput == '') { - log.info('No Deployments selected. Running remaining steps.') - return - } - else if (!userInput.contains('All')) { - deployment_order = userInput.split(',') as List - } - - commit_branch_name = env.CHANGE_ID ? env.CHANGE_BRANCH : env.BRANCH_NAME - def commit_id = gitObj.fetchLatestCommitId(repo_name, commit_branch_name) - env.COMMIT_ID = commit_id - - env.SERVICES = deployment_order - //Show selected applications to deploy - log.info('Following services will be deployed:\n' + deployment_order.join('\n')) - // Clone the repo just once for all the deployments - gitObj.clone("${WORKSPACE}", "${env.helm_repo_name}", "${helm_branch_name}") - - // Clone the repo just once for all the deployments - gitObj.clone("${WORKSPACE}", "${env.argo_repo_name}", "${argo_branch_name}") - - for (deployment in deployment_order) { - try { - - def isMultizoneEnabled = constructParam.isMultizoneEnabled(deployment) - log.info(" Multi-zone enabled for ${deployment} - ${isMultizoneEnabled}") - if (isMultizoneEnabled) { - def multizone = "Multi-zone enabled for this deployable ${deployment}. Please use Ringmaster for deployment." - currentBuild.result = env.FAILURE - throw new Exception(multizone) - } - def value_binding = yamlObj.getParam("${repo_name}/deployments", "${deployment}.yaml") - constructParam.perDeploymentVars(value_binding) - // if(envrn == 'prd') { - // stage("${deployment}: Downscale Pods in Preprod Env") { - // preprod_downscale(repo_name, deployment,notify_channel) - // } - // } - stage("${deployment}: Update Argo App") { - update_argo_repo(repo_name, deployment, argo_branch_name, envrn) - } - stage('Update Argo App of apps') { - refresh_app_of_apps(envrn) - } - stage("${deployment}: Update Helm Repo") { - update_helm_repo(repo_name, deployment, tag, build_team, helm_branch_name, envrn, dockerBuildVersion, notify_channel) - } - stage("${deployment}: Refresh & Sync App in argoCD") { - refresh_and_sync(repo_name, deployment, envrn) - } - } - catch (Exception e) { - log.error(e) - currentBuild.result = env.FAILURE - } - } -} - -// def preprod_downscale(String repo_name, String deployment, String notify_channel){ -// def yamlobj = new getYamlParameter() -// def deploy_values = yamlobj.getParam("${repo_name}/deployments","${deployment}.yaml") -// def app_name = deploy_values.app_name -// def bu = deploy_values.bu -// def slack_channel = notify_channel -// def as_enabled = deploy_values.environment.'int'.as_enabled -// log.info("########################### Invoking Jenkins Job to Downscale Pods in Preprod Environment. ###########################") -// build wait: false, job: 'downscale-preprod-eks', parameters:[string(name:'app_name', value:"${app_name}"), -// string(name:'bu',value:"${bu}"), -// string(name:'as_enabled',value:"${as_enabled}"), -// string(name:'slack_channel',value:"${slack_channel}")] -// } - -def wait_for_user_input(def deployments) { - def userInput = '' - String choices = 'All,' + deployments.join(',') - int visibleItemCount = 1 + deployments.size() - def multiSelect = new ExtendedChoiceParameterDefinition('deployments', //name - 'PT_CHECKBOX', // parameter type - choices, //values - '', //projectName - '', //propertyFile - '', //groovyScript - '', //groovyScriptFile - '', //bindings - '', //groovyClasspath - '', //propertyKey - '', //defaultValue - '', //defaultPropertyFile - '', //defaultGroovyScript - '', //defaultGroovyScriptFile - '', //defaultBindings - '', //defaultGroovyClasspath - '', //defaultPropertyKey - '', //descriptionPropertyValue - '', //descriptionPropertyFile - '', //descriptionGroovyScript - '', //descriptionGroovyScriptFile - '', //descriptionBindings - '', //descriptionGroovyClasspath - '', //descriptionPropertyKey - '', //javascriptFile - '', //javascript - false, //saveJSONParameterToFile - false, //quoteValue - visibleItemCount, //visibleItemCount - 'Choose Deployments', //description - ',') //multiSelectDelimiter - - stage('wait for user input') { - try { - echo "Skipping User Input - ${env.skip_user_input}" - if (env.skip_user_input.toBoolean()) { - userInput = 'All' - } - else { - userInput = input message: 'Choose applications to deploy', ok: 'Deploy', parameters: [multiSelect] - } - return userInput - } - catch (Exception e) { - env.msg = 'Error in taking userInput' + e.toString() - env.error_msg_to_db = 'Error Taking User Input for Deployment of Applications' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - } -} - -def update_helm_repo(String repo_name, String deployment, String tag, String build_team, String helm_branch_name, String envrn, def dockerBuildVersion, String notify_channel) { - def yamlobj = new getYamlParameter() - def btObj = new buTeamMapping() - def gitObj = new gitActions() - def templateObj = new constructTemplate() - def nodePoolSelection = new nodePoolSelection() - // def helm_repo_name = 'devops-helm-charts' - - def value_yaml_file = get_value_yaml_file(dockerBuildVersion) - def value_binding1 = yamlobj.getParam("${repo_name}/deployments", "${deployment}.yaml") - if (value_binding1.containsKey('cron')) { - if (value_binding1['cron']) { - value_yaml_file = 'cron-values.yaml' - } - } - - def env_norm = ['prd':'prod', 'int':'pre-prod', 'stg':'stg', 'ftr':'feature'] - def pr_num = '' - def commit_status = 0 - - try { - // Get values from deployments/deployment.yaml - isfeatureDeployment = (envrn == 'ftr') ? true : false - value_binding1 = yamlobj.getParam("${repo_name}/deployments", "${deployment}.yaml") - def memory_request = value_binding1.environment[env.cicd_environment].memory_request - def cpu_request = value_binding1.environment[env.cicd_environment].cpu_request - def priority_v2 = value_binding1.priority_v2 - def commit_id = env.COMMIT_ID - if ( env.CLOUD_PROVIDER == 'GCP' && !value_binding1.containsKey('cron') ) { - nodeSelectorValue = nodePoolSelection.run(memory_request, cpu_request.toString(),priority_v2) - echo "Node Selector Value is ${nodeSelectorValue}" - value_binding1['nodeSelectorValue'] = nodeSelectorValue - } - def xms = '' - def xmx = '' - if (!value_binding1.containsKey('cron')) { - if (dockerBuildVersion.contains('maven') || dockerBuildVersion.contains('gradle') || dockerBuildVersion.contains('go')) { - value_binding1['activeProcessorCount'] = calculate_active_processors(cpu_request.toString()) - } - if (dockerBuildVersion.contains('maven') || dockerBuildVersion.contains('gradle')) { - def memory_limit = value_binding1.environment[env.cicd_environment].memory_limit - def deployment_args = value_binding1.environment[env.cicd_environment].deployment_args - echo "memory_limit is ${memory_limit}" - def memory_string = memory_limit - echo "performing operations on this memory string - ${memory_string}" - - def memory_value = '' - - if ( memory_string.contains('M') ) { - memory_value = memory_string.replaceAll('Mi', '') - memory_value = memory_value.replaceAll('M', '') - try { - memory_value = memory_value.toInteger() - } - catch (NumberFormatException e) { - memory_value = memory_value.toDouble() - memory_value = memory_value.toInteger() - } - } - else if ( memory_string.contains('G') ) { - memory_value = memory_string.replaceAll('Gi', '') - memory_value = memory_value.replaceAll('G', '') - try { - memory_value = memory_value.toInteger() * 1024 - } - catch (NumberFormatException e) { - memory_value = memory_value.toDouble() * 1024 - memory_value = memory_value.toInteger() - } - } - - memory_value = memory_value * 0.5 - memory_value = memory_value.toInteger() - xms = "${memory_value}M" - xmx = "${memory_value}M" - - echo "xms and xmx from memory value - ${memory_value} are - ${xms} ${xmx}" - for (arg in deployment_args) { - if (arg.contains('Xms')) { - xms = arg.replaceAll('.*Xms', '') - echo "xms from deployment_args - ${xms}" - } - if (arg.contains('Xmx')) { - xmx = arg.replaceAll('.*Xmx', '') - echo "xmx from deployment_args - ${xmx}" - } - } - echo "final xms and xmx - ${xms} ${xmx}" - } - } - - value_binding1['dockerBuildVersion'] = dockerBuildVersion == 'python-3.10.12' ? 'python-3.7' : dockerBuildVersion - deployEnv = !value_binding1['environment'].containsKey(envrn) && isfeatureDeployment ? 'stg' : envrn - Map value_binding = value_binding1['environment'].collectEntries { key, value -> deployEnv.matches(key) ? value : [ : ] } - value_binding1.remove('environment') - value_binding1.putAll(value_binding) - - value_binding1['repo_name'] = repo_name - value_binding1['environment'] = envrn - value_binding1['environment_norm'] = env_norm[envrn] - value_binding1['tag'] = tag - value_binding1['repo_name'] = repo_name - value_binding1['build_team'] = build_team - value_binding1['commit_id'] = commit_id - - def buini = btObj.get_bu_initials(value_binding1.bu) - def bu = btObj.get_bu_initials(value_binding1.bu) - def teamini = btObj.get_team_initials(value_binding1.team) - def team = btObj.get_team_initials(value_binding1.team) - def app_name = value_binding1.app_name - def app_branch = app_name + '-' + helm_branch_name - - def app_helm_repo = "${env.helm_repo_name}/${env.helmChartsPath}/${buini}/${teamini}/${app_name}" - def ingress_val = value_binding1.ingress_val ?: (env.CHANGE_ID) ? "pr-${CHANGE_ID}" : "${BRANCH_NAME}" - - sh "chmod -R 777 ${app_helm_repo}" - - sh "yq . ${app_helm_repo}/values_properties.yaml -y > a.yaml;mv a.yaml ${app_helm_repo}/values_properties.yaml" - sh "cat ${app_helm_repo}/values_properties.yaml" - if (isfeatureDeployment) { - env.ingress_val = ingress_val - value_binding1['env_ns'] = ingress_val - value_binding1['vault_env'] = value_binding1['create_vault_path'] ? ingress_val : 'stg' - sh(returnStdout: true, script: """ - mkdir -p ${app_helm_repo}/${ingress_val} - sed "s/INGRESS_PR_NUMBER/${ingress_val}/g" ${app_helm_repo}/values_properties.yaml > ${app_helm_repo}/${ingress_val}/values_properties.yaml - """) - value_binding2 = yamlobj.getParam("${app_helm_repo}/${ingress_val}", 'values_properties.yaml') - app_branch = ingress_val + '-' + app_branch - } - else { - value_binding1['vault_env'] = envrn - value_binding1['env_ns'] = envrn - value_binding2 = yamlobj.getParam("${app_helm_repo}", 'values_properties.yaml') - echo '722 Printing Value Binding 2' - print value_binding2 - } - - value_binding1.putAll(value_binding2) - value_binding1['bu_norm'] = value_binding1['bu'] - value_binding1['team_norm'] = value_binding1['team'] - value_binding1['bu'] = buini - value_binding1['team'] = teamini - - - def serviceTypes = ["httpstateless", "consumer", "producer", "scheduler", "worker", "grpc", "web", "websocket", "cache", "database"] - //validate service type params - if (value_binding1['service_type']){ - // Validating the parameter type - if(!(value_binding1['service_type'] instanceof List) || value_binding1['service_type'].isEmpty()){ - env.msg = 'You have not specified service_type correctly. Exiting the pipeline.' - log.error(env.msg) - sh 'exit 1' - } - // Check if value_binding1['service_type'] contains any service type not in servicesType - def invalidServiceTypes = value_binding1['service_type'].findAll { !serviceTypes.contains(it) } - if (!invalidServiceTypes.isEmpty()) { - log.error("Invalid service type(s): ${invalidServiceTypes.join(',')}. Allowed service_types are: ${serviceTypes.join(',')}") - sh 'exit 1' - } - value_binding1['service_type_norm'] = value_binding1['service_type'].join(',') - } - else{ - value_binding1['service_type_norm']='' - } - - // Enable backward compatibility for missing keys - enable_backward_compatibility(value_binding1) - value_binding1['prismsdk_environment'] = 'PRODUCTION' // THIS KEY WILL CHANGE ONCE 3RD CONFIRM TO STANDERIZE - if (envrn == 'stg'){ - value_binding1['otel_enabled'] = true - value_binding1['prismsdk_environment'] = 'SANDBOX' // THIS KEY WILL CHANGE ONCE 3RD CONFIRM TO STANDERIZE - } - // Override hot fix and notify_channel - value_binding1['canary']['skipAnalysis'] = (env.hot_fix) ? true : value_binding1['canary']['skipAnalysis'] - value_binding1['canary']['slackChannel'] = notify_channel - value_binding1['xms'] = xms - value_binding1['xmx'] = xmx - value_binding1['CLOUD_PROVIDER'] = env.CLOUD_PROVIDER - def appConfig = value_binding1['appConfigEnabled'] - if (appConfig) { - def configModule = (value_binding1["module"] == 'module_less') ? repo_name : value_binding1["module"] - // read the static config from configs directory - def value_binding3 = yamlobj.getParamAsString("${repo_name}/configs/${configModule}", "application-${envrn}.yml") - // to update in values.yaml - value_binding1['staticAppConfigData'] = value_binding3 - //read the dynamic config from config directory - def value_binding4 = yamlobj.getParamAsString("${repo_name}/configs/${configModule}", "application-dyn-${envrn}.yml") - // to update in values.yaml - value_binding1['dynamicAppConfigData'] = value_binding4 - echo 'printing the values_binding3 and values_binding4' - print value_binding3 - print value_binding4 - value_binding1['vault_env'] = value_binding1['vault_env'] + '-cac' - - } - - - gitObj.preDeleteBranch(env.helm_repo_name, helm_branch_name, app_branch) - gitObj.branchCheckOut(env.helm_repo_name, app_branch) - - echo "Helm Step - Value Binding 1 - ${value_binding1}" - if (isfeatureDeployment) { - echo 'It is feature deployment' - templateObj.renderTemplate(value_binding1, value_yaml_file, "${app_helm_repo}/${ingress_val}/values.yaml") - echo 'Before YAML Linting' - sh "cat ${app_helm_repo}/${ingress_val}/values.yaml" - sh "yq . ${app_helm_repo}/${ingress_val}/values.yaml -y > a.yaml;mv a.yaml ${app_helm_repo}/${ingress_val}/values.yaml" - echo 'After YAML Linting' - sh "cat ${app_helm_repo}/${ingress_val}/values.yaml" - gitObj.add(env.helm_repo_name, "${env.helmChartsPath}/${buini}/${teamini}/${app_name}/${ingress_val}/values.yaml") - } - else { - echo 'Not a feature deployment' - templateObj.renderTemplate(value_binding1, value_yaml_file, "${app_helm_repo}/values.yaml") - echo 'Before YAML Linting' - sh "cat ${app_helm_repo}/values.yaml" - - // Perform YAML linting - sh "yq . ${app_helm_repo}/values.yaml -y > a.yaml; mv a.yaml ${app_helm_repo}/values.yaml" - echo 'After YAML Linting' - sh "cat ${app_helm_repo}/values.yaml" - - // creating a map from final values.yaml to check canary enforcement conditions - def valuesMap = yamlobj.getParam("${app_helm_repo}", "values.yaml") - - // Canary enforcement for sp0 services - // Also will have to check for cron, worker and scheduler services - def enforceCanary = false - if (!app_helm_repo.contains("cron") && !app_helm_repo.contains("worker") && !app_helm_repo.contains("scheduler") && !app_helm_repo.contains("consumer") && (valuesMap["labels"]["priority_v2"] == "sp0" || valuesMap["labels"]["priority_v2"] == "up0") && !value_binding1["addHeadless"] && !value_binding1['dockerBuildVersion'].contains("node") && envrn == "prd") { - enforceCanary = true - } - - def proceed = false - def deploymentFailureErrorMessage - if (enforceCanary) { - if (valuesMap["canary"]["enabled"] == true) { - if (valuesMap["canary"]["skipAnalysis"] == false) { - if (valuesMap["canary"]["enableManualPromotion"] == true) { - proceed = true - } else { - error("Error: Update the enableManualPromotion parameter") - } - } else { - error("Error: Update the skipAnalysis parameter") - - } - } else { - error("Error: Enable canary and retry") - } - } else { - proceed = true - } - - // Check if priority labels and environment conditions require critical dependency check - def criticalPriorities = ["sp0", "up0", "cp0", "sp1", "up1", "cp1"] - def priority = valuesMap["labels"]["priority_v2"] - if (criticalPriorities.contains(priority) && (envrn == "prd")) { - isDependabotCritcal = dependabotCriticalCheck(repo_name) - if (isDependabotCritcal) { - error("Error: Critical vulnerabilities found in repo: " + repo_name + " \nPlease resolve the alerts marked with CRITICAL here and retry: https://github.com/Homelab/" + repo_name + "/security/dependabot and Retry.") - } - } - - def param = new constructParam() - log.info("checking if appConfig is enabled") - def isAppConfigDisabled = param.appConfigDisabledForbidden(appConfig, repo_name, envrn, dockerBuildVersion) - if (isAppConfigDisabled){ - error("Error: appConfig is disabled , onboard your application with config-as-code changes") - } - - sh "cat ${app_helm_repo}/values.yaml" - gitObj.add(env.helm_repo_name, "${env.helmChartsPath}/${buini}/${teamini}/${app_name}/values.yaml") - - } - - - commit_status = gitObj.codeCommit(env.helm_repo_name, app_branch, 'Generating values yaml file') - if (commit_status == 0) { - gitObj.codePush(env.helm_repo_name, app_branch) - pr_num = gitObj.createPR(app_name, env.helm_repo_name, helm_branch_name, app_branch, 'Merge helm values file') - gitObj.mergePR(env.helm_repo_name, pr_num, app_branch) - gitObj.deleteBranch(env.helm_repo_name, helm_branch_name, app_branch) - } - } - catch (FileNotFoundException e) { - env.msg = 'Error Updating Helm Repo ' + e.toString() - env.error_msg_to_db = 'File Not Found' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - catch (Exception e) { - env.msg = 'Error Updating Helm Repo ' + e.toString() - env.error_msg_to_db += 'Error Updating in Helm repo. Git error message - ' + env.error_part_msg_to_db - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -def refresh_and_sync(String repo_name, String deployment, String envrn) { - def yamlobj = new getYamlParameter() - def deploy_values = yamlobj.getParam("${repo_name}/deployments", "${deployment}.yaml") - def app_name = deploy_values.app_name - // def argoUrl = (env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'main' || env.CHANGE_TARGET == 'master' || env.CHANGE_TARGET == 'main') ? 'prod-ops-argocd.homelab.com' : 'stg-dev-argocd.homelabtest.in' - def argoEnv = (envrn == 'ftr') ? env.ingress_val : envrn - - log.info('########################### Pulling latest changes in ArgoCD. ###########################') - withCredentials([usernamePassword(credentialsId: env.argoCreds, passwordVariable: 'ARGO_PASSWORD', usernameVariable: 'ARGO_USERNAME')]) { - try { - sh """ - set +x - argocd login ${env.argoURL}:443 --username ${ARGO_USERNAME} --password ${ARGO_PASSWORD} --grpc-web - argocd app get --hard-refresh ${argoEnv}-${app_name} --grpc-web - """ - } - catch (Exception e) { //added try catch block here - env.msg = "Error in hard refresh of app ${app_name} full error: ${e}" - env.error_msg_to_db += "Error in hard refresh of app ${app_name};" - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } - log.info('########################### Syncing latest changes in ArgoCD. ###########################') - return_value = sh(returnStatus: true, script: "argocd app sync ${argoEnv}-${app_name} --grpc-web --http-retry-max 3 --retry-backoff-duration 1m") as Integer - if (return_value == 0) { - log.info('App synced succesfully.') - } else { - env.msg = 'App sync failed. Please check in ArgoCD UI.' - env.error_msg_to_db += "Error Argo App sync failed ${app_name};" - log.error(env.msg) - error "${env.msg}" - } - } -} - -def refresh_app_of_apps(String envrn) { - try { - appofapps = env.argoIncubator - log.info('########################### Pulling latest changes in ArgoCD for App of Apps. ###########################') - withCredentials([usernamePassword(credentialsId: env.argoCreds, passwordVariable: 'ARGO_PASSWORD', usernameVariable: 'ARGO_USERNAME')]) { - sh """ - set +x - argocd login ${env.argoURL}:443 --username ${ARGO_USERNAME} --password ${ARGO_PASSWORD} --grpc-web - argocd app sync ${appofapps} --grpc-web --http-retry-max 3 --retry-backoff-duration 1m || true - """ - } - } - catch (Exception e) { - env.msg = 'Error in App Sync' + e.toString() - env.error_msg_to_db += 'Error syncing app of app;' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} - -def update_argo_repo(String repo_name, String deployment, String argoBranch, String envrn) { - def yamlobj = new getYamlParameter() - def btObj = new buTeamMapping() - def gitObj = new gitActions() - def templateObj = new constructTemplate() - // def argo_repo_name = 'devops-argo-config' - def pr_num = '' - def commit_status = 0 - def commit_id = env.COMMIT_ID - - value_binding1 = yamlobj.getParam("${repo_name}/deployments", "${deployment}.yaml") - value_binding1['CLOUD_PROVIDER'] = env.CLOUD_PROVIDER - isfeatureDeployment = (envrn == 'ftr') ? true : false - deployEnv = !value_binding1['environment'].containsKey(envrn) && isfeatureDeployment ? 'stg' : envrn - Map value_binding = value_binding1['environment'].collectEntries { key, value -> deployEnv.matches(key) ? value : [ : ] } - value_binding1.remove('environment') - value_binding1.putAll(value_binding) - value_binding1['environment'] = envrn - - def buini = btObj.get_bu_initials(value_binding1.bu) - def bu = value_binding1.bu - - def teamini = btObj.get_team_initials(value_binding1.team) - def team = value_binding1.team - def app_name = value_binding1.app_name - def filename = "${teamini}-${app_name}.yaml" - def helm_values_path = "${env.helmChartsPath}/${buini}/${teamini}/${app_name}" - def app_branch = app_name + '-' + argoBranch - def ingress_val = value_binding1.ingress_val ?: (env.CHANGE_ID) ? "pr-${CHANGE_ID}" : "${BRANCH_NAME}" - value_binding1['helm_version'] = value_binding1.helm_version ?: env.defaultHelmChartVersion - - if (isfeatureDeployment) { - value_binding1['env_ns'] = ingress_val - value_binding1['helm_values_path'] = "${env.helmChartsPath}/${buini}/${teamini}/${app_name}/${ingress_val}" - filename = ingress_val + '-' + "${app_name}.yaml" - app_branch = ingress_val + '-' + app_branch - } - else { - value_binding1['env_ns'] = envrn - value_binding1['helm_values_path'] = "${env.helmChartsPath}/${buini}/${teamini}/${app_name}" - filename = "${teamini}-${app_name}.yaml" - } - - value_binding1['server'] = env.clusterName - value_binding1['clusterName'] = env.clusterName - - value_binding1['branch_name'] = argoBranch - value_binding1['buini'] = buini - value_binding1['teamini'] = teamini - - value_binding1['priority_v2'] = value_binding1.priority_v2 ?: 'cp3' - value_binding1['primary_owner'] = value_binding1.primary_owner != null ? value_binding1.primary_owner.split('@')[0] : value_binding1.team - value_binding1['secondary_owner'] = value_binding1.secondary_owner != null ? value_binding1.secondary_owner.split('@')[0] : value_binding1.team - value_binding1['argoAppNS'] = env.argoAppNS - value_binding1['commit_id'] = commit_id - log.info(commit_id) - gitObj.preDeleteBranch(env.argo_repo_name, argoBranch, app_branch) - gitObj.branchCheckOut(env.argo_repo_name, app_branch) - echo "Argo Step - Value Binding 1 - ${value_binding1}" - - dir(env.argo_repo_name) { - def dirExists = sh(script: "cat ${env.argoAppsPath}/${filename}", returnStatus: true) - if ( dirExists != 0 ) { - sh "mkdir -p ${env.argoAppsPath}" - sh "touch ${env.argoAppsPath}/${filename}" - } - sh "chmod -R 777 ${env.argoAppsPath}/${filename}" - templateObj.renderTemplate(value_binding1, 'argoApp.yaml', "${env.argoAppsPath}/${filename}") - sh "yq . ${env.argoAppsPath}/${filename} -y > a.yaml;mv a.yaml ${env.argoAppsPath}/${filename}" - sh "cat ${env.argoAppsPath}/${filename}" - } - - gitObj.add(env.argo_repo_name, "${env.argoAppsPath}/${filename}") - commit_status = gitObj.codeCommit(env.argo_repo_name, app_branch, "onboarding ${deployment} app to ARGO") - if (commit_status == 0) { - gitObj.codePush(env.argo_repo_name, app_branch) - pr_num = gitObj.createPR(app_name, env.argo_repo_name, argoBranch, app_branch, 'Merge argo application configuration') - gitObj.mergePR(env.argo_repo_name, pr_num, app_branch) - gitObj.deleteBranch(env.argo_repo_name, argoBranch, app_branch) - } -} - -def calculate_active_processors(String cpu_request) { - // If lowercase m is present - if (cpu_request.contains('m')) { - // Remove m - cpu_request = cpu_request.replaceAll('m', '') - - // If this fails means invalid input is given with m, so we can let the pipeline fail here - // Convert to double, divide by thousand to get another double, round it off and then convert to integer. - return Math.ceil(cpu_request.toDouble() / 1000).toInteger() - } - - return Math.ceil(cpu_request.toDouble()).toInteger() -} - -def enable_backward_compatibility(def bindings) { - // def primaryEmailInitial = bindings.primary_owner.tokenize( '@' )[0]: null - // def secondaryEmailInitial = bindings.secondary_owner.tokenize( '@' )[0]: null - def dparam_obj = new getDockerParams() - bindings['registry'] = env.registry - def canary_default = [ - 'progressDeadlineSeconds': 300, - 'analysisInterval': '120s', - 'analysisThreshold': 5, - 'analysisMaxWeight': 5, - 'analysisStepWeight': 5, - 'analysisMetrics':[ - 'thresholdRangeMin': 0.99, - 'interval': '1m'], - 'skipAnalysis': false - ] - def statefulset_default = [ - 'updateStrategy': 'RollingUpdate', - 'volumeType': 'dynamic', - 'dynamicVolume':[ - 'accessMode': 'ReadWriteMany', - 'mountPath': '/opt/data', - 'size': '5Gi', - 'storageClass': ''], - 'staticVolume':[ - 'accessMode': 'ReadWriteMany', - 'size': '5Gi', - 'mountPath': '/opt/data', - 'storageClass': '', - 'volumeHandle': '', - 'csiDriver': ''] - ] - bindings['hostAliases'] = bindings.hostAliases ?: false - if (env.CLOUD_PROVIDER == 'AWS') { - bindings['nodeSelector'] = bindings.nodeSelector ?: (env.cicd_environment == 'stg' || env.cicd_environment == 'ftr') ? bindings.bu : ( env.cicd_environment == 'int' ? bindings.bu + '-int' : bindings.team ) - bindings['nodeSelectorValue'] = 'dedicated' - } - else if (env.CLOUD_PROVIDER == 'GCP') { - if (env.cicd_environment == 'int') { - bindings['nodeSelector'] = bindings.nodeSelector ?: 'cloud.google.com/compute-class' - } else { - bindings['nodeSelector'] = bindings.nodeSelector ?: 'dedicated' - } - bindings['nodeSelectorValue'] = bindings.nodeSelectorValue ?: 'megatetra' - } - bindings['triggers'] = bindings.triggers ?: false - bindings['host'] = bindings.host ?: false - bindings['hosts'] = bindings.hosts ?: false - bindings['grpc_host'] = bindings.grpc_host ?: false - bindings['grpc_hosts'] = bindings.grpc_hosts ?: false - bindings['serviceAccount'] = bindings.serviceAccount ?: false - bindings['ingress_annotations'] = bindings.ingress_annotations ?: '' - bindings['canary'] = bindings.canary ?: canary_default - bindings['minCanaryReplicas'] = bindings.canary?.minCanaryReplicas ?: bindings.minCanaryReplicas ?: bindings.as_min - bindings['maxCanaryReplicas'] = bindings.canary?.maxCanaryReplicas ?: bindings.maxCanaryReplicas ?: bindings.as_max - bindings['enableManualPromotion'] = bindings.canary?.enableManualPromotion ?: bindings.enableManualPromotion ?: false - bindings['cpu_limit'] = bindings.cpu_limit ?: bindings.cpu_request - bindings['slowStartWindow'] = bindings.slowStartWindow ?: false - bindings['slowStartAggression'] = bindings.slowStartAggression ?: '1' - bindings['slowStartMinPercent'] = bindings.slowStartMinPercent ?: '10' - bindings['lifecycle'] = bindings.lifecycle ?: false - bindings['maxSurge'] = bindings.maxSurge ?: '50' - bindings['as_down_pod_count'] = bindings.as_down_pod_count ?: '2' - bindings['as_up_pod_count'] = bindings.as_up_pod_count ?: '2' - bindings['as_up_pod_percentage'] = bindings.as_up_pod_percentage ?: '10' - bindings['createContourGateway'] = bindings.createContourGateway ?: false - bindings['service_annotations'] = bindings.service_annotations ?: false - bindings['pod_annotations'] = bindings.pod_annotations ?: false - bindings['kind'] = bindings.kind ?: 'deployment' - bindings['statefulset'] = bindings.statefulset ?: statefulset_default - bindings['contourResponseTimeout'] = bindings.contourResponseTimeout ?: false - bindings['pdbMinAvailable'] = bindings.pdbMinAvailable ?: '' - bindings['pdbMaxUnavailable'] = bindings.pdbMaxUnavailable ?: '10%' - - // Just to keep compatibility for services which still use grpc_port - // If someone has supplied primary_port, then it is used - // Else we check for grpc_port, and that is used - // If none of the above is supplied, then app_port is used just like normal flow - bindings['primary_port'] = bindings.primary_port ?: bindings.grpc_port ?: bindings.app_port - bindings['grpc_port'] = bindings.grpc_port ?: false - // bindings['xmx'] = bindings.xmx ?: '50.0' - // bindings['xms'] = bindings.xms ?: '50.0' - bindings['enableWebsocket'] = bindings.enableWebsocket ?: false - bindings['external_secrets_annotations'] = bindings.external_secrets_annotations ?: '' - bindings['liveness_failure_threshold'] = bindings.liveness_failure_threshold ?: '5' - bindings['liveness_period_seconds'] = bindings.liveness_period_seconds ?: bindings.team_norm == 'ml-platform' ? '5' : '10' - bindings['liveness_success_threshold'] = bindings.liveness_success_threshold ?: '1' - bindings['liveness_timeout_seconds'] = bindings.liveness_timeout_seconds ?: '2' - bindings['readiness_failure_threshold'] = bindings.readiness_failure_threshold ?: '5' - bindings['readiness_period_seconds'] = bindings.liveness_period_seconds ?: bindings.team_norm == 'ml-platform' ? '5' : '10' - bindings['readiness_success_threshold'] = bindings.liveness_success_threshold ?: '1' - bindings['readiness_timeout_seconds'] = bindings.liveness_timeout_seconds ?: '2' - bindings['addon_ports'] = bindings.addon_ports ?: false - - validateRequiredMetadata(bindings) - - bindings['priority_v2'] = bindings.priority_v2 ?: 'cp3' - bindings['primary_owner'] = bindings.primary_owner != null ? bindings.primary_owner.split('@')[0] : bindings.team_norm - bindings['secondary_owner'] = bindings.secondary_owner != null ? bindings.secondary_owner.split('@')[0] : bindings.team_norm - if (env.CLOUD_PROVIDER == 'AWS') { - if (bindings.otel_enabled == null) { - bindings['otel_enabled'] = (bindings.priority_v2 == 'cp1' || bindings.priority_v2 == 'up1') ? true : false - } - else { - bindings['otel_enabled'] = bindings.otel_enabled - } - } - else if (env.CLOUD_PROVIDER == 'GCP') { - echo "Checking for otel value - ${bindings.otel_enabled}" - if (bindings.otel_enabled == null) { - bindings['otel_enabled'] = true - } - else { - bindings['otel_enabled'] = bindings.otel_enabled - } - - // Setting metrics mode -> avaliable options: telegraf | otel | dual - echo "Setting metrics mode - ${bindings.metrics_mode}" - // Setting default values - bindings['telegraf_metrics'] = false - bindings['otel_metrics'] = false - bindings['metrics_mode'] = bindings.metrics_mode?.toLowerCase() - def samplerArg = bindings.otel_traces_sampler_arg - def isValidSamplerArg = false - if (samplerArg != null && (samplerArg instanceof Float || samplerArg instanceof Double)) { - if (samplerArg >= 0.0 && samplerArg <= 1.0) { - isValidSamplerArg = true - } - } - bindings['otel_traces_sampler_arg'] = isValidSamplerArg ? samplerArg : '0.1' - log.info("1 - otel_traces_sampler_arg - ${bindings.otel_traces_sampler_arg}") - log.info("2 - ${bindings['otel_traces_sampler_arg']}") - // Enabling metrics based on mode - if (bindings.metrics_mode == 'telegraf') { - bindings['telegraf_metrics'] = true - } - else if (bindings.metrics_mode == 'otel') { - bindings['otel_metrics'] = true - } - else if (bindings.metrics_mode == 'dual') { - bindings['telegraf_metrics'] = true - bindings['otel_metrics'] = true - } - else { - bindings['metrics_mode'] = 'telegraf' - bindings['telegraf_metrics'] = true - } - } - bindings['command'] = bindings.command - if (bindings.command == null) { - bindings['command'] = get_default_command(bindings['dockerBuildVersion']) - } - bindings['as_down_stable_window'] = bindings.as_down_stable_window ?: '1800' - bindings['podDistributionSkew'] = bindings.podDistributionSkew ?: false - bindings["appConfigEnabled"] = bindings.appConfigEnabled ?: false - bindings["addHeadless"] = bindings.addHeadless?: false -} - -def get_value_yaml_file(def dockerBuildVersion) { - switch (dockerBuildVersion) { - case ~/^maven-.*/: return 'values.yaml' - case ~/^node-.*/: return 'node-values.yaml' - case ~/^python-.*/: return 'python-values.yaml' - case ~/^go.*/: return 'go-values.yaml' - case 'php': return 'php-values.yaml' - case 'gradle': return 'values.yaml' - } -} - -def get_default_command(def dockerBuildVersion) { - switch (dockerBuildVersion) { - case ~/^maven-.*/: return 'java' - case ~/^node-.*/: return 'pm2-runtime' - case ~/^go.*/: return '/app/server' - case 'php': return 'apache2-foreground' - case 'gradle': return 'java' - default: return null - } -} - -def validateRequiredMetadata(def bindings) { - def requiredParams = ['primary_owner', 'secondary_owner', 'priority_v2', 'service_type'] - def serviceOwners = ['primary_owner', 'secondary_owner'] - // Validate if required parameters are present - for (param in requiredParams) { - if (bindings[param] == null || bindings[param] == '') { - env.msg = 'You have not supplied ' + param + '. Exiting the pipeline.' - log.error(env.msg) - sh 'exit 1' - } - } - - // Validate if the owners are valid or not - if (env.cicd_environment == 'dev' || env.cicd_environment == 'ftr' || env.cicd_environment == 'stg') { - return - } - - for (param in serviceOwners) { - final String owner = bindings[param] - final String url = "https://pulse.homelabgcp.in/api/anonymous-User/userexist?email=${owner}" - final def(String response, String code) = sh(returnStdout: true, script: """ - set +x - curl -s -X GET -w '\n%{response_code}' $url - set -x - """).trim().tokenize('\n') - - if (code != "200") { - // Let's not break the pipeline in case the API fails - log.info("Received ${code} code from Pulse while checking for user. Skipping the checks further and letting the pipeline proceed.") - } else { - // Check if the user exists - def jqCommand = "echo '${response}' | jq -r '.exists'" - def userExists = sh(returnStdout: true, script: """ - set +x - ${jqCommand} - set -x - """).trim() - if (userExists != "true") { - env.msg = "Invalid value provided in ${param}. Check if the user ${owner} exists" - log.error(env.msg) - sh 'exit 1' - } - } - } -} - -def dependabotCriticalCheck(def repoName) { - def repo = repoName - - def alerts - withCredentials([usernamePassword(credentialsId: "${env.GITHUB_CRED}", usernameVariable:'user', passwordVariable: 'token')]) { - validateDependabot = httpRequest httpMode: 'GET', - customHeaders: [ - [name: 'Accept', value: 'application/vnd.github+json'], - [maskValue: true, name: 'Authorization', value: 'Bearer ' + token] - ], - url: "https://api.github.com/repos/homelab/${repo}/dependabot/alerts?state=open&per_page=100", - validResponseCodes: '200', - timeout: 10 - alerts = readJSON(text: validateDependabot.content) - - } - - // if (!alerts) { - // println "Failed to fetch alerts for ${repo}." - // return false - // } - - // Parse the JSON response - if (!alerts) { - println "No alerts found for ${repo}." - return false - } - - def criticalCount = 0 - alerts.each { alert -> - def severity = alert?.security_vulnerability?.severity - - if (severity == "critical") { - criticalCount++ - } - - } - - if (criticalCount > 0) { - println "Critical vulnerabilities found in ${repo}: ${criticalCount}" - return true // Critical vulnerabilities found - } else { - println "No critical vulnerabilities in ${repo}." - return false // No critical vulnerabilities found - } -} diff --git a/src/com/homelab/stages/deployJar.groovy b/src/com/homelab/stages/deployJar.groovy deleted file mode 100644 index 0a9d254..0000000 --- a/src/com/homelab/stages/deployJar.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.homelab.stages - -def run(Map param){ - //get the jar for deployment, get it from the params module - //run the ansible playbook - //create the inventory file with given ip in parameters - def server_ip = param.run_automation.server_ip - def app_name = param.run_automation.app_name - def healthcheck_api = param.run_automation.healthcheck_api - def app_port = param.run_automation.app_port - def repo_name = param.repo_name - - deploy(server_ip,repo_name,app_name,healthcheck_api,app_port) -} - -def deploy(String server_ip, String repo_name, String app_name, String healthcheck_api, String app_port){ - try{ - stage('Deploying JAR'){ - sh "echo '$server_ip' > host_file.txt" - echo "inventory created" - def playbook_content = libraryResource 'com/homelab/deployJar.yaml' - writeFile file:"deployJar.yaml", text: playbook_content - sh(returnStatus: true, script: "ansible-playbook -i host_file.txt -u 'ubuntu' -e 'env=stage' -e 'app_name=${app_name}' -e 'repo_name=${repo_name}' -e 'healthcheck_api=${healthcheck_api}' -e 'app_port=${app_port}' deployJar.yaml -v") - } - } - catch( Exception e) { - env.msg = "Error while deploying JAR. Please check console output for more details." - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} diff --git a/src/com/homelab/stages/deployRingmaster.groovy b/src/com/homelab/stages/deployRingmaster.groovy deleted file mode 100644 index 7d342ef..0000000 --- a/src/com/homelab/stages/deployRingmaster.groovy +++ /dev/null @@ -1,144 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.getYamlParameter -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatterBuilder - -def run(String repo_name, def deployment_order, def tag, def build_team, def dockerBuildVersion, String notify_channel){ - def yamlobj = new getYamlParameter() - def applicationNames = [] - def jobStatusMap=["SUCCESS": "BUILD_STATUS_COMPLETED","FAILURE": "BUILD_STATUS_FAILED","UNSTABLE":"BUILD_STATUS_FAILED"] - def build_user = currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId() - - for (deployment in deployment_order){ - def value_binding = yamlobj.getParam("${repo_name}/deployments","${deployment}.yaml") - applicationNames.add(value_binding['app_name']) - } - def end_time = getDateTime() - def working_env = (env.CLOUD_PROVIDER == "GCP")? "gcp_${env.cicd_environment}" : env.cicd_environment - - def jsonMap = [:] - jsonMap["hot_fix"] = env.hot_fix ? true : false - jsonMap["job_name"] = env.JOB_NAME ? env.JOB_NAME.split('/')[0] : "unknown" - jsonMap["build_no"] = env.BUILD_NUMBER - jsonMap["image"] = tag - jsonMap["applications"] = applicationNames - jsonMap["job_status"] = jobStatusMap[currentBuild?.currentResult] - jsonMap["err_msg"] = env.error_msg_to_db - jsonMap["end_time"] = end_time - jsonMap["email"] = currentBuild?.rawBuild?.getCause(Cause.UserIdCause)?.getUserId() - jsonMap["start_time"] = env.STARTTIME - jsonMap["build_team"] = build_team - jsonMap["docker_build_version"] = dockerBuildVersion - jsonMap["notify_channel"] = notify_channel - jsonMap["branch_name"] = env.CHANGE_ID ? env.CHANGE_BRANCH : env.BRANCH_NAME - jsonMap["commit_id"] = env.commit_id - jsonMap["deploy_argo"] = env.deployArgo != null ? env.deployArgo.toBoolean() : false - jsonMap["pr_number"] = env.CHANGE_ID ? env.CHANGE_ID.toInteger() : 0 - - final String baseUrl - - switch (env.cicd_environment) { - case 'prd': - case 'int': - baseUrl = 'https://ringmaster-api.homelabgcp.in/api/v1/key/cicd/cd/update' - break - default: - baseUrl = 'https://ringmaster-api.admin.homelabgcp.in/api/v1/key/cicd/cd/update' - } - final String url = "${baseUrl}?workingEnv=${working_env}" - final String header = "Content-Type: application/json" - final String jsonData = writeJSON returnText: true, json: jsonMap - - - if (build_user == "ringmaster-bot"){ - callApi(url, header, jsonData) - } else{ - def newCICD_Payload = [:] - newCICD_Payload["repo_name"] = repo_name - newCICD_Payload["source_branch"] = env.CHANGE_ID ? env.CHANGE_BRANCH : env.BRANCH_NAME - newCICD_Payload["pull_request_number"] = env.CHANGE_ID ? env.CHANGE_ID.toInteger() : 0 - newCICD_Payload["env"] = env.cicd_environment - newCICD_Payload["job_name"] = env.JOB_NAME ? env.JOB_NAME.split('/')[0] : "UNKNOWN" - newCICD_Payload["sub_job_name"] = env.JOB_NAME ? env.JOB_NAME.split('/')[1] : "UNKNOWN" - newCICD_Payload["build_number"] = env.BUILD_NUMBER.toInteger() - newCICD_Payload["image_tag"] = tag - newCICD_Payload["build_detailed_error"] = env.error_msg_to_db - switch (env.cicd_environment) { - case 'prd': - case 'int': - cicdBaseUrl = 'http://turbo-turtle.homelabgcp.in' - break - default: - cicdBaseUrl = 'http://turbo-turtle.admin.homelabgcp.in' - } - final String newCICD_JSON = writeJSON returnText: true, json: newCICD_Payload - //log.info("New CICD JSON - ${newCICD_JSON}") - final String newCICD_URL = cicdBaseUrl + "/api/v1/ci/jenkins/callback" - final String newCICD_Header = "Content-Type: application/json" - // Use a temporary file to store the JSON payload - // This avoids shell quoting issues completely - final String jsonFilePath = "cicd_payload_${env.BUILD_NUMBER}_${System.currentTimeMillis()}.json" - - try { - // 1. Write the JSON payload to a temporary file - // The writeJSON step ensures the content is valid JSON, escaping internal characters - writeFile(file: jsonFilePath, text: writeJSON(returnText: true, json: newCICD_Payload)) - final String newCICD_JSON_log = readFile(file: jsonFilePath) - log.info("New CICD JSON (from file) - ${newCICD_JSON_log.take(500)}...") // Log a snippet - final def(String response, String code) = sh( - returnStdout: true, - script: """ - curl -s -X POST \\ - -H '$newCICD_Header' \\ - -w '\\n%{response_code}' \\ - $newCICD_URL \\ - -d @$jsonFilePath - """ - ).trim().tokenize("\n") - - if (code != "200") { - log.error("CICD Application API call failed with error code - ${code}, response - ${response}") - } - } catch (Exception e) { - log.error("CICD Application API call failed - " + "Error: " + e.toString()) - throw e - } finally { - sh(script: "rm -f ${jsonFilePath}", returnStatus: true) - } - } - -} - -def callApi(String url, String header, String jsonData){ - try{ - withCredentials([usernamePassword(credentialsId: "ringmaster-token", usernameVariable:'user', passwordVariable: 'token')]){ - final def(String response, String code) = sh(returnStdout: true, script: "curl -s -X POST -H '$header' -H 'Authorization: $token' -w '\\n%{response_code}' $url -d '$jsonData'").trim().tokenize("\n") - log.info("HTTP response status code : ${code}") - if(code != "200"){ - log.error("API call failed with error code - ${code}, response - ${response}") - currentBuild.result = 'FAILURE' - } - } - } - catch ( Exception e) { - log.error("API call failed") - currentBuild.result = 'FAILURE' - throw e - } -} - -@NonCPS -def getDateTime() { - // Get the current date and time in IST - def currentDateTime = ZonedDateTime.now() - // Create a formatter for the desired pattern - def formatter = new DateTimeFormatterBuilder() - .appendPattern("yyyy-MM-dd'T'HH:mm:ss") - .appendOffset("+HH:mm", "+00:00") - .toFormatter() - // Format the current date and time using the formatter - def formattedDateTime = currentDateTime.format(formatter) - return formattedDateTime; -} - diff --git a/src/com/homelab/stages/helmGenerator.groovy b/src/com/homelab/stages/helmGenerator.groovy deleted file mode 100644 index cf803a0..0000000 --- a/src/com/homelab/stages/helmGenerator.groovy +++ /dev/null @@ -1,185 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.constructTemplate -import com.homelab.utilities.gitActions -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.getDockerParams -import com.homelab.stages.multiBranchPipeline - -def run(Map params) { - def btObj = new buTeamMapping() - def gitObj = new gitActions() - - def environments = ['stg':'develop', 'int':'pre-prod', 'prd':'main', 'ftr':'feature'] - final Map modifiedParams = new HashMap<>(params) - - // def helm_repo_name = 'devops-helm-charts' - // def argoRepo = "devops-argo-config" - - def deployment_order = modifiedParams.app_names.replaceAll('\\s', '').split(', ') as List - - modifiedParams['deployment_order'] = deployment_order - - stage('Create Jenkinsfile') { - jenkinsfileCreate(modifiedParams) - } - - modifiedParams['bu'] = btObj.get_bu_initials(modifiedParams['bu']) - modifiedParams['team'] = btObj.get_team_initials(modifiedParams['team']) - log.info(modifiedParams) - - stage('Update Helm charts') { - gitObj.clone("${WORKSPACE}", "${env.helm_repo_name}", null) - environments.each { - modifiedParams['environment'] = it.key - def helm_branch_name = it.value - for (deployment in deployment_order) { - modifiedParams['app_name'] = deployment - modifiedParams['host'] = helm_branch_name == 'feature' ? "INGRESS_PR_NUMBER-${deployment}.dev.internal.homelabtest.in" : "${deployment}.${it.key}.internal.homelabtest.in" - modifiedParams['host'] = (helm_branch_name == 'pre-prod') ? "${deployment}.${modifiedParams.bu}.internal.homelab.co" : modifiedParams.host - //If branch is main, then assuming the env as prod - //Making nodeSelector changes to hypercore or hypermem & based on arch, so that it moves to common node pool - if (helm_branch_name == 'main') { - def cpu = modifiedParams['cpu_request'] - def memory = modifiedParams['memory_request'] - def nodeSelector = 'hypercore' - - if (cpu.contains('m')) { - def numericValue = cpu.replaceAll('\\D+', '').toDouble() - def gb = numericValue / 1000 - cpu = gb.toString() - } - def mem_dgt = memory.replaceAll('\\D+', '').toDouble() - memory = memory.contains('Mi') ? mem_dgt / 1024 : mem_dgt - def ratio = cpu.toDouble() / memory.toDouble() - def arch = modifiedParams['arch'] - def graviton_required = false - if (arch == 'arm64') { - graviton_required = true - } - if (ratio <= 1 / 3) { - if (graviton_required) { - nodeSelector = 'hypermem-arm64' - } else { - nodeSelector = 'hypermem' - } - } else { - if (graviton_required) { - nodeSelector = 'hypercore-arm64' - } else { - nodeSelector = 'hypercore' - } - } - modifiedParams['nodeSelector'] = nodeSelector - } - updateHelmRepo(modifiedParams, env.helm_repo_name, helm_branch_name) - } - } - } - - // stage("Update Argo Application"){ - // gitObj.clone("${WORKSPACE}", "${argoRepo}", null) - // environments.each{ - // modifiedParams["environment"] = it.key - // def argo_branch_name = it.value - // for (deployment in deployment_order){ - // modifiedParams["app_name"] = deployment - // updateArgoRepo(modifiedParams,argoRepo,argo_branch_name) - // } - // } - // } - - stage('Create ECR repo') { - def buildDockerObj = new getDockerParams() - def modules = buildDockerObj.getModules(modifiedParams['repo_name']) - if (modules == null) { - modules = ['module_less'] - } - for (module in modules) { - def ecr_repo_name = (module == 'module_less') ? "${modifiedParams.team}/${modifiedParams.repo_name.toLowerCase()}" : "${modifiedParams.team}/${modifiedParams.repo_name.toLowerCase()}/${module}" - try { - sh "aws ecr create-repository --repository-name ${ecr_repo_name}" - } - catch (Exceptione) { - log.info('repository already present') - } - } - } - stage('Create Multibranch pipeline') { - def multiBranchPipelineObj = new multiBranchPipeline() - multiBranchPipelineObj.applicationOnboard(params['bu'], params['repo_name']) - } -} - -def jenkinsfileCreate(Map config) { - def constructObj = new constructTemplate() - def gitObj = new gitActions() - def repo_name = config.repo_name - def deployment_order = config.deployment_order - def repo_branch_name = 'eks_onboarding' - config['branch_params'] = '' - config['excludedMoudles'] = config.excludedMoudles.replaceAll('\\s', '').split(', ') as List - gitObj.clone("${WORKSPACE}", "${config.repo_name}", null) - gitObj.branchCheckOut(config.repo_name, repo_branch_name) - deployment_yaml_file = get_deployment_yaml_file(config['dockerBuildVersion']) - dir(repo_name) { - sh(script:'mkdir -p deployments/') - sh 'chmod -R 777 .' - constructObj.renderTemplate(config, 'Jenkinsfile', 'Jenkinsfile') - gitObj.add('.', 'Jenkinsfile') - - constructObj.renderTemplate(config, 'config.yaml', 'config.yaml') - gitObj.add('.', 'config.yaml') - - dir('deployments') { - for (deployment in deployment_order) { - config['app_name'] = deployment - constructObj.renderTemplate(config, deployment_yaml_file, deployment + '.yaml') - gitObj.add('.', deployment + '.yaml') - } - } - } - - gitObj.codeCommit(repo_name, repo_branch_name, 'Generating build and deployments files') - gitObj.codePush(repo_name, repo_branch_name) -} - -def updateHelmRepo(Map config, String helm_repo_name, String helm_branch_name) { - def constructObj = new constructTemplate() - def gitObj = new gitActions() - def filepath = "${env.helmChartsPath}/${config.bu}/${config.team}/${config.app_name}" - def file_name = 'values_properties.yaml' - def app_branch = config.app_name + '-' + helm_branch_name + '-helm' - def pr_num = '' - def commit_status = 0 - - gitObj.branchCheckOut(env.helm_repo_name, helm_branch_name) - gitObj.branchCheckOut("${env.helm_repo_name}", "${app_branch}") - // create filepath - dir(env.helm_repo_name) { - sh(script:"mkdir -p ${filepath}" ,returnStdout:true) - sh 'chmod -R 777 .' - constructObj.renderTemplate(config, file_name, filepath + '/' + file_name) - } - - // git commit and push - gitObj.add(env.helm_repo_name, filepath + '/' + file_name) - commit_status = gitObj.codeCommit(env.helm_repo_name, app_branch, 'Generating properties values yaml file') - if (commit_status == 0) { - gitObj.codePush(env.helm_repo_name, app_branch) - pr_num = gitObj.createPR(config.app_name, env.helm_repo_name, helm_branch_name, app_branch, 'Merge helm values properties') - gitObj.mergePR(env.helm_repo_name, pr_num, app_branch) - gitObj.deleteBranch(env.helm_repo_name, helm_branch_name, app_branch) - } -} - -def get_deployment_yaml_file(def dockerBuildVersion) { - switch(dockerBuildVersion) { - case ~/^maven-.*/: return 'deployment.yaml' - case ~/^node-.*/: return 'node-deployment.yaml' - case ~/^python-.*/: return 'python-deployment.yaml' - case ~/^go.*/: return 'go-deployment.yaml' - case 'gradle': return 'gradle-deployment.yaml' - case 'php': return 'php-deployment.yaml' - } -} diff --git a/src/com/homelab/stages/hotFix.groovy b/src/com/homelab/stages/hotFix.groovy deleted file mode 100644 index 4d47261..0000000 --- a/src/com/homelab/stages/hotFix.groovy +++ /dev/null @@ -1,24 +0,0 @@ -package com.homelab.stages - -def run(String repo_name){ - stage(stageName("Check for Hot Fix")){ - dir(repo_name){ - if (env.CHANGE_ID) { - def source_branch_name = env.CHANGE_BRANCH - log.info("Source-Branch : ${source_branch_name}") - source_branch_name = source_branch_name.toLowerCase() - if (source_branch_name.contains("hotfix_")){ - env.hot_fix = true - log.info("***************** Enabling Hot-fix workflow *****************") - } - else if (source_branch_name.matches('^revert-\\d+-.+$')){ - env.hot_fix = true - log.info("***************** Enabling Hot-fix workflow for revert branch *****************") - } - } - else { - log.info("**** Branch: ${env.BRANCH_NAME}, So Skipping Hot-fix check ****") - } - } - } -} \ No newline at end of file diff --git a/src/com/homelab/stages/multiBranchPipeline.groovy b/src/com/homelab/stages/multiBranchPipeline.groovy deleted file mode 100644 index 71ab828..0000000 --- a/src/com/homelab/stages/multiBranchPipeline.groovy +++ /dev/null @@ -1,73 +0,0 @@ -package com.homelab.stages - -import jxl.* -import hudson.util.PersistedList -import jenkins.model.Jenkins -import jenkins.branch.* -import jenkins.plugins.git.* -import org.jenkinsci.plugins.workflow.multibranch.* - -import com.cloudbees.hudson.plugins.folder.* -import org.jenkinsci.plugins.github_branch_source.* -import org.jenkinsci.plugins.workflow.libs.* -import hudson.scm.SCM -import hudson.plugins.git.* -import net.gleske.scmfilter.impl.trait.* - -def applicationOnboard(String foldername, String repo_name) { - // Bring some values in from ansible using the jenkins_script modules wierd "args" approach (these are not gstrings) - String folderName = "${foldername}" - String repoName = "${repo_name}" - String scriptPath = "Jenkinsfile" - String gitRepo = "https://github.com/Homelab/${repo_name}.git" - String mBPName = "${repo_name}-cicd" - String credentialsId = env.GITHUB_CRED - - Jenkins jenkins = Jenkins.instance // saves some typing - - // Get the folder where this job should be - // def folder = jenkins.getItem(folderName) - // //Create the folder if it doesn't exist - // if (folder == null) { - // folder = jenkins.createProject(Folder.class, folderName) - // } - - // Multibranch creation/update - WorkflowMultiBranchProject mbp - def view = jenkins.getView(foldername) - Item item = jenkins.getItem(mBPName) - if ( item != null ) { - // Update case - mbp = (WorkflowMultiBranchProject) item - } else { - // Create case - mbp = jenkins.createProject(WorkflowMultiBranchProject.class, mBPName) - } - - // Configure the script this MBP uses - mbp.getProjectFactory().setScriptPath(scriptPath) - - def implicit = false - def defaultVersion = "master" - def traits = [] - - GitHubSCMSource gitHubSCMSource = new GitHubSCMSource("Homelab", repoName, gitRepo, implicit) - gitHubSCMSource.credentialsId = credentialsId - - BranchDiscoveryTrait branchDiscoveryTrait = new BranchDiscoveryTrait(3) - OriginPullRequestDiscoveryTrait pullRequestTrait = new OriginPullRequestDiscoveryTrait(1) - WildcardSCMHeadFilterTrait wildcardSCMHeadFilterTrait = new WildcardSCMHeadFilterTrait('gcp-main*','','','*') - traits.add(branchDiscoveryTrait) - traits.add(pullRequestTrait) - traits.add(wildcardSCMHeadFilterTrait) - gitHubSCMSource.setTraits(traits) - BranchSource branchSource = new BranchSource(gitHubSCMSource) - NoTriggerBranchProperty noTriggerBranchProperty = new NoTriggerBranchProperty() - BranchProperty[] ntbp = [noTriggerBranchProperty] - branchSource.setStrategy(new DefaultBranchPropertyStrategy(ntbp)) - - PersistedList sources = mbp.getSourcesList() - sources.clear() - sources.add(branchSource) - view.add(mbp) -} diff --git a/src/com/homelab/stages/securityScan.groovy b/src/com/homelab/stages/securityScan.groovy deleted file mode 100644 index b0e731b..0000000 --- a/src/com/homelab/stages/securityScan.groovy +++ /dev/null @@ -1,24 +0,0 @@ -package com.homelab.stages - -def run(Map config) { - try { - def repo_name = config.repo_name - stage('Security scan') { - if (config.skip_security_scan) { - log.info('Skipping - Security Scan') - } - else { - final String url = '172.31.5.29:63232/scans' - final def(String response, String code) = sh(returnStdout: true, script: "curl -s -X POST -H 'Content-Type: application/json' -w '\\n%{response_code}' $url -d '{\"reponame\":\"$repo_name\",\"branch\":\"$BRANCH_NAME\"}'").trim().tokenize('\n') - log.info("HTTP response status code : ${code}") - log.info("Response: ${response}") - } - } - } - catch ( Exception e) { - env.msg = 'failed in security scan . Please check console output for more details.' - log.error(env.msg) - currentBuild.result = env.FAILURE - throw e - } -} diff --git a/src/com/homelab/stages/validateBuTeam.groovy b/src/com/homelab/stages/validateBuTeam.groovy deleted file mode 100644 index d3f0405..0000000 --- a/src/com/homelab/stages/validateBuTeam.groovy +++ /dev/null @@ -1,18 +0,0 @@ -package com.homelab.stages - -import com.homelab.utilities.buTeamMapping - -def run(String bu, String team,String module){ - stage("Validate BU and Team"){ - def vObj = new buTeamMapping() - if (vObj.validate(bu,team)){ - log.info("Correct team and BU values") - } - else{ - error "Incorrect BU and team values provided" - } - if(module == ""){ - error "Module can't be empty. if there is no module, Please provide the parameter value as module_less" - } - } -} \ No newline at end of file diff --git a/src/com/homelab/utilities/addSSHKey.groovy b/src/com/homelab/utilities/addSSHKey.groovy deleted file mode 100644 index 7464525..0000000 --- a/src/com/homelab/utilities/addSSHKey.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package com.homelab.utilities - -def create() { - withCredentials([file(credentialsId: 'ssh-private-key', variable: 'FILE')]) { - sh """ - cat ${FILE} > ./id_github_jenkins - chmod 600 ./id_github_jenkins - - # Ensure .ssh directory has correct permissions - chmod 700 /root/.ssh - - # Fix SSH config file permissions if it exists - if [ -f /root/.ssh/config ]; then - chmod 600 /root/.ssh/config - chown root:root /root/.ssh/config - fi - - # Fix existing SSH private key permissions if it exists - if [ -f /root/.ssh/id_rsa ]; then - chmod 600 /root/.ssh/id_rsa - chown root:root /root/.ssh/id_rsa - fi - - # Fix any other SSH key files that might exist - find /root/.ssh -type f -name "id_*" -exec chmod 600 {} \\; 2>/dev/null || true - """ - } -} diff --git a/src/com/homelab/utilities/buTeamMapping.groovy b/src/com/homelab/utilities/buTeamMapping.groovy deleted file mode 100644 index d4a0fce..0000000 --- a/src/com/homelab/utilities/buTeamMapping.groovy +++ /dev/null @@ -1,125 +0,0 @@ -/* -Purpose: Utility function to return BU and their respective teams -Author: Avinash kumar Lodhi -*/ -package com.homelab.utilities - -def validate(String bu, String team) { - def bu_team_map = ['supply':['supplier-ads', 'supplier-ads-frontend', 'experience', 'fulfilment', 'fulfilment-frontend','financial-services', 'cataloging', 'cataloging-frontend', 'payout', 'payout-frontend', 'supplier-acquisition-activation', 'supplier-service', 'returns', 'supply-shared', 'display-ads', 'offers','transact', 'supplier-live-commerce'], - 'demand':['comms-platform', 'live-commerce', 'shopping-platform', 'product-feed', 'search', 'product-meta', 'user-growth', 'web', 'transact', 'communications', 'discovery-platform', 'offers', 'android-platform', 'ios', 'demand-shared', 'discovery-ranking'], - 'farmiso':['farmiso'], - 'admin':['devops'], - 'central':['shared', 'devops', 'psec', 'dbe'], - 'dataengg':['data-platform', 'dataengg-shared', 'data-intelligence', 'data-platform-consumption', 'data-platform-ingestion', 'data-platform-nrt', 'data-platform-prism-frmw', 'data-platform-experimentation'], - 'datascience':['data-science', 'ml-platform', 'for-you', 'recommendation', 'catalog-listing-page', 'search', 'advertisement', 'explore', 'pricing', 'product-match', 'catalog-taxonomy', 'brand-infringment', 'fds', 'return-reimbursements', 'fullfilment', 'ugc-moderation-analysis', 'home-page', 'core', 'usergrowth', 'demand-forecast', 'catalog-qc'], - 'mcache':['mcache', 'mcache-shared', 'supplier-service'], - 'infra':['devops', 'dbe'] - ] - if (bu == null || team == null) { - return null - } - return bu_team_map[bu].contains(team) -} - -def get_initials(String targetMap, String targetString) { - def bu_initials = ['supply':'supl', - 'demand':'dmnd', - 'farmiso':'farm', - 'admin':'admn', - 'central':'cntr', - 'dataengg':'deng', - 'datascience':'dsci', - 'mcache':'mche', - 'infra':'infr' - ] - def team_initials = ['supplier-ads':'ads', - 'comms-platform': 'cplat', - 'supplier-ads-frontend':'fads', - 'experience':'xp', - 'fulfilment':'fnf', - 'fulfilment-frontend':'ffnf', - 'financial-services':'fsvc', - 'cataloging':'ctlng', - 'cataloging-frontend':'fctlg', - 'payout':'pay', - 'payout-frontend':'fpay', - 'supplier-acquisition-activation':'saa', - 'supplier-service':'ssvc', - 'seller-services': 'sis', - 'live-commerce':'lcom', - 'shopping-platform':'splat', - 'product-feed':'pfeed', - 'search':'srch', - 'product-meta':'pmeta', - 'user-growth':'grwth', - 'web':'web', - 'transact':'trnst', - 'communications':'comms', - 'discovery-platform':'dplat', - 'farmiso':'farm', - 'devops':'devop', - 'offers':'offer', - 'returns':'retrn', - 'android-platform':'andrd', - 'ios':'ios', - 'shared':'xcntr', - 'supply-shared':'xsupl', - 'demand-shared':'xdmnd', - 'data-platform':'dp', - 'data-science':'ds', - 'ml-platform':'ml', - 'dataengg-shared':'xdeng', - 'datascience-shared':'xdsci', - 'data-intelligence':'di', - 'recommendation':'rcmnd', - 'catalog-listing-page':'ctllp', - 'advertisement':'adv', - 'explore':'explr', - 'pricing':'price', - 'product-match':'patch', - 'catalog-taxonomy':'ctltx', - 'brand-infringment':'brndi', - 'fds':'fds', - 'return-reimbursements':'retrr', - 'fullfilment':'flfmt', - 'ugc-moderation-analysis':'umdra', - 'home-page':'hpage', - 'usergrowth':'ugrwt', - 'demand-forecast':'dmndf', - 'catalog-qc':'ctlqc', - 'data-platform-consumption':'dpcon', - 'data-platform-ingestion':'dping', - 'data-platform-nrt':'dpnrt', - 'data-platform-prism-frmw':'dpprf', - 'data-platform-experimentation':'dpexp', - 'display-ads':'dplay', - 'discovery-ranking':'drank', - 'mcache':'mche', - 'mcache-shared':'xmche', - 'supplier-live-commerce':'slcom', - 'trust-and-safety': 'tns', - 'valmo': 'vlm', - 'psec':'psec', - 'dbe':'dbe', - 'dev-productivity':'devprd'] - - if (targetString == null) { - return null - } - switch (targetMap) { - case 'bu_initials': - return bu_initials[targetString] - case 'team_initials': - return team_initials[targetString] - default: - return 'Undefined option' - } -} - -def get_team_initials(String team) { - return get_initials('team_initials', team) -} - -def get_bu_initials(String bu) { - return get_initials('bu_initials', bu) -} diff --git a/src/com/homelab/utilities/constructParam.groovy b/src/com/homelab/utilities/constructParam.groovy deleted file mode 100644 index 7c13e94..0000000 --- a/src/com/homelab/utilities/constructParam.groovy +++ /dev/null @@ -1,391 +0,0 @@ - -package com.homelab.utilities - -def getWhitelistedRepos(fileName){ - dir('whitelist'){ - git( - url: "https://github.com/Homelab/whitelists.git", - branch: "main", - credentialsId: 'cicd-github-app', - )} - def yaml = readYaml file: "whitelist/${fileName}.yaml" - return yaml.get("repos", []) as Set -} - -def getWhitelistedDeployable(fileName, keyName){ - dir('whitelist'){ - git( - url: "https://github.com/Homelab/whitelists.git", - branch: "main", - credentialsId: 'cicd-github-app', - )} - def yaml = readYaml file: "whitelist/${fileName}.yaml" - return yaml.get(keyName, []) as Set -} - -/* -* return `true` if we should not proceed -*/ -def isMultizoneEnabled( String deployable){ - def WHITELIST = getWhitelistedDeployable("multizone-enabled-repos" , "multizone_enabled_deployables") - if (WHITELIST.contains(deployable)) { - return true - } - return false -} - -/* -* return `true` if we should not proceed -*/ -def skipSonarCheckForbidden(Map config, Map environment_map) { - def branch = env.BRANCH_NAME - def environment = environment_map.getOrDefault(branch, "int") - - def WHITELIST = getWhitelistedRepos("skip-sonar-whitelist") - - // returns 'true' if we aren't skipping sonar - // or if we're allowed to skip sonar - def build_version = config['dockerBuildVersion'] - def repo_name = config['repo_name'] - - // if not a maven build OR if it a hotfix we exit early and don’t care what skip_sonar is - if (WHITELIST.contains(repo_name)|| !(build_version.contains("maven")) || environment != "prd" || branch.contains("hotfix")){ - return false; - } - - return config['skip_sonar']; -} - -def skipSonarCheckForGo(Map config) { - def branch = env.BRANCH_NAME - def WHITELIST = getWhitelistedRepos("skip-sonar-whitelist") - def build_version = config['dockerBuildVersion'] - def repo_name = config['repo_name'] - echo "env.INFRA_ENV: ${env.INFRA_ENV}" - if (WHITELIST.contains(repo_name)|| branch.contains("hotfix")|| env.INFRA_ENV == "toolchain"){ - return true; - } - return false; -} -/* -* return `true` if we should not proceed -*/ -def appConfigDisabledForbidden(boolean appConfigEnabled, String repo_name, String environment, String build_version){ - def branch = env.CHANGE_TARGET - def WHITELIST = getWhitelistedRepos("app-config-disabled") - if (WHITELIST.contains(repo_name) || environment!="stg"){ - return false - } - if (!(build_version.contains("maven") || build_version.contains("gradle"))){ - return false - } - return !appConfigEnabled -} - -/* -* return `true` if we should not proceed -*/ -def allowedNonDevelopPrDeploymentToIntRepos( String repo_name){ - def WHITELIST = getWhitelistedRepos("allowedNonDevelopPrDeploymentToInt") - if (WHITELIST.contains(repo_name)) { - return true - } - return false -} - -/* -* return `true` if we should not proceed -*/ -def ValidateCacConfigForRepo(boolean ValidateConfig, String repo_name ){ - def branch = env.CHANGE_TARGET - def WHITELIST = getWhitelistedRepos("ValidateCacConfig") - if (WHITELIST.contains(repo_name)) { - return true - } - return ValidateConfig -} - -def getToolchainEnv() { - def paramsAction = currentBuild.rawBuild.getAction(hudson.model.ParametersAction.class) - if (paramsAction) { - echo "paramsAction: ${paramsAction}" - def p = paramsAction.getParameter("TOOLCHAIN_ENV") - echo "p: ${p}" - if (p) { - return p.getValue()?.toString() - } - } - return null -} - -def run(Map config) { - def branch_name = env.BRANCH_NAME - if (env.INFRA_ENV == 'toolchain' && (config.build_tool?.startsWith('node-') || config.dockerBuildVersion?.startsWith('node-'))) { - branch_name = 'develop' // develop maps to stg in the environment_map - def tcEnv = getToolchainEnv() - env.TOOLCHAIN_ENV = tcEnv - log.info("Successfully extracted TOOLCHAIN_ENV from trigger cause: ${env.TOOLCHAIN_ENV}") - } - def environment_map = ['master':'prd', 'main':'prd', 'develop':'stg', 'gcp-main':'prd', 'farmiso-main':'prd', 'gcp-master':'prd', 'gcp-dev':'stg'] - env.skip_user_input = config.skip_user_input ?: false - - // don't allow skip_sonar - if (skipSonarCheckForbidden(config, environment_map)){ - throw new Exception("Not allowed to skip sonar (skip_sonar in config.yaml)") - } - - if (env.CHANGE_ID) { - branch_name = env.CHANGE_TARGET - environment_map = ['master':'int', 'main':'int', 'gcp-main':'int', 'farmiso-main':'int', 'gcp-master':'int', 'develop':'ftr', 'gcp-dev':'ftr'] - } - environment_map[branch_name] = environment_map[branch_name] ?: 'ftr' - - if (config.containsKey('branch_params')) { - Map branch_config = config['branch_params'].collectEntries { key, value -> branch_name.matches(key) ? value : [ : ] } - config.remove('branch_params') - config.putAll(branch_config) - } - if (config.containsKey('environment')) { - Map envrionment_config = config['environment'].collectEntries { key, value -> environment_map[branch_name].matches(key) ? value : [ : ] } - config.remove('environment') - config.putAll(envrionment_config) - } - - env.GITHUB_CRED = 'svc-devops-homelab' - env.cicd_environment = environment_map[branch_name] - env.helm_repo_name = 'devops-helm-charts' - env.argo_repo_name = 'devops-argo-config' - - echo "Branch Name - ${branch_name} and Environment - ${env.cicd_environment}" - if (env.CLOUD_PROVIDER == 'AWS') { - def prodAccountID = '847438129436' - def prodRegion = 'ap-southeast-1' - def prodObjBucket = 'homelab-prod-artifacts' - def devAccountID = '766380763301' - def devObjBucket = 'homelab-stg-artifacts' - def devRegion = 'ap-south-1' - def accountDetails = [ - 'prd': [ - 'accountID': prodAccountID, - 'region': prodRegion, - 'objBucket': prodObjBucket - ], - 'int': [ - 'accountID': prodAccountID, - 'region': prodRegion, - 'objBucket': prodObjBucket - ], - 'stg': [ - 'accountID': devAccountID, - 'region': devRegion, - 'objBucket': devObjBucket - ], - 'ftr': [ - 'accountID': devAccountID, - 'region': devRegion, - 'objBucket': devObjBucket - ] - ] - env.accountID = accountDetails[env.cicd_environment]['accountID'] - env.region = accountDetails[env.cicd_environment]['region'] - env.registry = "${env.accountID}.dkr.ecr.${env.region}.amazonaws.com" - env.buildRegistry = env.registry - env.helmChartsPath = 'charts' - env.defaultHelmChartVersion = '1.0.10' - env.objBucket = accountDetails[env.cicd_environment]['objBucket'] - env.skip_notify = false - echo "${env.accountID}.dkr.ecr.${env.region}.amazonaws.com" - } - else if (env.CLOUD_PROVIDER == 'GCP') { - def prodVaultURL = 'https://vault-prd.homelabgcp.in' - def prodVaultToken = 'vault-prd-token' - def prodSonarURL = 'https://sonarqube-prd.homelabgcp.in' - def prodSonarToken = 'sonar-token-prod' - def prodSonarEnv = 'sonarqube-test' - def prodGoProxyUrl = 'https://athens-prd.homelabgcp.in' - def prdDockerHost = 'dind-prd-svc' - def preProdDockerHost = 'dind-int-svc' - def prodGCPProject = "homelab-${config.bu}-prd-0622" - def preprodGCPProject = "homelab-shared-int-0525" - def devVaultURL = 'https://vault-dev.homelabgcp.in' - def devVaultToken = 'vault-dev-token' - def devSonarURL = "https://sonarqube-${config.bu}-dev.homelabgcp.in" - def devSonarToken = "sonar-token-${config.bu}-dev" - def devSonarEnv = "sonar-${config.bu}-dev" - def devGoProxyUrl = 'https://athens-dev.homelabgcp.in' - def devGCPProject = "homelab-${config.bu}-dev-0622" - def devDockerHost = 'dind-dev-new-svc.jenkins-new.svc.cluster.local' - def toolchainDockerHost = 'toolchain-dind-dev-svc.jenkins-toolchain.svc.cluster.local' - def accountDetails = [ - 'prd': [ - 'vaultURL': prodVaultURL, - 'vaultToken': prodVaultToken, - 'sonarURL': prodSonarURL, - 'sonarToken': prodSonarToken, - 'GCPProject': prodGCPProject, - 'sonarEnv': prodSonarEnv, - 'GCPLBProject': prodGCPProject, - 'goProxyUrl': prodGoProxyUrl, - 'dockerHost': prdDockerHost - ], - 'int': [ - 'vaultURL': prodVaultURL, - 'vaultToken': prodVaultToken, - 'sonarURL': prodSonarURL, - 'sonarToken': prodSonarToken, - 'GCPProject': preprodGCPProject, - 'sonarEnv': prodSonarEnv, - 'GCPLBProject': prodGCPProject, - 'goProxyUrl': prodGoProxyUrl, - 'dockerHost': preProdDockerHost - ], - 'stg': [ - 'vaultURL': devVaultURL, - 'vaultToken': devVaultToken, - 'sonarURL': devSonarURL, - 'sonarToken': devSonarToken, - 'GCPProject': devGCPProject, - 'sonarEnv': devSonarEnv, - 'GCPLBProject': devGCPProject, - 'goProxyUrl': devGoProxyUrl, - 'dockerHost': devDockerHost - ], - 'ftr': [ - 'vaultURL': devVaultURL, - 'vaultToken': devVaultToken, - 'sonarURL': devSonarURL, - 'sonarToken': devSonarToken, - 'GCPProject': devGCPProject, - 'sonarEnv': devSonarEnv, - 'GCPLBProject': devGCPProject, - 'goProxyUrl': devGoProxyUrl, - 'dockerHost': devDockerHost ] - ] - env.GCPProject = accountDetails[env.cicd_environment]['GCPProject'] - env.GCPLBProject = accountDetails[env.cicd_environment]['GCPLBProject'] - env.registry = 'asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622' - if (env.INFRA_ENV == 'toolchain') { - env.registry = 'asia-southeast1-docker.pkg.dev/homelab-central-dev-0622/toolchain' - } - env.buildRegistry = 'asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/admin' - env.helmChartsPath = env.cicd_environment == 'prd' ? 'values_v3' : 'values_v2' - env.defaultHelmChartVersion = '2.0.0' - env.objBucket = "gcs-infr-dvps-homelab-artifacts-${env.cicd_environment}" - env.vaultURL = accountDetails[env.cicd_environment]['vaultURL'] - env.vaultToken = accountDetails[env.cicd_environment]['vaultToken'] - env.sonarURL = accountDetails[env.cicd_environment]['sonarURL'] - env.sonarToken = accountDetails[env.cicd_environment]['sonarToken'] - env.sonarEnv = accountDetails[env.cicd_environment]['sonarEnv'] - env.goProxyUrl = accountDetails[env.cicd_environment]['goProxyUrl'] - env.skip_notify = true - env.DOCKER_HOST = accountDetails[env.cicd_environment]['dockerHost'] - if (env.INFRA_ENV == 'toolchain') { - env.DOCKER_HOST = toolchainDockerHost - } - } - echo "Bucket and Image Repo Details - ${env.registry} ${env.buildRegistry} ${env.objBucket}" - echo "Docker Host - ${env.DOCKER_HOST}" -} - -def perDeploymentVars(Map value_binding) { - env.BU = value_binding.bu - echo "${env.BU}" - - if (env.CLOUD_PROVIDER == 'AWS') { - def inClusterName = 'https://kubernetes.default.svc' - def prodArgoURL = 'prod-ops-argocd.homelab.com' - def devArgoURL = 'stg-dev-argocd.homelabtest.in' - def prodK8sCluster = [ - 'supply': 'https://211689C65F4496AAA76FE19B29E24B6E.yl4.ap-southeast-1.eks.amazonaws.com', - 'demand': 'https://9059D138B6277A0EA592BA7F4B680CEC.gr7.ap-southeast-1.eks.amazonaws.com', - 'dataengg': 'https://806ADE97231CA65D2A0FFB780352630D.yl4.ap-southeast-1.eks.amazonaws.com', - 'datascience': 'https://E34D119516F751AFD1A61043B0514726.yl4.ap-southeast-1.eks.amazonaws.com', - 'central': 'https://C95FCEDF7CEE890F531E1D0488BEC6C3.gr7.ap-southeast-1.eks.amazonaws.com', - 'mcache': 'https://2FFADF214AD8BE58769C5F2797987E50.gr7.ap-southeast-1.eks.amazonaws.com' - ] - def devK8sCluster = [ - 'supply': inClusterName, - 'demand': inClusterName, - 'dataengg': inClusterName, - 'datascience': inClusterName, - 'central': inClusterName, - 'mcache': inClusterName - ] - def accountDetails = [ - 'prd': [ - 'argoURL': prodArgoURL, - 'argoIncubator': 'prod-app-of-apps', - 'serverMap': prodK8sCluster - ], - 'int': [ - 'argoURL': prodArgoURL, - 'argoIncubator': 'int-app-of-app', - 'serverMap': prodK8sCluster - ], - 'stg': [ - 'argoURL': devArgoURL, - 'argoIncubator': 'app-of-apps', - 'serverMap': devK8sCluster - ], - 'ftr': [ - 'argoURL': devArgoURL, - 'argoIncubator': 'ftr-app-of-apps', - 'serverMap': devK8sCluster - ] - ] - env.clusterName = accountDetails[env.cicd_environment]['serverMap'][env.BU] - env.argoAppsPath = 'applications' - env.argoURL = accountDetails[env.cicd_environment]['argoURL'] - env.argoCreds = 'argocd-jenkins' - env.argoIncubator = accountDetails[env.cicd_environment]['argoIncubator'] - env.argoAppNS = 'argocd' - } - else if (env.CLOUD_PROVIDER == 'GCP') { - def prodArgoURL = "argocd-${env.BU}-prd.homelabgcp.in" - def prodArgoCreds = "argocd-${env.BU}-prd-creds" - def preprodArgoURL = "argocd-shared-int.homelabgcp.in" - def preprodArgoCreds = "argocd-shared-int-creds" - def devArgoURL = 'argocd-dev.homelabgcp.in' - def devArgoCreds = 'argocd-dev-creds' - def accountDetails = [ - 'prd': [ - 'argoURL': prodArgoURL, - 'argoCreds': prodArgoCreds, - 'argoAppNS': "argocd-${env.BU}-prd", - 'clusterName': "k8s-${env.BU}-prd-ase1" - ], - 'int': [ - 'argoURL': preprodArgoURL, - 'argoCreds': preprodArgoCreds, - 'argoAppNS': "argocd-shared-int", - 'clusterName': "k8s-shared-int-ase1" - ], - 'stg': [ - 'argoURL': devArgoURL, - 'argoCreds': devArgoCreds, - 'argoAppNS': "argocd-dev", - 'clusterName': "k8s-${env.BU}-stg-ase1" - ], - 'ftr': [ - 'argoURL': devArgoURL, - 'argoCreds': devArgoCreds, - 'argoAppNS': "argocd-dev", - 'clusterName': "k8s-${env.BU}-stg-ase1" - ] - ] - - env.clusterName = accountDetails[env.cicd_environment]['clusterName'] - - if (env.cicd_environment == 'int') { - env.argoAppsPath = "applications_v2/k8s-${env.BU}-int-ase1" - } else { - env.argoAppsPath = "applications_v2/${env.clusterName}" - } - - env.argoURL = accountDetails[env.cicd_environment]['argoURL'] - env.argoCreds = accountDetails[env.cicd_environment]['argoCreds'] - env.argoAppNS = accountDetails[env.cicd_environment]['argoAppNS'] - env.argoIncubator = "incubator-apps-k8s-${env.BU}-${env.cicd_environment}-ase1" - } - echo "${env.clusterName} ${env.argoURL} ${env.argoIncubator}" -} diff --git a/src/com/homelab/utilities/dockerUtilities.groovy b/src/com/homelab/utilities/dockerUtilities.groovy deleted file mode 100644 index 89ce40b..0000000 --- a/src/com/homelab/utilities/dockerUtilities.groovy +++ /dev/null @@ -1,52 +0,0 @@ -package com.homelab.utilities - -def retryDockerPush(String cmd) { - int maxAttempts = 5 - int attempt = 1 - while (attempt <= maxAttempts) { - try { - sh cmd - break - } catch (err) { - if (attempt == maxAttempts) { - error("Command failed after ${maxAttempts} attempts: ${err}") - } - echo "Command failed, retrying... (${attempt}/${maxAttempts})" - sleep 3 - attempt++ - } - } -} - -def imageExists(String registry, String repoName, String tag) { - int maxAttempts = 5 - int attempt = 1 - - while (attempt <= maxAttempts) { - try { - if (env.CLOUD_PROVIDER == 'GCP') { - def result = sh( - script: "gcloud container images list-tags ${registry}/${repoName} --filter='tags:${tag}' --format='get(tags)'", - returnStdout: true - ).trim() - return result != "" - } - else if (env.CLOUD_PROVIDER == 'AWS') { - echo "AWS not supported." - return false - } - } catch (Exception e) { - echo "Attempt ${attempt}/${maxAttempts} failed: Error checking image existence: ${e.toString()}" - - if (attempt == maxAttempts) { - echo "Max attempts reached. Assuming image does not exist or service is down." - return false - } - - echo "Retrying in 5 seconds..." - sleep 5 - attempt++ - } - } - return false -} \ No newline at end of file diff --git a/src/com/homelab/utilities/getDockerParams.groovy b/src/com/homelab/utilities/getDockerParams.groovy deleted file mode 100644 index b602bd5..0000000 --- a/src/com/homelab/utilities/getDockerParams.groovy +++ /dev/null @@ -1,61 +0,0 @@ -package com.homelab.utilities - -def getCommitid(String repo_name) { - dir(repo_name) { - def gitCmd = env.INFRA_ENV == 'toolchain' ? 'git -c safe.directory="$(pwd)"' : 'git' - def commitID = sh(returnStdout: true, script: "${gitCmd} log -1 --format=%h").trim() - env.commit_id = sh(returnStdout: true, script: "${gitCmd} log -1 --format=%H").trim() - return commitID - } -} - -def getVersion(String repo_name) { - dir(repo_name) { - if (fileExists('pom.xml')) { - return sh(returnStdout: true, script: 'xq -r .project.version pom.xml').trim() - } - else if (fileExists('package.json')) { - return sh(returnStdout: true, script: 'jq -r .version package.json').trim() - } - else { - return '1.0' - } - } -} - -def getModules(String repo_name) { - dir(repo_name) { - if (fileExists('pom.xml')) { - modules = sh(returnStdout: true, script: 'xq -r .project.modules.module[] pom.xml 2>/dev/null || xq -r .project.modules.module pom.xml 2>/dev/null || echo empty').trim() - if (modules == 'empty' || modules == 'null') { - return null - } - modules = modules.split('\n') as List - return modules - } - else { - return null - } - } -} - -def getTag(String repo_name) { - def version = getVersion(repo_name) - def commitID = getCommitid(repo_name) - def date = new Date() - def timesha = date.getTime() - def tag = "v${version}-${commitID}-${timesha}" - if (env.INFRA_ENV == 'toolchain') { - tag = "v${version}-${commitID}" - } - - return tag -} - -def getTagShort(String repo_name) { - def version = getVersion(repo_name) - def commitID = getCommitid(repo_name) - def tagShort = "v${version}-${commitID}" - - return tagShort -} diff --git a/src/com/homelab/utilities/getYamlParameter.groovy b/src/com/homelab/utilities/getYamlParameter.groovy deleted file mode 100644 index 6cb06c9..0000000 --- a/src/com/homelab/utilities/getYamlParameter.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package com.homelab.utilities - -def getParam(String wd, String fileName = 'config.yaml') { - dir(wd) { - def config = readYaml file: fileName - return config - } -} -//read as string -def getParamAsString(String wd, String fileName = 'config.yaml') { - dir(wd) { - // Read the entire file content as a string - def yamlContent = readFile(file: fileName) - return yamlContent - } -} diff --git a/src/com/homelab/utilities/gitActions.groovy b/src/com/homelab/utilities/gitActions.groovy deleted file mode 100644 index d41c406..0000000 --- a/src/com/homelab/utilities/gitActions.groovy +++ /dev/null @@ -1,342 +0,0 @@ -package com.homelab.utilities - -def clone(String path, String repo_name, String branch_name) { - log.info("Cloning repo - ${repo_name}, branch - ${branch_name} in path - ${path}") - dir(path) { - try { - sh "rm -rf ${repo_name}" - } - catch (Exception e) { - log.info('Repo not present, proceeding to clone the repo.') - } - try { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - if (branch_name) { - sh "git clone -b ${branch_name} https://github.com/Homelab/${repo_name}.git" - } - else { - sh "git clone https://github.com/Homelab/${repo_name}.git" - } - } - } - catch (Exception e) { - env.msg += "\n\nFAILED -\n ```Unable to clone repo - ${repo_name}, branch - ${branch_name} in path - ${path}.\n Full Erroror Details - ${e}```" - env.error_msg_to_db += "Unable to Clone Repo ${repo_name};" - log.error("${env.msg}") - throw e - } - } -} - -def branchCheckOut(String path, String branch_name) { - log.info("Checking out to branch - ${branch_name} in path - ${path}") - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh 'git fetch' - try { - sh "git checkout ${branch_name}" - sh 'git pull --ff-only' - } - catch (Exception e) { - log.info('Creating new branch') - sh "git checkout -b ${branch_name}" - } - sh 'git branch' - } - } -} - -def add(String path, String git_add_file) { - log.info("Adding files - ${git_add_file} to git, in path - ${path}") - try { - dir(path) { - sh "git add ${git_add_file}" - } - } - catch (Exception e) { - env.msg += "\n\nFAILED -\n ```Unable to add files - ${git_add_file} to git, in path - ${path}.\n Full Erroror Details - ${e}```" - env.error_msg_to_db += 'Unable to Add Files;' - log.error("${env.msg}") - throw e - } -} - -def createTag(path, git_tag, tag_message) { - log.info("Creating tag - ${git_tag} in git, in path - ${path}") - try { - dir(path) { - sh "git tag ${git_tag} -m '${tag_message}'" - } - } - catch (Exception e) { - env.msg += "\n\nFAILED -\n ```Unable to create tag - ${git_tag} in git, in path - ${path}.\n Full Erroror Details - ${e}```" - env.error_msg_to_db += "Unable to Create Tag ${git_tag};" - log.error("${env.msg}") - throw e - } -} - -def codeCommit(String path, String branch_name, String commit_message) { - log.info("Commiting code with message - ${commit_message} in git, in path - ${path}") - def global_arg = '--global' - def gitName = 'svc-devops-homelab' - def gitEmail = 'devops@homelab.com' - def gitEnv = [ - "GIT_AUTHOR_NAME=${gitName}", - "GIT_AUTHOR_EMAIL=${gitEmail}", - "GIT_COMMITTER_NAME=${gitName}", - "GIT_COMMITTER_EMAIL=${gitEmail}", - ] - dir(path) { - withEnv(gitEnv) { - if (sh(returnStatus: true, script: "git diff-index --quiet ${branch_name} 2>/dev/null")) { - try { - sh "git commit -m '${commit_message}'" - return 0 - } - catch (Exception e) { - env.msg += "\n\nFAILED -\n ```Unable to commit code with message - ${commit_message} to git in path - ${path}.\n Full Erroror Details - ${e}```" - env.error_msg_to_db += 'Unable to Commit;' - log.error("${env.msg}") - return 1 - } - } - else { - log.info("No changes were made in branch - ${branch_name}") - return 1 - // log.error("${env.msg}") - } - } - } -} - -def codePush(String path, String branch_name) { - log.info("Pushing code in branch - ${branch_name} in path - ${path}") - try { - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - try { - sh "git push -f --set-upstream origin ${branch_name}" - } - catch (Exception e) { - sh 'git pull --ff-only' - sh 'git push -f' - } - } - } - } - catch (Exception e) { - env.msg += "\n\nFAILED -\n ```Unable to push code in branch - ${branch_name} in path - ${path}.\n Full Erroror Details - ${e}```" - env.error_msg_to_db += "Unable to push code in branch - ${branch_name};" - log.error("${env.msg}") - throw e - } -} - -def tagPush(String path) { - log.info("Pushing tag in path - ${path}") - try { - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh 'git push --tags' - } - } - } - catch (Exception e) { - env.msg += "\n\nFAILED -\n ```Unable to push tag from path - ${path}.\n Full Erroror Details - ${e}```" - env.error_msg_to_db += 'Unable to Push tag;' - log.error("${env.msg}") - throw e - } -} - -def createPR(String app_name, String repo_name, String base_branch, String target_branch, String pr_message) { - log.info("Creating PR with message - ${pr_message} in Github for repo ${repo_name}") - def body = "{\"title\":\"${app_name} ${base_branch} Onboarding\",\"body\":\"${pr_message}\",\"head\":\"${target_branch}\",\"base\":\"${base_branch}\"}" - def pr_num = 'empty' - - try { - withCredentials([usernamePassword(credentialsId: "${env.GITHUB_CRED}", usernameVariable:'user', passwordVariable: 'token')]) { - create_pr = httpRequest httpMode: 'POST', - customHeaders: [ - [name: 'Accept', value: 'application/vnd.github+json'], - [maskValue: true, name: 'Authorization', value: 'Bearer ' + token] - ], - requestBody: body, - url: "https://api.github.com/repos/Homelab/${repo_name}/pulls", - validResponseCodes: '201', - timeout: 10 - def create_pr_json = readJSON(text: create_pr.content) - try { - error_filter = create_pr_json.errors.message[0] - } - catch (Exception e) { - error_filter = 'empty' - } - if ( error_filter.contains('No commits between') ) { - log.info("No changes were made in branch - ${target_branch}. Skipping - PR Creation...\n${create_pr_json}") - } - else if ( error_filter.contains('A pull request already exists') ) { - log.info(error_filter) - } - else { - pr_num = create_pr_json.number - pr_url = create_pr_json.url - log.info("PR Number - ${pr_num} and URL - ${pr_url}") - } - return pr_num.toString() - } - } - catch (Exception e) { - env.msg = "\n\nFAILED -\n Unable to create PR with message - ${pr_message}.\n Full Error Details - ${e}" - def error_code = "${e}".split('Status code')[1] - error_code = error_code.toString() - error_code = error_code.split(' ') - error_code = error_code[1] - env.error_part_msg_to_db = "Unable to Create PR for base branch - ${base_branch}, target branch - ${target_branch}, Repo Name - ${repo_name}. Failed with status code ${error_code};" //Akshay has asked to remove it - log.error("${env.msg}") - throw e - } -} - -def mergePR(String repo_name, String pr_num, String target_branch) { - log.info("Merging PR ${pr_num} in repo - ${repo_name}") - if ( "${pr_num}" == 'empty' || pr_num == null) { - log.info("No changes were made in branch - ${target_branch}. Skipping - PR Merge...") - } - else { - try { - withCredentials([usernamePassword(credentialsId: "${env.GITHUB_CRED}", usernameVariable:'user', passwordVariable: 'token')]) { - merge_pr = httpRequest httpMode: 'PUT', - customHeaders: [ - [name: 'Accept', value: 'application/vnd.github+json'], - [maskValue: true, name: 'Authorization', value: 'Bearer ' + token] - ], - url: "https://api.github.com/repos/Homelab/${repo_name}/pulls/${pr_num}/merge", - validResponseCodes: '200', - timeout: 10 - - def merge_pr_json = readJSON(text: merge_pr.content) - if ( merge_pr_json.message.contains('not mergeable') ) { - env.msg += "\n\nFAILED -\n Unable to merge PR - ${pr_num}. PR URL - https://github.com/Homelab/${repo_name}/pulls/${pr_num}. ${merge_pr_json.message}" - log.error("${env.msg}") - error("${env.msg}") - } - else { - log.info("PR Merge was SUCCESSFUL. Message - ${merge_pr_json.message}") - } - } - } - catch (Exception e) { - env.msg = "\n\nFAILED -\n Unable to merge PR - ${pr_num} \n Full Error Details - ${e}" - def error_code = "${e}".split('Status code')[1] - error_code = error_code.toString() - error_code = error_code.split(' ') - error_code = error_code[1] - env.error_part_msg_to_db = "Unable to Merge PR - ${pr_num}, Repo Name - ${repo_name} status code ${error_code};" //Akshay has asked to remove it - log.error("${env.msg}") - throw e - } - } -} - -def deleteBranch(String path, String base_branch, String target_branch) { - log.info("Deleting branch - ${target_branch}") - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh 'git fetch' - try { - sh "git checkout ${base_branch}" - sh "git branch -d ${target_branch}" - sh "git push -d origin ${target_branch}" - } - catch (Exception e) { - env.msg = "\n\nFAILED -\n Unable to delete branch - ${target_branch}.\n Full Error Details - ${e}" - env.error_msg_to_db = "Unable to Delete Branch ${target_branch};" - log.error("${env.msg}") - throw e - } - sh 'git branch' - } - } -} - -def preDeleteBranch(String path, String base_branch, String target_branch) { - log.info("Pre Deleting branch - ${target_branch}") - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - try { - sh 'git fetch' - sh "git checkout ${base_branch}" - sh "git push -d origin ${target_branch}" - } - catch (Exception e) { - echo "Pre Delete Branch - Unable to delete branch ${target_branch}" - } - sh 'git branch' - } - } -} - -def fetchDiffFilesForPullRequest(String path , String target_branch){ - log.info("check diff from target branch - ${target_branch}") - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - try { - sh "git fetch origin ${target_branch}:${target_branch}" - sh "git branch" - def changedFiles = sh(script: "git diff --name-only HEAD ${target_branch}", returnStdout: true).trim() - echo "${changedFiles}" - return changedFiles - } - catch (Exception e) { - echo "failed to get the diff files from ${target_branch}" - } - } - } -} -def fetchDiffFilesForPushRequest(String path ){ - log.info("check diff of current and previous commit for ${path}") - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - try { - sh "git fetch origin" - - // Get the latest commit hash and the previous commit hash - def currentCommit = sh(script: "git rev-parse HEAD", returnStdout: true).trim() - def previousCommit = sh(script: "git rev-parse HEAD~1", returnStdout: true).trim() - - // Capture the list of changed files in a Groovy variable - def changedFiles = sh(script: "git diff --name-only ${previousCommit} ${currentCommit}", returnStdout: true).trim() - - return changedFiles - } - catch (Exception e) { - echo "failed to get the diff files for ${path}" - } - } - } -} - -def fetchLatestCommitId(String path, String branch) { - log.info("Checking the latest commit for branch '${branch}' in path '${path}'") - dir(path) { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - try { - // Fetch the latest changes for the specific branch - sh "git fetch origin ${branch}:${branch}" - - // Check out the specified branch - sh "git checkout ${branch}" - - // Get the latest commit hash for the branch - def currentCommit = sh(script: "git rev-parse ${branch}", returnStdout: true).trim() - - return currentCommit - } catch (Exception e) { - echo "Failed to get the latest commit for branch '${branch}' in path '${path}': ${e.message}" - return null - } - } - } -} diff --git a/src/com/homelab/utilities/nodePoolSelection.groovy b/src/com/homelab/utilities/nodePoolSelection.groovy deleted file mode 100644 index d881d46..0000000 --- a/src/com/homelab/utilities/nodePoolSelection.groovy +++ /dev/null @@ -1,141 +0,0 @@ -package com.homelab.utilities - -def run(String memory_request, String cpu_request, String priority_v2) { - echo 'Code to select node pool based on environment' - switch (env.cicd_environment) { - case 'prd': - echo 'Code to select node pool based on memory and cpu request in prd' - def mem_req_part = memory_request - def cpu_req_part = cpu_request - def priority = priority_v2 - echo "mem_req_part is ${mem_req_part}" - echo "cpu_req_part is ${cpu_req_part}" - if ( mem_req_part.contains('M') ) { - mem_req = mem_req_part.replaceAll('Mi', '') - mem_req = mem_req.replaceAll('M', '') - try { - mem_req = mem_req.toDouble() - } - catch (NumberFormatException e) { - mem_req = mem_req.toDouble() - //mem_req = mem_req.toInteger() - } - } - else if ( mem_req_part.contains('G') ) { - mem_req = mem_req_part.replaceAll('Gi', '') - mem_req = mem_req.replaceAll('G', '') - try { - mem_req = mem_req.toDouble() * 1024 - } - catch (NumberFormatException e) { - mem_req = mem_req.toDouble() * 1024 - //mem_req = mem_req.toInteger() - } - } - echo "memory_request is ${memory_request} ${mem_req}" - - if ( cpu_req_part.contains('m') ) { - cpu_req = cpu_req_part.replaceAll('m', '') - try { - cpu_req = cpu_req.toDouble() - } - catch (NumberFormatException e) { - cpu_req = cpu_req.toDouble() - //cpu_req = cpu_req.toInteger() - } - } - else { - try { - cpu_req = cpu_req_part.toDouble() * 1000 - } - catch (NumberFormatException e) { - cpu_req = cpu_req_part.toDouble() * 1000 - //cpu_req = cpu_req.toInteger() - } - } - echo "cpu_request is ${cpu_request} ${cpu_req}" - - def ratio = (mem_req / cpu_req).toDouble() - if ( cpu_req > mem_req ) { - ratio = 2 - } - //ratio = ratio.toInteger() - echo "Ratio - ${ratio}" - if(priority.equalsIgnoreCase('cp1')||priority.equalsIgnoreCase('cp2')||priority.equalsIgnoreCase('cp3')||priority.equalsIgnoreCase('up1')||priority.equalsIgnoreCase('up2')||priority.equalsIgnoreCase('up3')||priority.equalsIgnoreCase('sp1')||priority.equalsIgnoreCase('sp2')||priority.equalsIgnoreCase('sp3')){ - low_priority="lite" - if ( ratio >= 2.5 ) { - ratiovalue = "tetra" - } - else { - ratiovalue = "duo" - } - if ( cpu_req >=2200 ) { - nodename = "sumo" - } - else{ - nodename= "mega" - } - nodeSelectorvalue = "${nodename}${ratiovalue}${low_priority}" - println nodeSelectorvalue - break - } - else{ - if ( ratio > 5.5 ) { - ratiovalue = 'octa' - } - else if ( ratio >= 2.5 ) { - ratiovalue = 'tetra' - } - else { - ratiovalue = 'duo' - } - - if ( cpu_req >= 2200) { - nodevalue = 'sumo' - } - else if ( cpu_req < 2200 && cpu_req >= 1000) { - nodevalue = 'mega' - } - else { - nodevalue = 'compact' - } - - switch ( env.BU?.toLowerCase() ) { - case 'supply': - if (nodevalue == 'sumo' && (ratiovalue == 'hexa' || ratiovalue == 'octa')){ - ratiovalue = 'tetra' - } - else if (nodevalue == 'mega' && (ratiovalue == 'quad' || ratiovalue == 'octa')){ - ratiovalue = 'tetra' - } - else if (nodevalue == 'compact' && ratiovalue == 'trio'){ - ratiovalue = 'tetra' - } - break - case 'demand': - if (nodevalue == 'mega' && ratiovalue == 'quad'){ - ratiovalue = 'tetra' - } - else if (nodevalue == 'compact' && (ratiovalue == 'octa' || ratiovalue == 'trio')){ - ratiovalue = 'tetra' - } - break - } - - nodeSelectorvalue = "${nodevalue}${ratiovalue}" - break - - } - case 'int': - echo 'Shared node pool for int/pre-prod' - nodeSelectorvalue = "preprod-cost-optimized" - break - case ['dev', 'ftr', 'stg']: - echo 'Shared node pool for dev and ftr' - nodeSelectorvalue = "${env.BU}-shared" - break - default: - log.error('Unable to fetch environment') - } - return nodeSelectorvalue -} diff --git a/vars/buildDockerGroovyGke.groovy b/vars/buildDockerGroovyGke.groovy deleted file mode 100644 index 4a077ca..0000000 --- a/vars/buildDockerGroovyGke.groovy +++ /dev/null @@ -1,164 +0,0 @@ -package com.homelab.stages - -def run(Map config){ - stage('Build docker image'){ - container('dockerpush') { - def env = "dev" //write if condition according to branch if branch is dev/staging or any other branch then master it will be dev if master prod - def ecrImagePod = config.ecrImagePod - def repo_name = config.repo_name - def dockerBuildVersion = config.dockerBuildVersion ?: "" - def version = getVersion() - def artifactId = getArtifactId() - def modules = getModules() - def excludedModules = config.excludedModules ?: [] - j = 0 - for (module in modules){ - if (excludedModules.contains(module)){ - echo "===== NOT Building container for Module - ${module} =====" - }else { - def dockerfileRef = getDockerfile("${dockerBuildVersion}","${repo_name}","${version}","${module}","NO") - j+=1 - sh """ - echo "===== ${j}. Creating Container for Module - ${module} ======" - if [ -f ${module}/target/*.jar ] - then - echo "docker build -t ${ecrImagePod}/${repo_name}/${module} ${dockerfileRef}; rm -f Dockerfile-${module}" - else - echo "No jar file found." - exit 1 - fi - """ - } - } - } - } -} - -def getCommitid(){ - return sh(returnStdout: true, script: 'git log -1 --format=%h').trim() -} - -def registry(String buildenv){ - switch(env) { - case "prod": - return 'asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/prod/' - break; - case "dev": - return 'asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622/dev/' - break; - default: - return 'asia-southeast1-docker.pkg.dev/homelab-devops-admin-0622' - } -} - -def getVersion(){ -// if (fileExists("pom.xml")) { -// return sh(returnStdout: true, script: 'xq -r .project.version pom.xml').trim() -// }else if (fileExists("package.json")) { -// return sh(returnStdout: true, script: 'jq -r .version package.json').trim() -// } - return "2.0" -} - -def getModules(){ - // if (fileExists("pom.xml")) { - // modules = sh(returnStdout: true, script: 'xq -r .project.modules.module[] pom.xml 2>/dev/null || xq -r .project.modules.module pom.xml 2>/dev/null || echo empty').trim() - // if (modules == 'empty' || modules == 'null') { - // return null - // } - // modules = modules.split("\n") - // return modules - // } - // else { - // return null - // } - return ["server"] -} - - -def getArtifactId(){ - // if (fileExists("pom.xml")) { - // return sh(returnStdout: true, script: 'xq -r .project.artifactId pom.xml').trim() - // } else if (fileExists("package.json")) { - // return sh(returnStdout: true, script: 'jq -r .name package.json').trim() - // } - return "external-payment-gateway" -} - - def getTag(){ - def buildenv = "${env.BUILD_ENV}" - echo "========get version========" - def version = getVersion() - echo "========commitID========" - def commitID = getCommitid() - echo "========tag========" - def tag = "v${version}-${commitID}" - return ${tag} -} - - - -def release(Map config){ - stage('Push docker image'){ - def repo_name = config.repo_name - def branch_name = "${env.BRANCH_NAME}" - def ecrImagePod = config.ecrImagePod - def bu = config.BU - def buildenv = "${env.BUILD_ENV}" - echo "========registry========" - def registry = registry("${buildenv}") - echo "========get version========" - def version = getVersion() - echo "========commitID========" - def commitID = getCommitid() - echo "========tag========" - echo "registry is ${registry}" - def tag = "v${version}-${commitID}" - echo "${registry}" - echo "tag is ${tag}" - def artifactId = getArtifactId() - def modules = getModules() - def excludedModules = config.excludedModules ?: [] - - log.info("########################### Pushing Docker Images ###########################") - if (modules == null){ - echo "docker tag ${ecrImagePod}/${repo_name} ${registry}/${bu}/${ecrImagePod}/${repo_name}:${tag}" - echo "gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet" - echo "docker push ${registry}/${bu}/${ecrImagePod}/${repo_name}:${tag}" - }else { - j = 0 - for (module in modules){ - if (excludedModules.contains(module)){ - echo "===== NOT Pushing container for Module - ${common} =====" - }else { - j+=1 - sh """ - echo "===== ${j}. Pushing Container for Module - ${module} ======" - echo "docker tag ${ecrImagePod}/${repo_name}/${module} ${registry}/${bu}/${ecrImagePod}/${repo_name}/${module}:${tag}" - echo "gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet" - echo "docker push ${registry}/${bu}/${ecrImagePod}/${repo_name}/${module}:${tag}" - """ - } - } - } - - } -} - -def getDockerfile(String dockerBuildVersion, String repo_name, String version, String module, String doBuild) { - if (dockerBuildVersion) { - if(dockerBuildVersion == "maven-3.3-jdk-8"){ - def scriptcontents = libraryResource "org/homelab/templates/${dockerBuildVersion}.sh" - writeFile file: "${dockerBuildVersion}.sh", text: scriptcontents - sh "chmod a+x ./${dockerBuildVersion}.sh;./${dockerBuildVersion}.sh ${module} ${version} ${doBuild} ${repo_name}" - return "-f Dockerfile-${module} ." - }else if(dockerBuildVersion == "node-12.22"){ - def scriptcontents = libraryResource "org/homelab/templates/${dockerBuildVersion}.sh" - writeFile file: "${dockerBuildVersion}.sh", text: scriptcontents - sh "chmod a+x ./${dockerBuildVersion}.sh;./${dockerBuildVersion}.sh ${module}" - return "-f Dockerfile-${module} ." - } - } else { - return "-f Dockerfile ." - } -} diff --git a/vars/buildPipeline.groovy b/vars/buildPipeline.groovy deleted file mode 100644 index e9bc318..0000000 --- a/vars/buildPipeline.groovy +++ /dev/null @@ -1,43 +0,0 @@ -import com.homelab.stages.checkOut -import com.homelab.stages.buildObjHelper -import com.homelab.stages.notify -import com.homelab.stages.securityScan -import com.homelab.stages.automationTest -import com.homelab.utilities.constructParam - -def call(Map param) { - def constructParam = new constructParam() - def checkObj = new checkOut() - def buildObjHelper = new buildObjHelper() - def buildObj = buildObjHelper.run(param.build_tool) - def notify = new notify() - def securityScan = new securityScan() - def automationTest = new automationTest() - node('slave02') { - env.msg = 'Job Passed' - timestamps { - ansiColor('xterm') { - try { - constructParam.run(param) - log.info(param) - checkObj.run(param) - // securityScan.run(param) - buildObj.run(param) - if (param.containsKey('run_automation') && env.CHANGE_ID) { - automationTest.run(param) - } - } - catch (Exception e) { - if (env.msg == 'Job Passed') { - log.error(e.toString()) - currentBuild.result = env.FAILURE - env.msg = 'Job Failed. Error: ' + e.toString() - } - } - finally { - notify.run(param) - } - } - } - } -} diff --git a/vars/cdHookRunner.groovy b/vars/cdHookRunner.groovy deleted file mode 100644 index e8540fa..0000000 --- a/vars/cdHookRunner.groovy +++ /dev/null @@ -1,195 +0,0 @@ -// cdHookRunner — standalone Jenkins job that runs turbo-turtle deploy-phase -// CI/CD hooks (pre_deploy / post_deploy) in an isolated agent pod. -// -// turbo-turtle triggers this parameterized job during the CD workflow (see -// TriggerCdHookPipelineActivity). It checks out the target repo, reads the -// deploy-phase hooks declared under `environment..hooks.` in -// the per-app deployment file (deployments/.yaml), executes them via -// the shared runHooks execution core, and POSTs an aggregate completion callback -// to /api/v1/cd/hook/callback which signals the waiting workflow. A blocking hook -// failure makes success=false so the workflow can fail the deploy; advisory -// (blocking:false) failures keep success=true. -// -// Required parameters (Jenkins job params, read from params/env): -// REPO_URL, TT_REPO, APP_NAME, BRANCH, COMMIT, TT_ENV, HOOK_PHASE, -// IMAGE_TAG, TT_IS_HOTFIX, TT_PR_NUMBER, TT_WORKFLOW_ID, TT_RUN_ID -def call(Map jobParams = [:]) { - String repoUrl = param('REPO_URL', jobParams) - String repoName = param('TT_REPO', jobParams) - String appName = param('APP_NAME', jobParams) - String branch = param('BRANCH', jobParams) - String commit = param('COMMIT', jobParams) - String ttEnv = param('TT_ENV', jobParams) - String phase = param('HOOK_PHASE', jobParams) - String workflowID = param('TT_WORKFLOW_ID', jobParams) - String runID = param('TT_RUN_ID', jobParams) - - if (!repoUrl && repoName) { - repoUrl = "https://github.com/Homelab/${repoName}" - } - // TT_WORKFLOW_ID / TT_RUN_ID are required: the completion callback correlates - // to the waiting Temporal run by them. If either is missing (e.g. incomplete - // job-param registration), fail before checkout/hook execution rather than run - // hooks whose callback can never be matched (workflow would wait to timeout). - if (!repoName || !appName || !ttEnv || !phase || !workflowID || !runID) { - error("cdHookRunner: TT_REPO, APP_NAME, TT_ENV, HOOK_PHASE, TT_WORKFLOW_ID and TT_RUN_ID are required parameters.") - } - // Make env available to stageName / the hook context. - env.cicd_environment = ttEnv - - String podyaml = "org/homelab/${env.INFRA_ENV ?: 'prd'}-pod.yaml" - podTemplate(yaml: libraryResource(podyaml)) { - node(POD_LABEL) { - container('devops-tools') { - boolean success = true - String errMsg = '' - try { - stage(stageName("cd-hook checkout: ${repoName}")) { - deleteDir() - dir(repoName) { - checkout([ - $class: 'GitSCM', - branches: [[name: commit ?: "*/${branch}"]], - userRemoteConfigs: [[url: repoUrl, credentialsId: 'svc-devops-homelab']], - extensions: [[$class: 'CloneOption', shallow: false, noTags: false]], - ]) - } - } - - def hooks = resolveHooks(repoName, appName, ttEnv, phase) - if (!hooks) { - log.info("cdHookRunner: no ${phase} hooks declared for env ${ttEnv} in ${repoName}/deployments/${appName}.yaml — nothing to run.") - } else { - List ctxEnv = deployPhaseContextEnv(repoName, ttEnv, jobParams) - runHooks.executeHooks(repoName, hooks, phase, ctxEnv) - } - } catch (Exception e) { - success = false - errMsg = e.toString() - log.error("cdHookRunner: ${phase} hook run failed for ${repoName} (env ${ttEnv}): ${errMsg}") - } finally { - postCdHookCallback(repoName, ttEnv, phase, success, errMsg, workflowID, runID) - } - if (!success) { - // Surface as a build failure too (the workflow reads the callback, - // but a red build aids debugging). - currentBuild.result = 'FAILURE' - error("cdHookRunner: ${phase} hooks failed for ${repoName}: ${errMsg}") - } - } - } - } -} - -// resolveHooks reads the per-app deployment file (deployments/.yaml) and -// returns the deploy-phase hook list for env+phase (or null). Deploy-phase hooks -// live in deployment.yaml, not config.yaml. -def resolveHooks(String repoName, String appName, String ttEnv, String phase) { - dir(repoName) { - String deployFile = "deployments/${appName}.yaml" - if (!fileExists(deployFile)) { - error("cdHookRunner: ${deployFile} not found in ${repoName}.") - } - def deployment = readYaml file: deployFile - def envBlock = deployment?.environment?.get(ttEnv) - def hooksBlock = envBlock?.hooks - if (!(hooksBlock instanceof Map)) { - return null - } - def hooks = hooksBlock[phase] - return (hooks instanceof List && !hooks.isEmpty()) ? hooks : null - } -} - -// deployPhaseContextEnv builds the TT_* context contract from the job params. -def deployPhaseContextEnv(String repoName, String ttEnv, Map jobParams) { - return [ - "TT_REPO_NAME=${repoName}", - "TT_ENV=${ttEnv}", - "TT_EVENT=push", - "TT_IS_HOTFIX=${param('TT_IS_HOTFIX', jobParams) ?: 'false'}", - "TT_BRANCH=${param('BRANCH', jobParams) ?: ''}", - "TT_TARGET_BRANCH=", - "TT_PR_NUMBER=${param('TT_PR_NUMBER', jobParams) ?: ''}", - "TT_COMMIT_SHA=${param('COMMIT', jobParams) ?: ''}", - "TT_IMAGE_TAG=${param('IMAGE_TAG', jobParams) ?: ''}", - ] -} - -// postCdHookCallback POSTs the aggregate result to turbo-turtle, which signals -// the waiting CD workflow. Env-routed exactly like the Jenkins CI callback. -def postCdHookCallback(String repoName, String ttEnv, String phase, boolean success, String errMsg, String workflowID, String runID) { - String baseUrl - switch (ttEnv) { - case 'prd': - case 'int': - baseUrl = 'http://turbo-turtle.homelabgcp.in' - break - default: - baseUrl = 'http://turbo-turtle.admin.homelabgcp.in' - } - Map payload = [ - repo_name : repoName, - env : ttEnv, - phase : phase, - success : success, - error : errMsg, - build_url : env.BUILD_URL ?: '', - workflow_id: workflowID, - run_id : runID, - ] - String url = baseUrl + "/api/v1/cd/hook/callback" - String jsonFilePath = "cd_hook_callback_${env.BUILD_NUMBER}.json" - int maxAttempts = 3 - // The callback is turbo-turtle's ONLY completion signal for the waiting run. - // Retry delivery (bounded, with connect/request timeouts) and, if every attempt - // fails, fail the job so the failure is visible — otherwise the job goes green - // while the workflow waits until its backstop timeout. Duplicate delivery is - // safe: turbo-turtle correlates callbacks by workflow/run/repo/env/phase. - try { - writeFile(file: jsonFilePath, text: writeJSON(returnText: true, json: payload)) - for (int attempt = 1; attempt <= maxAttempts; attempt++) { - boolean delivered = false - try { - final def(String response, String code) = sh( - returnStdout: true, - script: """ - curl -s --connect-timeout 10 --max-time 30 -X POST \\ - -H 'Content-Type: application/json' \\ - -w '\\n%{response_code}' \\ - $url \\ - -d @$jsonFilePath - """ - ).trim().tokenize("\n") - if (code == "200") { - log.info("cdHookRunner: callback delivered (attempt ${attempt}/${maxAttempts})") - delivered = true - } else { - log.warn("cdHookRunner: callback attempt ${attempt}/${maxAttempts} failed code=${code} response=${response}") - } - } catch (Exception e) { - log.warn("cdHookRunner: callback attempt ${attempt}/${maxAttempts} error: ${e}") - } - if (delivered) { - return - } - if (attempt < maxAttempts) { - sleep(time: attempt * 5, unit: 'SECONDS') - } - } - error("cdHookRunner: callback POST to ${url} failed after ${maxAttempts} attempts; turbo-turtle will not receive completion for repo=${repoName} env=${ttEnv} phase=${phase}") - } finally { - sh(script: "rm -f ${jsonFilePath}", returnStatus: true) - } -} - -// param reads a job parameter, preferring an explicit map, then params, then env. -def param(String key, Map jobParams) { - if (jobParams?.containsKey(key)) { - return jobParams[key]?.toString() - } - if (params?.containsKey(key) && params[key] != null) { - return params[key].toString() - } - return env[key]?.toString() -} diff --git a/vars/cloudFunctionCICD.groovy b/vars/cloudFunctionCICD.groovy deleted file mode 100644 index d69f187..0000000 --- a/vars/cloudFunctionCICD.groovy +++ /dev/null @@ -1,30 +0,0 @@ - -pipeline { - agent none - - environment { - K8S_LABEL = 'cloud-function-cicd-agent' - GITHUB_CRED = 'cicd-github-app' - } - - podTemplate(yaml: libraryResource('org/homelab/pod-cloud-function.yaml')) { - node(POD_LABEL) { - container('devops-tools') { - cloudFunctionCICDFlow() - } - } - } -} - -def cloudFunctionCICDFlow() { - stages { - stage { - steps { - script { - sh 'ls -al' - echo 'Hello World' - } - } - } - } -} diff --git a/vars/createEKSconfigs.groovy b/vars/createEKSconfigs.groovy deleted file mode 100644 index 7ec9244..0000000 --- a/vars/createEKSconfigs.groovy +++ /dev/null @@ -1,24 +0,0 @@ -import com.homelab.stages.helmGenerator -import com.homelab.stages.validateBuTeam - -def call(Map params) { - podTemplate(yaml: libraryResource("org/homelab/${env.INFRA_ENV}-pod.yaml")) { - node(POD_LABEL) { - container('devops-tools') { - timestamps { - ansiColor('xterm') { - env.GITHUB_CRED = 'svc-devops-homelab' - // Initialize objects - def validateObj = new validateBuTeam() - def hemlGenObj = new helmGenerator() - - // validate parameters - validateObj.run(params.bu, params.team, params.module) - // Create helm file - hemlGenObj.run(params) - } - } - } - } - } -} diff --git a/vars/eksCICD.groovy b/vars/eksCICD.groovy deleted file mode 100644 index dff115b..0000000 --- a/vars/eksCICD.groovy +++ /dev/null @@ -1,155 +0,0 @@ -import com.homelab.stages.hotFix -import com.homelab.stages.checkOut -import com.homelab.stages.buildObjHelper -import com.homelab.stages.notify -import com.homelab.utilities.getYamlParameter -import com.homelab.utilities.constructParam -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatterBuilder - -def call(Map repo) { - ansiColor('xterm') { - def userId = null - - try { - def cause = currentBuild.rawBuild.getCause(hudson.model.Cause.UserIdCause) - if (cause) { - userId = cause.getUserId() - } - } catch (Exception e) { - log.error("Failed to get user ID: ${e.message}") - } - - // Per-user allowlist removed — it hardcoded real people's emails - // from the original org and doesn't apply to a solo homelab. - // This whole eksCICD path is unused legacy code anyway (nothing - // in homelabPipeline.groovy calls it); the branch below is now - // permanently skipped rather than deleted, to avoid hand-editing - // the escape-sequence-heavy echo blocks it guards. - if (false) { - echo "\u001B[1;31m========================================\n[ERROR] Build triggered by unauthorized user: ${userId}\n\nPlease use Ringmaster to trigger builds and deployments: https://ringmaster.homelabgcp.in/applications/cicd/home\n========================================\u001B[0m" - error("Build triggered by unauthorized user: ${userId}.") - } else if (!userId) { - echo "\u001B[1;31m========================================\n[ERROR] Could not determine the user who triggered the build\n\nThis might be a scheduled or system-triggered build.\n========================================\u001B[0m" - error("Could not determine the user who triggered the build") - } else { - echo "Starting the build....." - } - } - - env.STARTTIME = getDateTime() - env.FAILURE = 'FAILURE' - echo "CLOUD_PROVIDER : ${env.CLOUD_PROVIDER}" - echo "INFRA_ENV : ${env.INFRA_ENV}" - switch (env.CLOUD_PROVIDER) { - case 'GCP': - echo 'Running on GCP' - gcpInfra(repo) - break - case 'AWS': - echo 'Running on AWS' - awsInfra(repo) - break - default: - log.error('Not running on AWS or GCP') - currentBuild.result = env.FAILURE - def msg = 'Job Failed. Error: Not running on AWS or GCP' - notify.run(msg) - } -} - -def gcpInfra (Map repo) { - def isSidecarNeeded = repo.get('useSidecar', false) - def yamlName = isSidecarNeeded ? "${env.INFRA_ENV}-sidecar-pod.yaml" : "${env.INFRA_ENV}-pod.yaml" - def podyaml = "org/homelab/${yamlName}" - echo "Architecture Check: useSidecar=${isSidecarNeeded}. Loading ${podyaml}" - podTemplate(yaml: libraryResource(podyaml)) { - node(POD_LABEL) { - container('devops-tools') { - commonCICDFlow(repo) - } - } - } -} - -def awsInfra (Map repo) { - node('EKS') { - commonCICDFlow(repo) - } -} - -def commonCICDFlow (Map repo) { - def constructParam = new constructParam() - def ymlObj = new getYamlParameter() - def checkObj = new checkOut() - def buildObjHelper = new buildObjHelper() - def hotFixObj = new hotFix() - def notify = new notify() - def param = [:] - def msg = 'Job Passed' - env.msg = msg - env.error_msg_to_db = '' - - timestamps { - ansiColor('xterm') { - try { - env.deploymentStartTime = new Date().format('yyyy-MM-dd HH:mm:ss') - checkObj.run(repo) - param = ymlObj.getParam(repo.repo_name) - def maintainer = param.maintainer ?: 'jenkins-user' - def buildObj = buildObjHelper.run(param.build_tool) - constructParam.run(param) - param['skip_notify'] = env.skip_notify - log.info(param) - hotFixObj.run(param.repo_name) - // Pre-build hooks: user scripts declared under - // environment..hooks.pre_build in the repo's config.yaml. - // constructParam has merged the resolved env block into param, - // so param.hooks holds the current environment's hooks. A - // blocking hook failure fails the build before the docker build. - runHooks(param, 'pre_build') - buildObj.run(param) - // Post-build hooks: run after a successful build. - runHooks(param, 'post_build') - // Auto-trigger the standalone ai-blitz-jobs (coverage-only) job - // once the per-repo CI has succeeded. Internally gated on - // PR / hot-fix / toolchain / mainline-branch (see helper), and - // fire-and-forget — coverage observability must never block - // or fail this build. Repos opt out via skip_coverage_trigger - // in config.yaml. - triggerCoverageOnly(param) - } - catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) { - currentBuild.result = 'ABORTED' - log.info(currentBuild.result) - env.msg = 'Job Aborted' - env.error_msg_to_db = 'Job ABORTED by the User' - } - catch (Exception e) { - if (env.msg == msg) { - log.error(e.toString()) - currentBuild.result = env.FAILURE - env.msg = 'Job Failed. Error: ' + e.toString() - } - } - finally { - env.deploymentEndTime = new Date().format('yyyy-MM-dd HH:mm:ss') - notify.run(param) - } - } - } -} - -@NonCPS -def getDateTime() { - // Get the current date and time in IST - def currentDateTime = ZonedDateTime.now() - // Create a formatter for the desired pattern - def formatter = new DateTimeFormatterBuilder() - .appendPattern("yyyy-MM-dd'T'HH:mm:ss") - .appendOffset('+HH:mm', '+00:00') - .toFormatter() - // Format the current date and time using the formatter - def formattedDateTime = currentDateTime.format(formatter) - return formattedDateTime -} diff --git a/vars/gcpMigration.groovy b/vars/gcpMigration.groovy deleted file mode 100644 index 8090f05..0000000 --- a/vars/gcpMigration.groovy +++ /dev/null @@ -1,278 +0,0 @@ -import com.homelab.utilities.gitActions -import com.homelab.utilities.buTeamMapping -import com.homelab.utilities.getYamlParameter -import com.homelab.utilities.constructTemplate -import com.homelab.stages.multiBranchPipeline -import com.homelab.stages.deployArgoCD - -def call(Map params){ - podTemplate(yaml: libraryResource('org/homelab/pod.yaml')) { - node(POD_LABEL) { - container('devops-tools') { - timestamps{ - ansiColor("xterm"){ - env.GITHUB_CRED = 'svc-devops-homelab' - def gitObj = new gitActions() - def bu_team_obj = new buTeamMapping() - def constructObj = new constructTemplate() - def application_info_map = [:] - def repo_bu = "" - stage('App Repo: cloning the repo'){ - gitObj.clone("${WORKSPACE}", params.repo_name, params.repo_main_branch) - } - - stage('APP Repo: Create Jenkinsfile(gcp-main)'){ - if (!params.skip_jenkinsfile){ - def repo_branch_name = "gcp-main" - def config = ["repo_name":params.repo_name] - gitObj.branchCheckOut(params.repo_name, repo_branch_name) - dir(params.repo_name){ - sh "chmod 777 Jenkinsfile" - constructObj.renderTemplate(config, 'Jenkinsfile', 'Jenkinsfile') - gitObj.add('.', 'Jenkinsfile') - } - def commit_status = gitObj.codeCommit(repo_name, repo_branch_name, 'Generating build and deployments files') - if (commit_status == 0) { - gitObj.codePush(repo_name, repo_branch_name) - } - } - else { - log.info("skipping this stage") - } - } - - stage('Helm Repo: Update values_property file'){ - def yaml_obj = new getYamlParameter() - gitObj.clone("${WORKSPACE}","devops-helm-charts","main") - def helm_repo_name = "devops-helm-charts" - def helm_checkout_branch = "gcp-migration-${params.repo_name}-${env.BUILD_NUMBER}" - def repo_branch_name = "gcp-main" - gitObj.branchCheckOut("devops-helm-charts", helm_checkout_branch) - gitObj.branchCheckOut(params.repo_name, repo_branch_name) - def build_config = yaml_obj.getParam(params.repo_name) - - if (build_config['copy_file'] || (build_config['environment'] && build_config['environment']['prd']['copy_file'])){ - // voilating naming convention willing to avoid confilct with deployment variables - log.info("Checking copy_file path") - def buSrt = bu_team_obj.get_bu_initials(build_config["bu"]) - def teamSrt = bu_team_obj.get_team_initials(build_config["team"]) - def copy_path = (build_config['copy_file'])? build_config.copy_file.path : build_config.environment.prd.copy_file.path - if (copy_path.startsWith("gs:")){ - log.info("Copy file path already pointing to gcs") - } else { - def file_name = copy_path.split('/')[-1] - def new_path = "gs://gcs-${buSrt}-${teamSrt}-config-prd/${params.repo_name}/${file_name}" - if(build_config.copy_file){ - build_config.copy_file.path = new_path - } else { - build_config.environment.prd.copy_file.path = new_path - } - dir(params.repo_name){ - sh "chmod 777 config.yaml" - writeYaml file: 'config.yaml', data: build_config, overwrite: true - } - gitObj.add(params.repo_name, 'config.yaml') - def config_commit = gitObj.codeCommit(repo_name, repo_branch_name, 'Changed copy_file path to gcs') - if (config_commit == 0) { - gitObj.codePush(repo_name, repo_branch_name) - } - log.warning("Path for copy_file has been changed to gcs. Please upload the contents to ${new_path} from ${copy_path}") - } - } - if (build_config.containsKey('environment')) { - Map envrionment_config = build_config['environment'].collectEntries { key, value -> "prd".matches(key)? value: [:]} - build_config.remove('environment') - build_config.putAll(envrionment_config) - } - repo_bu = build_config.bu - env.skip_user_input = params.skip_userinput - def wait_obj = new deployArgoCD() - def deployment_order = build_config.deployment_order - timeout(unit: 'SECONDS', time: 300) { - userInput = wait_obj.wait_for_user_input(build_config.deployment_order) - } - if (userInput == '') { - log.info('No Deployments selected. Running remaining steps.') - return - } - else if (!userInput.contains('All')) { - deployment_order = userInput.split(',') as List - } - - for (deployment in deployment_order){ - def application_config = yaml_obj.getParam(params.repo_name,"deployments/${deployment}.yaml") - def bu_short = bu_team_obj.get_bu_initials(application_config["bu"]) - def BU = application_config["bu"] - def team_short = bu_team_obj.get_team_initials(application_config["team"]) - //def app_name_short = application_config.app_name.substring(0, Math.min(originalString.length(), 15)) - def app_name_short = application_config.app_name - def values_version = env.cicd_environment == 'prd' ? 'values_v3' : 'values_v2' - def value_properties_path = "${values_version}/${bu_short}/${team_short}/${deployment}/values_properties.yaml" - def value_properties_content = yaml_obj.getParam(helm_repo_name,value_properties_path) - def canary_default = ['progressDeadlineSeconds': 300,'analysisInterval': '120s','analysisThreshold': 5,'analysisMaxWeight': 5,'analysisStepWeight': 5,'analysisMetrics':['thresholdRangeMin': 0.99,'interval': '1m'],'skipAnalysis': true] - - // Set/change value_properties values for migration - value_properties_content['as_min']= 1 - log.info("Setting canary skipAnalysis to true") - if (value_properties_content['canary']){ - value_properties_content['canary']['skipAnalysis'] = true - } else { - value_properties_content['canary'] = canary_default - } - log.info("Canary skipAnalysis has been set to true") - log.info("set min and nodeselector") - if (value_properties_content['ingress_class']!= "contour-external"){ - if(value_properties_content['host']){ - value_properties_content.host = application_config['app_name']+".prd.homelab.int" - } - else if(value_properties_content.hosts){ - for (host_arr in value_properties_content.hosts){ - host_arr.host = application_config['app_name']+".prd.homelab.int" - } - } - if (application_config.priority_v2 == "up0" || application_config.priority_v2 == "sp0" || application_config.priority_v2 == "cp0"){ - value_properties_content.ingress_class = "contour-internal-0" - } - else{ - value_properties_content.ingress_class = "contour-internal-1" - } - } - else if (value_properties_content['ingress_class'] == "external"){ - value_properties_content.ingress_class = "contour-external" - } - log.info("set host") - if (value_properties_content.serviceAccount){ - value_properties_content.serviceAccount.annotations.remove('eks.amazonaws.com/role-arn') - value_properties_content.serviceAccount.annotations['iam.gke.io/gcp-service-account'] = "sa-${bu_short}-prd-${app_name_short}@homelab-${BU}-prd-0622.iam.gserviceaccount.com" - } - log.info("set service account") - dir(helm_repo_name){ - sh "chmod 777 ${value_properties_path}" - writeYaml file: value_properties_path, data: value_properties_content, overwrite: true - } - - def host = (value_properties_content.host)? value_properties_content.host : value_properties_content.hosts[0].host - log.info(host) - application_info_map[application_config.app_name]=[ - "bu": application_config.bu, - "team": application_config.team, - "host": host, - "ingress_class": value_properties_content.ingress_class - ] - log.info(application_info_map) - gitObj.add(helm_repo_name,value_properties_path) - } - commit_status = gitObj.codeCommit(helm_repo_name, helm_checkout_branch, "Modified all application of ${params.repo_name} repo") - if (commit_status == 0) { - try{ - gitObj.codePush(helm_repo_name, helm_checkout_branch) - } - catch(Exception e){ - log.error("Error in code push") - return - } - try{ - pr_num = gitObj.createPR(params.repo_name, helm_repo_name, "main", helm_checkout_branch, "Merge ${params.repo_name} changes") - gitObj.mergePR(helm_repo_name, pr_num, helm_checkout_branch) - gitObj.deleteBranch(helm_repo_name, "main", helm_checkout_branch) - } - catch(Exception ex){ - log.error("error in code push") - log.error(ex) - log.info("Deleting the branch") - gitObj.deleteBranch(helm_repo_name, "main", helm_checkout_branch) - } - } - } - stage('Jenkins: Create Jenkins job'){ - def job_obj = new multiBranchPipeline() - job_obj.applicationOnboard(repo_bu,params.repo_name) - log.info("Jenkins job created") - } - // stage('GCP: Create DNS record'){ - // def zone="pvt-homelab-admin-prd-homelab-int" - // def project="homelab-admin-prd-0622" - // def app_name_list = application_info_map.collect{ it.key } - // log.info(app_name_list) - // for (app_name in app_name_list) { - // def host=application_info_map[app_name].host - // def ingress_class=application_info_map[app_name].ingress_class - // def BU=application_info_map[app_name].bu - // def dns_status= sh(script: "gcloud dns --project=${project} record-sets describe ${host} --zone=${zone} --type='CNAME' ",returnStatus: true) - // if( dns_status != 0 ){ - // dns_status = sh(script: "gcloud dns --project=${project} record-sets create ${host} --zone=${zone} --type=CNAME --ttl='300' --rrdatas='${ingress_class}.${BU}.prd.prd.homelab.int.' ",returnStatus: true) - // if(dns_status == 0 ){ - // log.info("DNS recordSet ${host} created successfully") - // } - // else{ - // log.info("DNS recordset failed with status ${dns_status}") - // } - // } - // } - - // } - // stage('GCP: Create workload identity'){ - // def bu_short=bu_team_obj.get_bu_initials(application_info_map[app_name].bu) - // def BU = application_info_map[app_name].bu - // def project="homelab-${BU}-prd-0622" - // def app_name_list = application_info_map.collect{ it.key } - // for (app_name in app_name_list){ - // if (params.provide_service_role){ - // //def app_name_short= app_name.substring(0, Math.min(originalString.length(), 15)) - // def app_name_short=app_name - // def sa = "sa-${bu_short}-prd-${app_name_short}" - // sh(script: "gcloud iam service-accounts create ${sa} --display-name='service account for ${app_name}' --project=${project} ") - // sh(script: "gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member 'serviceAccount:${sa}@homelab-${BU}-prd-0622.iam.gserviceaccount.com' --project=homelab-${BU}-prd-0622") - // sh(script: "gcloud iam service-accounts add-iam-policy-binding --role roles/${params.SA_role} --member 'serviceAccount:${sa}@homelab-${BU}-prd-0622.iam.gserviceaccount.com' --project=homelab-${BU}-prd-0622") - // } - // } - // } - stage('CoreDNS: Add entry for svc-svc'){ - def yaml_obj = new getYamlParameter() - def coredns_repo_name = "devops-infra-helm-charts" - def coredns_checkout_branch = "gcp-migration-${params.repo_name}-${env.BUILD_NUMBER}" - gitObj.clone("${WORKSPACE}",coredns_repo_name,"main") - gitObj.branchCheckOut(coredns_repo_name, coredns_checkout_branch) - def coredns_path= "helm-templates/coredns/values.yaml" - def coredns_content = yaml_obj.getParam(coredns_repo_name,coredns_path) - def app_name_list = application_info_map.collect{ it.key } - for (app_name in app_name_list) { - def host=application_info_map[app_name].host - def ingress_class=application_info_map[app_name].ingress_class - def BU=application_info_map[app_name].bu - coredns_content.rewrites[host]="${ingress_class}-${BU}-prd" - log.info("coredns entry for ${host} is set") - dir(coredns_repo_name){ - sh "chmod 777 ${coredns_path}" - writeYaml file: coredns_path, data: coredns_content, overwrite: true - } - gitObj.add(coredns_repo_name,coredns_path) - } - commit_status = gitObj.codeCommit(coredns_repo_name, coredns_checkout_branch, "Modified all application of ${params.repo_name} repo") - if (commit_status == 0) { - try{ - gitObj.codePush(coredns_repo_name, coredns_checkout_branch) - } - catch(Exception e){ - log.error("Error in code push") - return - } - try{ - pr_num = gitObj.createPR(params.repo_name, coredns_repo_name, "main", coredns_checkout_branch, "Merge ${params.repo_name} changes") - gitObj.mergePR(coredns_repo_name, pr_num, coredns_checkout_branch) - gitObj.deleteBranch(coredns_repo_name, "main", coredns_checkout_branch) - } - catch(Exception ex){ - log.error("error in code push") - log.error(ex) - log.info("Deleting the branch") - gitObj.deleteBranch(coredns_repo_name, "main", coredns_checkout_branch) - } - } - } - } - } - } - } - } -} diff --git a/vars/gkeCICD.groovy b/vars/gkeCICD.groovy deleted file mode 100644 index 0bbf27e..0000000 --- a/vars/gkeCICD.groovy +++ /dev/null @@ -1,202 +0,0 @@ -import com.homelab.stages.buildDocker -import com.homelab.stages.notify - - -def Codecall(Map stepParams) { - def buildDocker = new buildDocker() - def notify = new notify() - checkOutCode() - config = readYaml file: "${stepParams.file}" - def maintainer = config.maintainer ?: "jenkins-user" - def msg = "Job Passed" - try{ - mvnBuild() - dockerBuild(config) - } - catch(Exception e){ - log.error(e.toString()) - currentBuild.result = env.FAILURE - msg = "Job Failed. Error: "+ e.toString() - } - finally{ - log.info(msg) - notify.run(msg,maintainer) - } - -} - -def checkOutCode () { - stage('Checking Out Code') { - checkout scm - } -} - -def mvnBuild() { - stage('Maven Build') { - container('maven') { - sh "mvn clean install -DskipTests" - } - } - } - -def Podcall(Map stepParams) { - podTemplate( - yaml: libraryResource('org/homelab/pod.yaml')) { - node(POD_LABEL) { - Codecall(file: "$stepParams.file") - } - } -} - -def dockerBuild(Map config) { - // container('dockerpush') { - buildDockerGroovyGke.run(config) - // buildDockerGroovyGke.release(config) - // } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// container('tools') { -// stage('Dockerfile creation') { -// if (config.dockerfilePresent == "Yes"){ -// sh ''' docker build -t "${config.gcrAddress}/${config.repo_name} -f ${config.dockerFileName}" ''' - -// } -// else{ -// def artifactId = config.artifactId -// def version = config.version -// def doBuild = "NO" -// sh "ls -al" -// echo "chmod after creating dockerfile" -// sh "cd server ; ls" -// // sh "cp /root/.m2/repository/com/homelab/payment/gateway/server/2.9.1/server-2.9.1.jar ." -// def dockerfile = buildDocker.getDockerfile(config.dockerBuildVersion, config.repo_name, version, artifactId, doBuild) -// echo "ls after creating dockerfile" -// sh "ls " - -// def tag = config.version - -// sh "gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet" -// sh "docker build -t asia-southeast1-docker.pkg.dev/supply-poc-351106/homelab-devops/external-payment-gateway:${tag} ${dockerfile}" -// sh "docker push asia-southeast1-docker.pkg.dev/supply-poc-351106/homelab-devops/external-payment-gateway:${tag}" - - - -// // echo "docker build -t ${config.gcrImagePod}/${config.repo_name} ${dockerfile}" -// // echo "docker tag ${config.gcrImagePod}/${config.repo_name} ${config.gcrAddress}/${config.gcrImagePod}/${config.repo_name}:${tag}" -// // echo "docker push ${config.gcrAddress}/${config.gcrImagePod}/${config.repo_name}:${tag}" -// } -// } -// } -// } diff --git a/vars/onlyPushtoJfrog.groovy b/vars/onlyPushtoJfrog.groovy deleted file mode 100644 index f59dc01..0000000 --- a/vars/onlyPushtoJfrog.groovy +++ /dev/null @@ -1,115 +0,0 @@ -pipeline { - agent { - kubernetes { - yamlFile "resources/org/homelab/${env.INFRA_ENV}-pod.yaml" - defaultContainer 'devops-tools' - } - } - - environment { - GITHUB_CRED = 'svc-devops-homelab' - } - - parameters { - string(name: 'repo_name', defaultValue: '', trim: true, description: 'Please enter repo name.') - string(name: 'branch_name', defaultValue: 'main', trim: true, description: 'Please enter branch name.') - choice(name: 'jdk_version', choices: ['jdk8', 'jdk11', 'jdk17', 'jdk21', 'jdk25'], description: 'Please select JDK Version. For adding new JDK Version, please contact DevOps Team.') - booleanParam(name: 'skip_tests', defaultValue: true, description: 'This option will skip tests while mvn build.') - booleanParam(name: 'sonar_scan', defaultValue: true, description: 'This option will perform static analysis of code via SonarQube.') - booleanParam(name: 'sub_modules', defaultValue: false, description: 'This option will enable the use of Git submodules.') - } - - stages { - stage('Validate Params') { - steps { - script { - echo '####################### Validating Parameters ###########################' - echo "Repo Name: ${params.repo_name}" - echo "Branch Name: ${params.branch_name}" - echo "JDK Version: ${params.jdk_version}" - echo "Skip Tests: ${params.skip_tests}" - echo "Sonar Scan: ${params.sonar_scan}" - echo "Sub module: ${params.sub_modules}" - } - } - } - stage('Build') { - steps { - ansiColor('xterm') { - script { - sh 'whoami' - def extra_args = '' - if (env.INFRA_ENV == 'prd') { - jfrog_repo = '-DuseProdRepo=true' - } - else { - jfrog_repo = '-DuseTestRepo=true' - } - if (skip_tests.toBoolean()) { - extra_args = '-DskipTests' - } - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh "git clone -b ${branch_name} https://github.com/Homelab/${repo_name}.git" - } - if (sub_modules.toBoolean()) { - dir("${repo_name}") { - withCredentials([gitUsernamePassword(credentialsId: "${env.GITHUB_CRED}", gitToolName: 'git-tool')]) { - sh(script:'git submodule init && git submodule update && git submodule update --recursive --remote') - } - } - } - - def effective_jdk = params.jdk_version - dir("${repo_name}") { - if (fileExists('config.yaml')) { - def config = readYaml file: 'config.yaml' - def dv = config?.dockerBuildVersion?.toString() ?: '' - def matcher = (dv =~ /maven-.*-jdk-(\d+)/) - if (matcher) { - effective_jdk = "jdk${matcher[0][1]}" - echo "Using JDK from config.yaml dockerBuildVersion: ${dv} -> ${effective_jdk}" - } - } - } - if (effective_jdk == 'jdk25') { - env.JAVA_HOME = '/usr/lib/jvm/java-25-openjdk-amd64/' - } - else if (effective_jdk == 'jdk21') { - env.JAVA_HOME = '/usr/lib/jvm/java-21-openjdk-amd64/' - } - else if (effective_jdk == 'jdk17') { - env.JAVA_HOME = '/usr/lib/jvm/java-17-openjdk-amd64/' - } - else if (effective_jdk == 'jdk11') { - env.JAVA_HOME = '/usr/lib/jvm/java-11-openjdk-amd64/' - } - else { - env.JAVA_HOME = '/usr/lib/jvm/java-8-openjdk-amd64/' - } - echo "Resolved JAVA_HOME: ${env.JAVA_HOME} (effective_jdk: ${effective_jdk})" - dir("${repo_name}") { - echo '####################### Building Artifacts ###########################' - sh """ - pwd - ls -al - mvn -v - mvn clean install ${extra_args} - """ - if (sonar_scan.toBoolean()) { - withSonarQubeEnv('sonarqube-test') { - echo '####################### Performing Sonar Scan ###########################' - sh(script: "JAVA_HOME='${env.JAVA_HOME}' mvn sonar:sonar -Dsonar.branch.name=${branch_name} -Dsonar.projectName=${repo_name}") - } - } - else { - echo '####################### Skipping Sonar Scan ###########################' - } - echo '####################### Pushing Artifacts to Jfrog ###########################' - sh(script: "mvn package deploy -DskipTests=${skip_tests} ${jfrog_repo}") - } - } - } - } - } - } -} diff --git a/vars/runHooks.groovy b/vars/runHooks.groovy deleted file mode 100644 index 8c0e16b..0000000 --- a/vars/runHooks.groovy +++ /dev/null @@ -1,160 +0,0 @@ -// runHooks — generic per-environment CI/CD hook runner (execution core). -// -// Executes user-provided scripts (shell or python) that live in the service -// repo and are declared under `environment..hooks.` in the repo's -// config.yaml. The platform stays generic: it only locates, contextualises -// (via TT_* env vars) and runs each script — all use-case logic lives in the -// script itself. -// -// Build-phase entry (call from commonCICDFlow, repo already checked out): -// runHooks(param, 'pre_build') // before docker build -// runHooks(param, 'post_build') // after a successful build -// -// Deploy-phase re-use (from cdHookRunner, after it checks out the repo): -// runHooks.executeHooks(repoName, hooks, phase, ctxEnv) -// -// By the time the build-phase entry runs, constructParam has merged the -// resolved environment block into `param` top-level, so the current env's hooks -// are at `param.hooks`. The repo is checked out into `${repoName}`, so scripts -// run with CWD at the repo root and can reach any repo file. -def call(Map param, String phase) { - def hooksBlock = param?.hooks - if (!(hooksBlock instanceof Map)) { - return - } - def hooks = hooksBlock[phase] - if (!(hooks instanceof List) || hooks.isEmpty()) { - return - } - - String repoName = param?.repo_name - if (!repoName) { - error("runHooks: param.repo_name is missing — cannot locate hook scripts.") - } - - executeHooks(repoName, hooks, phase, buildPhaseContextEnv(repoName, phase)) -} - -// executeHooks runs each hook spec in `hooks` inside the repo working directory, -// injecting `ctxEnv` (the TT_* context contract) plus a per-hook TT_HOOK_NAME. -// Reused by both the build-phase entry and the deploy-phase cdHookRunner. -def executeHooks(String repoName, def hooks, String phase, List ctxEnv) { - hooks.eachWithIndex { hook, idx -> - runOneHook(repoName, phase, idx, hook, ctxEnv) - } -} - -// runOneHook executes a single hook spec inside the repo working directory. -def runOneHook(String repoName, String phase, int idx, def hook, List ctxEnv) { - String name = (hook?.name ?: "${phase}-${idx}").toString() - String script = hook?.script?.toString() - String interpreter = hook?.interpreter?.toString() - String requirements = hook?.requirements?.toString() - // Blocking by default; only an explicit `blocking: false` demotes to advisory. - boolean blocking = !(hook?.blocking?.toString() == 'false') - int timeoutSeconds = 600 - if (hook?.timeout_seconds) { - try { timeoutSeconds = hook.timeout_seconds.toString().toInteger() } catch (ignored) { timeoutSeconds = 600 } - } - - if (!script) { - error("runHooks: ${phase}[${idx}] '${name}' has no 'script' path in config.yaml.") - } - assertRepoRelative(script, "${phase}[${idx}] '${name}' script") - if (requirements) { - assertRepoRelative(requirements, "${phase}[${idx}] '${name}' requirements") - } - - stage(stageName("${phase}: ${name}")) { - dir(repoName) { - if (!fileExists(script)) { - error("runHooks: ${phase} hook '${name}' script not found in repo: ${script}") - } - - List hookEnv = [] - hookEnv.addAll(ctxEnv) - hookEnv.add("TT_HOOK_PHASE=${phase}") - hookEnv.add("TT_HOOK_NAME=${name}") - String runCmd = buildRunCommand(script, interpreter, requirements) - - log.info("runHooks: executing ${phase} hook '${name}' (${script}), blocking=${blocking}, timeout=${timeoutSeconds}s") - withEnv(hookEnv) { - try { - timeout(time: timeoutSeconds, unit: 'SECONDS') { - sh(script: runCmd) - } - } catch (Exception e) { - if (blocking) { - log.error("runHooks: blocking ${phase} hook '${name}' failed: ${e}") - throw e - } - // Advisory hook — record and continue without failing the build. - log.warning("runHooks: advisory ${phase} hook '${name}' failed (non-blocking): ${e}") - currentBuild.description = (currentBuild.description ? currentBuild.description + " | " : "") + "hook(${name}) advisory-failed" - } - } - } - } -} - -// buildRunCommand resolves the interpreter (explicit > extension > shebang) and, -// when a python requirements file is given, provisions an ephemeral venv. -def buildRunCommand(String script, String interpreter, String requirements) { - String interp = interpreter - if (!interp) { - if (script.endsWith('.py')) { - interp = 'python3' - } else if (script.endsWith('.sh')) { - interp = 'bash' - } - } - - boolean isPython = (interp == 'python3' || interp == 'python' || script.endsWith('.py')) - if (requirements && isPython) { - String py = interp ?: 'python3' - return """ - set -e - ${py} -m venv .tt_hook_venv - . .tt_hook_venv/bin/activate - pip install --quiet --disable-pip-version-check -r ${requirements} - ${py} ${script} - """.stripIndent().trim() - } - - if (interp) { - return "set -e\n${interp} ${script}" - } - // No interpreter resolved — rely on the script's shebang. - return "set -e\nchmod +x ${script}\n./${script}" -} - -// buildPhaseContextEnv builds the standard TT_* env-var contract for build-phase -// hooks from the pipeline env already populated by constructParam / the GitHub -// Branch Source plugin. -def buildPhaseContextEnv(String repoName, String phase) { - boolean isPR = (env.CHANGE_ID ? true : false) - String branch = isPR ? (env.CHANGE_BRANCH ?: env.BRANCH_NAME ?: '') : (env.BRANCH_NAME ?: '') - boolean isHotfix = (env.hot_fix == 'true' || env.hot_fix == true) - return [ - "TT_REPO_NAME=${repoName}", - "TT_ENV=${env.cicd_environment ?: ''}", - "TT_EVENT=${isPR ? 'pr' : 'push'}", - "TT_IS_HOTFIX=${isHotfix}", - "TT_BRANCH=${branch}", - "TT_TARGET_BRANCH=${env.CHANGE_TARGET ?: ''}", - "TT_PR_NUMBER=${env.CHANGE_ID ?: ''}", - "TT_COMMIT_SHA=${env.GIT_COMMIT ?: ''}", - "TT_IMAGE_TAG=${env.image_tag ?: ''}", - ] -} - -// assertRepoRelative rejects absolute paths and parent-directory traversal so a -// hook can only execute code that lives inside the checked-out repo. -def assertRepoRelative(String p, String what) { - if (p.startsWith('/')) { - error("runHooks: ${what} path '${p}' must be repo-relative, not absolute.") - } - if (p == '..' || p.startsWith('../') || p.contains('/../')) { - error("runHooks: ${what} path '${p}' must not traverse outside the repo ('..').") - } -} diff --git a/vars/triggerCoverageOnly.groovy b/vars/triggerCoverageOnly.groovy deleted file mode 100644 index f9e7c4b..0000000 --- a/vars/triggerCoverageOnly.groovy +++ /dev/null @@ -1,92 +0,0 @@ -// Best-effort fire of the standalone ai-blitz-jobs (coverage-only) Jenkins -// job after a successful per-repo CI build. ai-blitz-jobs runs the -// coverage-only.Jenkinsfile from devops-lib@coverage-only-pipeline against -// the repo+branch we just built, so coverage and test-quality metrics flow -// to novaviz without anyone having to trigger the job manually. -// -// Gates — skip when triggering would waste compute or pollute the data: -// - PR builds (CHANGE_ID set; coverage on PRs is noise) -// - hot-fix builds (env.hot_fix == true) -// - toolchain image rebuilds (env.INFRA_ENV == 'toolchain') -// - explicit opt-out (skip_coverage_trigger: true in config.yaml) -// - non-develop branches (current policy: ONLY 'develop' fires. main / -// master / gcp-main / gcp-master are -// deploy-only branches at Homelab — tests -// already ran on the develop merge that -// produced their content, so re-running -// coverage on them would be duplicate -// spend. Expand this allowlist if a repo -// ships from a non-develop branch.) -// -// Failure is swallowed — observability must never block prod CI. The -// ai-blitz-jobs Jenkins job being renamed/disabled/missing logs a warning -// and continues; the parent build stays green. -def call(Map param) { - if (env.CHANGE_ID) { - log.info("triggerCoverageOnly: skipping PR build (CHANGE_ID=${env.CHANGE_ID}).") - return - } - if (env.hot_fix == 'true' || env.hot_fix == true) { - log.info("triggerCoverageOnly: skipping hot-fix build.") - return - } - if ((env.INFRA_ENV ?: '') == 'toolchain') { - log.info("triggerCoverageOnly: skipping toolchain (image-rebuild) build.") - return - } - if (param?.skip_coverage_trigger?.toString() == 'true') { - log.info("triggerCoverageOnly: skipping — repo opted out via config.yaml skip_coverage_trigger=true.") - return - } - - String branch = (env.BRANCH_NAME ?: '').trim() - Set allowed = ['develop'] - if (!allowed.contains(branch)) { - log.info("triggerCoverageOnly: skipping branch '${branch}' (only ${allowed} triggers coverage today).") - return - } - - String repoName = param?.repo_name - if (!repoName) { - log.warning("triggerCoverageOnly: param.repo_name missing — cannot construct REPO_URL. Skipping.") - return - } - // Homelab convention: every backend service lives at github.com/Homelab/. - // ai-blitz-jobs' coverage-only.Jenkinsfile takes the full URL as REPO_URL. - String repoUrl = "https://github.com/Homelab/${repoName}" - - // Absolute path. Per-repo CI jobs may live inside Jenkins folders - // (e.g. /Homelab/order-service); a relative 'ai-blitz-jobs' would try - // the parent folder first and 404 there. The leading slash anchors at - // the Jenkins root, where ai-blitz-jobs lives (confirmed by the URL - // shape http://jenkins-dev.../job/ai-blitz-jobs//). - String targetJob = '/ai-blitz-jobs' - - try { - log.info("triggerCoverageOnly: launching ${targetJob} for ${repoName} @ ${branch} (fire-and-forget).") - // The `build` step is in-process Jenkins RPC — no API tokens or - // credentials needed. It runs with the parent build's identity - // (UpstreamCause), and ai-blitz-jobs' coverage-only.Jenkinsfile - // calls coverageOnly() directly (NOT eksCICD), so the - // allowedUsers gate in eksCICD doesn't apply to this path. Bot - // and upstream-triggered builds both work. - // wait:false → parent build queues the downstream and moves on - // immediately. It never reads the downstream result, so `propagate` - // is a no-op here and intentionally omitted. The try/catch below is - // what shields the parent from step-level errors (job not found, - // bad params, queue full). - build( - job: targetJob, - parameters: [ - string(name: 'REPO_URL', value: repoUrl), - string(name: 'BRANCH', value: branch) - ], - wait: false, - quietPeriod: 0 - ) - } catch (Exception e) { - // ai-blitz-jobs renamed/disabled/queue-blocked/whatever — log and - // continue. We never block the parent build on observability. - log.warning("triggerCoverageOnly: build(job: '${targetJob}', …) threw: ${e}. Continuing — parent CI stays green.") - } -}