diff --git a/.github/workflows/script-mirror.yaml b/.github/workflows/script-mirror.yaml new file mode 100755 index 0000000..cf4bb83 --- /dev/null +++ b/.github/workflows/script-mirror.yaml @@ -0,0 +1,22 @@ +name: Repo sync with Cloud π Native + +on: + push: + branches: + - "main" + workflow_dispatch: + +jobs: + mirror: + name: Sync repo with Cloud π Native + runs-on: ubuntu-latest + steps: + - name: Checks-out repository + uses: actions/checkout@v3 + - name: Send a sync request to DSO api + run: | + sh ./ci/mirror.sh \ + -a "${{ env.DSO_API_URL }}" \ + -b "${{ github.ref_name }}" \ + -g "${{ secrets.GITLAB_TRIGGER_TOKEN }}" \ + -i "${{ secrets.GITLAB_PROJECT_ID }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/.gitlab-ci-dso.yml b/.gitlab-ci-dso.yml new file mode 100755 index 0000000..450627b --- /dev/null +++ b/.gitlab-ci-dso.yml @@ -0,0 +1,46 @@ +include: + - project: $CATALOG_PATH + file: + - vault-ci.yml + - kaniko-ci.yml + - sonar-ci.yml + ref: main + +default: + image: alpine:latest + +variables: + REGISTRY_URL: ${REGISTRY_HOST}/${PROJECT_PATH} + +stages: + - read-secret + - check-quality + - docker-build + +read_secret: + stage: read-secret + extends: .vault:read_secret + +sonarqube: + image: docker.io/sonarsource/sonar-scanner-cli:latest + stage: check-quality + variables: + SONAR_PROJECT_KEY: ${PROJECT_KEY} + extends: .sonar:sonar-scanner + allow_failure: true + +docker-build-client: + variables: + WORKING_DIR: ./apps/client + DOCKERFILE: ./Dockerfile + IMAGE_NAMES: tuto-client:1.0.0 tuto-client:latest + stage: docker-build + extends: .kaniko:build-push + +docker-build-server: + variables: + WORKING_DIR: ./apps/server + DOCKERFILE: ./Dockerfile + IMAGE_NAMES: tuto-server:1.0.0 tuto-server:latest + stage: docker-build + extends: .kaniko:build-push diff --git a/apps/client/.gitignore b/apps/client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/client/Dockerfile b/apps/client/Dockerfile new file mode 100755 index 0000000..48c4283 --- /dev/null +++ b/apps/client/Dockerfile @@ -0,0 +1,43 @@ +ARG BASE_IMAGE=docker.io/oven/bun:1.1.8 +ARG PROD_IMAGE=docker.io/bitnami/nginx:1.24.0 + +# Base +FROM ${BASE_IMAGE} AS base + +WORKDIR /app +COPY ./package.json ./bun.lockb ./ + + +# Install +FROM base AS install + +COPY ./ ./ +RUN bun install --ignore-scripts + + +# Dev +FROM base AS dev + +COPY --from=install /app/node_modules ./node_modules +COPY ./ ./ +ENTRYPOINT [ "bun", "run", "dev" ] + + +# Build +FROM base AS build + +ENV NODE_ENV=production +COPY --from=install /app/node_modules ./node_modules +COPY ./ ./ +RUN bun run build + + +# Prod +FROM ${PROD_IMAGE} AS prod + +USER 0 +COPY --chown=1001:0 --chmod=770 --from=build /app/dist /opt/bitnami/nginx/html/ +COPY --chown=1001:0 --chmod=660 ./nginx/nginx-static.conf /opt/bitnami/nginx/conf/server_blocks/default.conf +COPY --chown=1001:0 ./nginx/entrypoint.sh /docker-entrypoint-initdb.d/load-env.sh +USER 1001 +EXPOSE 8080 diff --git a/apps/client/bun.lockb b/apps/client/bun.lockb new file mode 100755 index 0000000..328a295 Binary files /dev/null and b/apps/client/bun.lockb differ diff --git a/apps/client/index.html b/apps/client/index.html new file mode 100644 index 0000000..b02a479 --- /dev/null +++ b/apps/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Cloud Pi Native + + +
+ + + diff --git a/apps/client/nginx/entrypoint.sh b/apps/client/nginx/entrypoint.sh new file mode 100755 index 0000000..7894dd7 --- /dev/null +++ b/apps/client/nginx/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +ROOT_DIR=/opt/bitnami/nginx/html + +populate () { + KEY=$(echo "app-$1" | tr '[:upper:]' '[:lower:]' | sed 's/_/-/g') + VALUE=$(eval "echo \${$1}") + sed -i 's|'${KEY}'|'${VALUE}'|g' $2 +} + + +echo "Replacing env constants in JS" +for file in $ROOT_DIR/assets/*.js; do + echo "Processing $file ..."; + + populate SERVER_HOST $file +done + +nginx -g 'daemon off;' diff --git a/apps/client/nginx/nginx-static.conf b/apps/client/nginx/nginx-static.conf new file mode 100755 index 0000000..133437f --- /dev/null +++ b/apps/client/nginx/nginx-static.conf @@ -0,0 +1,16 @@ +server { + listen 8080; + server_name localhost; + root /opt/bitnami/nginx/html; + index index.html; + + large_client_header_buffers 4 32k; + + location / { + try_files $uri $uri/ @rewrites; + } + + location @rewrites { + rewrite "^(.+)$" /index.html last; + } +} diff --git a/apps/client/package.json b/apps/client/package.json new file mode 100644 index 0000000..0d2b455 --- /dev/null +++ b/apps/client/package.json @@ -0,0 +1,16 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "bunx --bun vite --host --port 8080", + "build": "tsc && vite build", + "preview": "bunx --bun vite preview" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.2.0", + "@types/bun": "^1.1.2" + } +} diff --git a/apps/client/src/api.ts b/apps/client/src/api.ts new file mode 100644 index 0000000..c278dab --- /dev/null +++ b/apps/client/src/api.ts @@ -0,0 +1,11 @@ +const SERVER_HOST = import.meta.env.VITE_SERVER_HOST || 'app-server-host' + +export function getMsg(btn: HTMLButtonElement, msg: HTMLParagraphElement) { + const getMsg = async () => { + console.log({ env: import.meta.env }) + const res = await fetch(`${SERVER_HOST}/api`) + const { docUrl, githubUrl } = JSON.parse(await res.text()) + msg.innerHTML = `Welcome aboard !

Visit the documentation and the Github organization.` + } + btn.addEventListener('click', () => getMsg()) +} diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts new file mode 100644 index 0000000..97ae6b7 --- /dev/null +++ b/apps/client/src/main.ts @@ -0,0 +1,14 @@ +import './style.css' +import { getMsg } from './api.ts' + +document.querySelector('#app')!.innerHTML = ` +
+

Cloud Pi Native

+
+ +

+
+
+` + +getMsg(document.querySelector('#btn')!, document.querySelector('#msg')!) diff --git a/apps/client/src/style.css b/apps/client/src/style.css new file mode 100644 index 0000000..f9c7350 --- /dev/null +++ b/apps/client/src/style.css @@ -0,0 +1,96 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/apps/client/src/typescript.svg b/apps/client/src/typescript.svg new file mode 100644 index 0000000..d91c910 --- /dev/null +++ b/apps/client/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/src/vite-env.d.ts b/apps/client/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/client/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json new file mode 100644 index 0000000..75abdef --- /dev/null +++ b/apps/client/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/apps/server/.gitignore b/apps/server/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/apps/server/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile new file mode 100644 index 0000000..a905681 --- /dev/null +++ b/apps/server/Dockerfile @@ -0,0 +1,36 @@ +ARG BASE_IMAGE=docker.io/oven/bun:1.1.8 +ARG PROD_IMAGE=docker.io/oven/bun:1.1.8-slim + +# Base +FROM ${BASE_IMAGE} AS base + +WORKDIR /app +COPY ./package.json ./bun.lockb ./ + + +# Install +FROM base AS install + +COPY ./ ./ +RUN bun install --ignore-scripts + + +# Dev +FROM base AS dev + +COPY --from=install /app/node_modules ./node_modules +COPY ./ ./ +ENTRYPOINT [ "bun", "run", "dev" ] + + +# Prod +FROM ${PROD_IMAGE} AS prod + +ENV NODE_ENV=production +WORKDIR /app +COPY --chown=bun:root --from=install /app/package.json /app/package.json +COPY --chown=bun:root --from=install /app/node_modules /app/node_modules +COPY ./ ./ +USER bun +EXPOSE 8080/tcp +ENTRYPOINT [ "bun", "run", "/app/src/index.ts" ] diff --git a/apps/server/README.md b/apps/server/README.md new file mode 100644 index 0000000..5c8bc53 --- /dev/null +++ b/apps/server/README.md @@ -0,0 +1,15 @@ +# server + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run src/index.ts +``` + +This project was created using `bun init` in bun v1.1.8. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/apps/server/bun.lockb b/apps/server/bun.lockb new file mode 100755 index 0000000..99d0066 Binary files /dev/null and b/apps/server/bun.lockb differ diff --git a/apps/server/package.json b/apps/server/package.json new file mode 100644 index 0000000..7ac848b --- /dev/null +++ b/apps/server/package.json @@ -0,0 +1,15 @@ +{ + "name": "server", + "module": "src/index.ts", + "type": "module", + "scripts": { + "dev": "NODE_ENV=development bun run --hot ./src/index.ts", + "start": "bun run" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} \ No newline at end of file diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts new file mode 100644 index 0000000..b0f2679 --- /dev/null +++ b/apps/server/src/index.ts @@ -0,0 +1,16 @@ +const CORS_HEADERS = { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'OPTIONS, POST', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, +} + +const server = Bun.serve({ + port: 3000, + fetch(request) { + return new Response(JSON.stringify({ docUrl: 'https://cloud-pi-native.fr', githubUrl: 'https://github.com/cloud-pi-native' }), CORS_HEADERS) + }, +}) + +console.log(`Listening on ${server.url}`) diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/apps/server/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/ci/kind/README.md b/ci/kind/README.md new file mode 100644 index 0000000..34961e6 --- /dev/null +++ b/ci/kind/README.md @@ -0,0 +1,32 @@ +# Run kubernetes locally with docker + +## Prerequisite + +Download & install on your local machine : +- [kind](https://github.com/kubernetes-sigs/kind) +- [kubectl](https://github.com/kubernetes/kubectl) +- [helm](https://github.com/helm/helm) + +## Start cluster + +Put this directory in your git project, then : + +```sh +# Go to the root level of the git project +cd `git rev-parse --show-toplevel` + +# Start kind wrapper +sh "$(find . -d -name 'kind')"/run.sh -c create + +# Push your docker-compose images into the cluster +sh "$(find . -d -name 'kind')"/run.sh -c build -f docker-compose.yml + +# Stop kind wrapper +sh "$(find . -d -name 'kind')"/run.sh -c delete +``` + +## Cluster + +One single node is deployed but it can be customized in `./configs/kind-config.yml`. The cluster comes with [Traefik](https://doc.traefik.io/traefik/providers/kubernetes-ingress/) ingress controller installed with port mapping on both ports `80` and `443`. + +The node is using `extraMounts` to provide a volume binding between host working directory and `/app` to give the ability to bind mount volumes into containers during development. \ No newline at end of file diff --git a/ci/kind/configs/kind-config.yml b/ci/kind/configs/kind-config.yml new file mode 100644 index 0000000..7da4b52 --- /dev/null +++ b/ci/kind/configs/kind-config.yml @@ -0,0 +1,21 @@ +--- +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 30080 + hostPort: 80 + protocol: TCP + - containerPort: 30443 + hostPort: 443 + protocol: TCP + extraMounts: + - hostPath: ./ + containerPath: /app diff --git a/ci/kind/configs/traefik-values.yml b/ci/kind/configs/traefik-values.yml new file mode 100644 index 0000000..64844ea --- /dev/null +++ b/ci/kind/configs/traefik-values.yml @@ -0,0 +1,19 @@ +--- +providers: + kubernetesCRD: + namespaces: + - default + - traefik + kubernetesIngress: + namespaces: + - default + - traefik + +ports: + web: + nodePort: 30080 + websecure: + nodePort: 30443 + +service: + type: NodePort diff --git a/ci/kind/env/helm-values.dev.yaml b/ci/kind/env/helm-values.dev.yaml new file mode 100644 index 0000000..e8403bd --- /dev/null +++ b/ci/kind/env/helm-values.dev.yaml @@ -0,0 +1,37 @@ +ingress: + enabled: true + className: "" + annotations: {} + hosts: + - example.domain.local + +server: + image: + repository: tuto-monorepo/server + tag: dev + pullPolicy: Never + pullSecret: null + extraVolumes: + - name: dev-workspace + path: /app/apps/server/src + type: hostPath + extraVolumeMounts: + - name: dev-workspace + mountPath: /app/src + +client: + image: + repository: tuto-monorepo/client + tag: dev + pullPolicy: Never + pullSecret: null + extraVolumes: + - name: dev-workspace + path: /app/apps/client/src + type: hostPath + extraVolumeMounts: + - name: dev-workspace + mountPath: /app/src + env: + SERVER_HOST: null + VITE_SERVER_HOST: http://example.domain.local diff --git a/ci/kind/env/helm-values.prod.yaml b/ci/kind/env/helm-values.prod.yaml new file mode 100644 index 0000000..d0ff261 --- /dev/null +++ b/ci/kind/env/helm-values.prod.yaml @@ -0,0 +1,26 @@ +ingress: + enabled: true + className: "" + annotations: {} + hosts: + - host: example.domain.local + paths: + - path: /api/ + pathType: Prefix + +server: + image: + repository: tuto-monorepo/server + tag: prod + pullPolicy: Never + pullSecret: null + +client: + image: + repository: tuto-monorepo/client + tag: prod + pullPolicy: Never + pullSecret: null + env: + SERVER_HOST: null + VITE_SERVER_HOST: http://example.domain.local diff --git a/ci/kind/run.sh b/ci/kind/run.sh new file mode 100755 index 0000000..069bc76 --- /dev/null +++ b/ci/kind/run.sh @@ -0,0 +1,253 @@ +#!/bin/bash + +set -e +set -o pipefail + +# Colorize terminal +red='\e[0;31m' +no_color='\033[0m' + +# Get versions +DOCKER_VERSION="$(docker --version)" + +# Default +SCRIPTPATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +HELM_RELEASE_NAME="tuto-monorepo" +HELM_DIRECTORY="./helm" +HELM_ARGS="" + + +# Declare script helper +TEXT_HELPER="\nThis script aims to manage a local kubernetes cluster using Kind also known as Kubernetes in Docker. +Following flags are available: + + -c Command tu run. Multiple commands can be provided as a comma separated list. + Available commands are : + create - Create kind cluster. + clean - Delete images in kind cluster (keep only infra resources and ingress controller). + delete - Delete kind cluster. + build - Build, push and load docker images from compose file into cluster nodes. + load - Load docker images from compose file into cluster nodes. + dev - Run application in development mode. + prod - Run application in production mode. + + -d Domains to add in /etc/hosts for local services resolution. Comma separated list. This will require sudo. + + -f Path to the docker-compose file that will be used with Kind. + + -i Install kind. + + -t Tag used to deploy application images. + + -h Print script help.\n\n" + +print_help() { + printf "$TEXT_HELPER" +} + +# Parse options +while getopts hc:d:f:ik:t: flag; do + case "${flag}" in + c) + COMMAND=${OPTARG};; + d) + DOMAINS=${OPTARG};; + f) + COMPOSE_FILE=${OPTARG};; + i) + INSTALL_KIND=true;; + k) + KUBECONFIG_PATH=${OPTARG};; + t) + TAG=${OPTARG};; + h | *) + print_help + exit 0;; + esac +done + + +# Functions +install_kind() { + printf "\n\n${red}[kind wrapper].${no_color} Install kind...\n\n" + if [ "$(uname)" = "Linux" ]; then + OS="linux" + elif [ "$(uname)" = "Darwin" ]; then + OS="darwin" + else + printf "\n\nNo installation available for your system, plese refer to the installation guide\n\n" + exit 0 + fi + + if [ "$(uname -m)" = "x86_64" ]; then + ARCH="amd64" + elif [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then + ARCH="arm64" + fi + + KIND_VERSION="24.0.7" + curl -Lo ./kind "https://kind.sigs.k8s.io/dl/v${VERSION}/kind-${OS}-${ARCH}" + chmod +x ./kind + mv ./kind /usr/local/bin/kind + + printf "\n\n$(kind --version) installed\n\n" +} + +create_cluster () { + if [[ "$COMMAND" =~ "create" ]]; then + if [ -z "$(kind get clusters | grep 'kind')" ]; then + printf "\n\n${red}[kind wrapper].${no_color} Create Kind cluster\n\n" + + kind create cluster --config $SCRIPTPATH/configs/kind-config.yml + + printf "\n\n${red}[kind wrapper].${no_color} Install Traefik ingress controller\n\n" + + helm --kube-context kind-kind repo add traefik https://traefik.github.io/charts && helm repo update + helm --kube-context kind-kind upgrade \ + --install \ + --wait \ + --namespace traefik \ + --create-namespace \ + --values $SCRIPTPATH/configs/traefik-values.yml \ + traefik traefik/traefik + fi + fi +} + +build () { + printf "\n\n${red}[kind wrapper].${no_color} Build images into cluster node\n\n" + + cd $(dirname "$COMPOSE_FILE") \ + && docker buildx bake --file $(basename "$COMPOSE_FILE") --load \ + && cd - +} + +load () { + printf "\n\n${red}[kind wrapper].${no_color} Load images into cluster node\n\n" + + kind load docker-image $(cat "$COMPOSE_FILE" \ + | docker run -i --rm mikefarah/yq -o t '.services | map(select(.build) | .image)') +} + +clean () { + printf "\n\n${red}[kind wrapper].${no_color} Clean cluster resources\n\n" + + helm --kube-context kind-kind uninstall $HELM_RELEASE_NAME +} + +delete () { + printf "\n\n${red}[kind wrapper].${no_color} Delete Kind cluster\n\n" + + kind delete cluster +} + +dev () { + printf "\n\n${red}[kind wrapper].${no_color} Deploy application in development mode\n\n" + + helm --kube-context kind-kind upgrade \ + --install \ + --wait \ + --values $SCRIPTPATH/env/helm-values.dev.yaml \ + $HELM_RELEASE_NAME $HELM_DIRECTORY + + for i in $(kubectl --context kind-kind get deploy -o name); do + kubectl --context kind-kind rollout status $i -w --timeout=150s; + done +} + +prod () { + printf "\n\n${red}[kind wrapper].${no_color} Deploy application in production mode\n\n" + + if [ ! -z "$TAG" ]; then + HELM_ARGS="--set server.image.tag=$TAG --set client.image.tag=$TAG" + fi + helm --kube-context kind-kind upgrade \ + --install \ + --wait \ + --values $SCRIPTPATH/env/helm-values.prod.yaml \ + $HELM_ARGS \ + $HELM_RELEASE_NAME $HELM_DIRECTORY + + for i in $(kubectl --context kind-kind get deploy -o name); do + kubectl --context kind-kind rollout status $i -w --timeout=150s + done +} + + +# Script condition +if [ "$INSTALL_KIND" = "true" ] && [ -z "$(kind --version)" ]; then + install_kind +fi + +if [ -z "$(kind --version)" ]; then + echo "\nYou need to install kind to run this script.\n" + print_help + exit 1 +fi + +if [[ "$COMMAND" =~ "build" ]] && [ ! -f "$(readlink -f $COMPOSE_FILE)" ]; then + echo "\nDocker compose file $COMPOSE_FILE does not exist.\n" + print_help + exit 1 +fi + + +# Add local services to /etc/hosts +if [ ! -z "$DOMAINS" ]; then + printf "\n\n${red}[kind wrapper].${no_color} Add services local domains to /etc/hosts\n\n" + + FORMATED_DOMAINS="$(echo "$DOMAINS" | sed 's/,/\ /g')" + if [ "$(grep -c "$FORMATED_DOMAINS" /etc/hosts)" -ge 1 ]; then + printf "\n\n${red}[kind wrapper].${no_color} Services local domains already added to /etc/hosts\n\n" + else + sudo sh -c "echo $'\n\n# Kind\n127.0.0.1 $FORMATED_DOMAINS' >> /etc/hosts" + + printf "\n\n${red}[kind wrapper].${no_color} Services local domains successfully added to /etc/hosts\n\n" + fi +fi + + +# Deploy cluster with trefik ingress controller +if [[ "$COMMAND" =~ "create" ]]; then + create_cluster & + JOB_ID_CREATE="$!" +fi + + +# Build and load images into cluster nodes +if [[ "$COMMAND" =~ "build" ]]; then + build & + JOB_ID_BUILD="$!" + wait $JOB_ID_CREATE + wait $JOB_ID_BUILD + load +fi + + +# Load images into cluster nodes +if [[ "$COMMAND" =~ "load" ]]; then + wait $JOB_ID_CREATE + load +fi + + +# Clean cluster application resources +if [ "$COMMAND" = "clean" ]; then + clean +fi + + +# Deploy application in dev or test mode +if [[ "$COMMAND" =~ "dev" ]]; then + wait $JOB_ID_CREATE + dev +elif [[ "$COMMAND" =~ "prod" ]]; then + wait $JOB_ID_CREATE + prod +fi + + +# Delete cluster +if [ "$COMMAND" = "delete" ]; then + delete +fi diff --git a/ci/mirror.sh b/ci/mirror.sh new file mode 100755 index 0000000..38b4991 --- /dev/null +++ b/ci/mirror.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -e + +# Colorize terminal +red='\e[0;31m' +no_color='\033[0m' + +# Console step increment +i=1 + +# Default values +BRANCH_TO_SYNC=main + +# Declare script helper +TEXT_HELPER="\nThis script aims to send a synchronization request to DSO. +Following flags are available: + + -a Api url to send the synchronization request. + + -b Branch which is wanted to be synchronize. + Default is '$BRANCH_TO_SYNC'. + + -g GitLab token to trigger the pipeline on the gitlab mirror project. + + -m Gitlab mirror project id. + + -h Print script help.\n\n" + +print_help() { + printf "$TEXT_HELPER" +} + +# Parse options +while getopts :ha:b:g:i: flag +do + case "${flag}" in + a) + API_URL=${OPTARG};; + b) + BRANCH_TO_SYNC=${OPTARG};; + g) + GITLAB_TRIGGER_TOKEN=${OPTARG};; + i) + GITLAB_MIRROR_PROJECT_ID=${OPTARG};; + h | *) + print_help + exit 0;; + esac +done + + +# Test if arguments are missing +if [ -z ${API_URL} ] || [ -z ${BRANCH_TO_SYNC} ] || [ -z ${GITLAB_TRIGGER_TOKEN} ] || [ -z ${GITLAB_MIRROR_PROJECT_ID} ]; then + printf "\nArgument(s) missing.\n" + print_help + exit 0 +fi + + +# Send synchronization request +printf "\n\n${red}${i}.${no_color} Send request to DSO api.\n\n" + +curl \ + -X POST \ + --fail \ + -F token=${GITLAB_TRIGGER_TOKEN} \ + -F ref=main \ + -F variables[GIT_BRANCH_DEPLOY]=${BRANCH_TO_SYNC} \ + "${API_URL%/}/api/v4/projects/${GITLAB_MIRROR_PROJECT_ID}/trigger/pipeline" diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml new file mode 100644 index 0000000..19ab4a0 --- /dev/null +++ b/docker/docker-compose.dev.yml @@ -0,0 +1,38 @@ +services: + server: + image: tuto-monorepo/server:dev + container_name: tuto-monorepo_server + build: + context: ../apps/server + dockerfile: ./Dockerfile + target: dev + restart: always + ports: + - 3000:3000 + volumes: + - ../apps/server:/app + - /app/node_modules + networks: + - tuto-monorepo-network + + client: + image: tuto-monorepo/client:dev + container_name: tuto-monorepo_client + build: + context: ../apps/client + dockerfile: ./Dockerfile + target: dev + restart: always + ports: + - 8080:8080 + environment: + VITE_SERVER_HOST: http://localhost:3000 + volumes: + - ../apps/client:/app + - /app/node_modules + networks: + - tuto-monorepo-network + +networks: + tuto-monorepo-network: + driver: bridge \ No newline at end of file diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml new file mode 100644 index 0000000..638ef03 --- /dev/null +++ b/docker/docker-compose.prod.yml @@ -0,0 +1,32 @@ +services: + server: + image: tuto-monorepo/server:prod + container_name: tuto-monorepo_server + build: + context: ../apps/server + dockerfile: ./Dockerfile + target: prod + restart: always + ports: + - 3000:3000 + networks: + - tuto-monorepo-network + + client: + image: tuto-monorepo/client:prod + container_name: tuto-monorepo_client + build: + context: ../apps/client + dockerfile: ./Dockerfile + target: prod + restart: always + ports: + - 8080:8080 + environment: + SERVER_HOST: http://localhost:3000 + networks: + - tuto-monorepo-network + +networks: + tuto-monorepo-network: + driver: bridge \ No newline at end of file diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100755 index 0000000..0e8a0eb --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.lock b/helm/Chart.lock new file mode 100644 index 0000000..3271794 --- /dev/null +++ b/helm/Chart.lock @@ -0,0 +1,3 @@ +dependencies: [] +digest: sha256:643d5437104296e21d906ecb15b2c96ad278f20cfc4af53b12bb6069bd853726 +generated: "2024-05-15T04:12:49.088814+02:00" diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100755 index 0000000..2a9c720 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: tuto-monorepo +description: A Helm chart to deploy ... +type: application +keywords: [] +home: https://github.com/cloud-pi-native/tuto-monorepo +sources: + - https://github.com/cloud-pi-native/tuto-monorepo +dependencies: [] +maintainers: + - name: this-is-tobi + email: this-is-tobi@proton.me + url: https://github.com/this-is-tobi +# icon: A URL to an SVG or PNG image to be used as an icon (optional). +deprecated: false +annotations: {} +version: 1.0.0 +appVersion: "1.0.0" diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100755 index 0000000..bf9fd34 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,91 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "template.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "template.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + + +{{/* +Create the name of the service account to use +*/}} +{{- define "template.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "template.name" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "template.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + + +{{/* +Common labels +*/}} +{{- define "template.labels" -}} +helm.sh/chart: {{ include "template.chart" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + + +{{/* +Selector labels +*/}} +{{- define "template.client.selectorLabels" -}} +app.kubernetes.io/name: {{ include "template.name" . }}-client +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "template.server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "template.name" . }}-server +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + + +{{/* +Create container environment variables from configmap +*/}} +{{- define "template.env" -}} +{{ range $key, $val := .env }} +{{ $key }}: {{ $val | quote }} +{{- end }} +{{- end }} + + +{{/* +Create container environment variables from secret +*/}} +{{- define "template.secret" -}} +{{ range $key, $val := .secrets }} +{{ $key }}: {{ $val | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/helm/templates/client/configmap.yaml b/helm/templates/client/configmap.yaml new file mode 100644 index 0000000..d3fc996 --- /dev/null +++ b/helm/templates/client/configmap.yaml @@ -0,0 +1,16 @@ +{{- if or .Values.client.env .Values.global.env -}} +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ include "template.fullname" . }}-client + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.client.selectorLabels" . | nindent 4 }} +data: + {{- if .Values.global.env -}} + {{- include "template.env" .Values.global | indent 2 }} + {{- end -}} + {{- if .Values.client.env -}} + {{- include "template.env" .Values.client | indent 2 }} + {{- end -}} +{{- end -}} diff --git a/helm/templates/client/deployment.yaml b/helm/templates/client/deployment.yaml new file mode 100755 index 0000000..8ec0b3a --- /dev/null +++ b/helm/templates/client/deployment.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "template.fullname" . }}-client + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.client.selectorLabels" . | nindent 4 }} +spec: + {{- if not .Values.client.autoscaling.enabled }} + replicas: {{ .Values.client.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "template.client.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.client.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "template.client.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.client.image.pullSecret }} + imagePullSecrets: + - name: "{{- .Values.client.image.pullSecret }}" + {{- end }} + serviceAccountName: {{ include "template.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.client.podSecurityContext | nindent 8 }} + containers: + - name: client + securityContext: + {{- toYaml .Values.client.securityContext | nindent 12 }} + image: "{{ .Values.client.image.repository }}:{{ .Values.client.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.client.image.pullPolicy }} + envFrom: + {{- if or .Values.global.env .Values.client.env }} + - configMapRef: + name: {{ include "template.fullname" . }}-client + {{- end }} + {{- if or .Values.global.secrets .Values.client.secrets }} + - secretRef: + name: {{ include "template.fullname" . }}-client + {{- end }} + ports: + - name: http + containerPort: {{ .Values.client.container.port }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.client.resources | nindent 12 }} + {{- with .Values.client.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.client.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.client.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/client/hpa.yaml b/helm/templates/client/hpa.yaml new file mode 100755 index 0000000..ea44e9c --- /dev/null +++ b/helm/templates/client/hpa.yaml @@ -0,0 +1,33 @@ +{{- if .Values.client.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "template.fullname" . }}-client + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.client.selectorLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "template.fullname" . }}-client + minReplicas: {{ .Values.client.autoscaling.minReplicas }} + maxReplicas: {{ .Values.client.autoscaling.maxReplicas }} + metrics: + {{- if .Values.client.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.client.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.client.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.client.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/templates/client/secret.yaml b/helm/templates/client/secret.yaml new file mode 100644 index 0000000..0b5df73 --- /dev/null +++ b/helm/templates/client/secret.yaml @@ -0,0 +1,18 @@ +{{- if or .Values.client.secrets .Values.global.secrets -}} +kind: Secret +apiVersion: v1 +metadata: + name: {{ include "template.client.fullname" . }} + labels: {{- include "template.client.labels" . | nindent 4 }} + name: {{ include "template.fullname" . }}-client + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.client.selectorLabels" . | nindent 4 }} +data: + {{- if .Values.global.secrets -}} + {{- include "template.secret" .Values.global | indent 2 }} + {{- end -}} + {{- if .Values.client.secrets -}} + {{- include "template.secret" .Values.client | indent 2 }} + {{- end -}} +{{- end -}} diff --git a/helm/templates/client/service.yaml b/helm/templates/client/service.yaml new file mode 100755 index 0000000..1c37469 --- /dev/null +++ b/helm/templates/client/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "template.fullname" . }}-client + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.client.selectorLabels" . | nindent 4 }} +spec: + type: {{ .Values.client.service.type }} + ports: + - port: {{ .Values.client.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "template.client.selectorLabels" . | nindent 4 }} diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml new file mode 100755 index 0000000..f6d7fc3 --- /dev/null +++ b/helm/templates/ingress.yaml @@ -0,0 +1,49 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "template.fullname" . -}} +{{- $svcPortClient := .Values.client.service.port -}} +{{- $svcPortServer := .Values.server.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "template.fullname" . }} + {{- with .Values.ingress.labels }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if gt (len .Values.ingress.tls) 0 }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName | quote | default (printf "%s-%s" $fullName "secret") }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ printf "%s-%s" $fullName "client" }} + port: + number: {{ $svcPortClient }} + - path: /api + pathType: Prefix + backend: + service: + name: {{ printf "%s-%s" $fullName "server" }} + port: + number: {{ $svcPortServer }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/templates/server/configmap.yaml b/helm/templates/server/configmap.yaml new file mode 100644 index 0000000..6fed312 --- /dev/null +++ b/helm/templates/server/configmap.yaml @@ -0,0 +1,16 @@ +{{- if or .Values.server.env .Values.global.env -}} +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ include "template.fullname" . }}-server + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.server.selectorLabels" . | nindent 4 }} +data: + {{- if .Values.global.env -}} + {{- include "template.env" .Values.global | indent 2 }} + {{- end -}} + {{- if .Values.server.env -}} + {{- include "template.env" .Values.server | indent 2 }} + {{- end -}} +{{- end -}} diff --git a/helm/templates/server/deployment.yaml b/helm/templates/server/deployment.yaml new file mode 100755 index 0000000..b10abec --- /dev/null +++ b/helm/templates/server/deployment.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "template.fullname" . }}-server + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.server.selectorLabels" . | nindent 4 }} +spec: + {{- if not .Values.server.autoscaling.enabled }} + replicas: {{ .Values.server.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "template.server.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.server.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "template.server.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.server.image.pullSecret }} + imagePullSecrets: + - name: "{{- .Values.server.image.pullSecret }}" + {{- end }} + serviceAccountName: {{ include "template.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.server.podSecurityContext | nindent 8 }} + containers: + - name: server + securityContext: + {{- toYaml .Values.server.securityContext | nindent 12 }} + image: "{{ .Values.server.image.repository }}:{{ .Values.server.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.server.image.pullPolicy }} + envFrom: + {{- if or .Values.global.env .Values.server.env }} + - configMapRef: + name: {{ include "template.fullname" . }}-server + {{- end }} + {{- if or .Values.global.secrets .Values.server.secrets }} + - secretRef: + name: {{ include "template.fullname" . }}-server + {{- end }} + ports: + - name: http + containerPort: {{ .Values.server.container.port }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.server.resources | nindent 12 }} + {{- with .Values.server.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/server/hpa.yaml b/helm/templates/server/hpa.yaml new file mode 100755 index 0000000..d1baa69 --- /dev/null +++ b/helm/templates/server/hpa.yaml @@ -0,0 +1,33 @@ +{{- if .Values.server.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "template.fullname" . }}-server + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.server.selectorLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "template.fullname" . }}-server + minReplicas: {{ .Values.server.autoscaling.minReplicas }} + maxReplicas: {{ .Values.server.autoscaling.maxReplicas }} + metrics: + {{- if .Values.server.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.server.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.server.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.server.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/templates/server/secret.yaml b/helm/templates/server/secret.yaml new file mode 100644 index 0000000..c78d812 --- /dev/null +++ b/helm/templates/server/secret.yaml @@ -0,0 +1,16 @@ +{{- if or .Values.server.secrets .Values.global.secrets -}} +kind: Secret +apiVersion: v1 +metadata: + name: {{ include "template.fullname" . }}-server + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.server.selectorLabels" . | nindent 4 }} +data: + {{- if .Values.global.secrets -}} + {{- include "template.secret" .Values.global | indent 2 }} + {{- end -}} + {{- if .Values.server.secrets -}} + {{- include "template.secret" .Values.server | indent 2 }} + {{- end -}} +{{- end -}} diff --git a/helm/templates/server/service.yaml b/helm/templates/server/service.yaml new file mode 100755 index 0000000..6e3e699 --- /dev/null +++ b/helm/templates/server/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "template.fullname" . }}-server + labels: + {{- include "template.labels" . | nindent 4 }} + {{- include "template.server.selectorLabels" . | nindent 4 }} +spec: + type: {{ .Values.server.service.type }} + ports: + - port: {{ .Values.server.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "template.server.selectorLabels" . | nindent 4 }} diff --git a/helm/templates/serviceaccount.yaml b/helm/templates/serviceaccount.yaml new file mode 100755 index 0000000..d1880da --- /dev/null +++ b/helm/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "template.serviceAccountName" . }} + labels: + {{- include "template.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100755 index 0000000..49e682d --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,109 @@ +nameOverride: "" +fullnameOverride: "" +serviceAccount: + create: false + annotations: {} + name: "" + +global: + env: {} + secrets: {} + +ingress: + enabled: true + className: "" + annotations: {} + hosts: + - host: tuto-mono.dev.numerique-interieur.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: tuto-mono.dev.numerique-interieur.com + # hosts: + # - tuto-mono.dev.numerique-interieur.com + +client: + replicaCount: 1 + image: + repository: harbor.apps.c6.numerique-interieur.com/mi-test/tuto-client + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + pullSecret: registry-pull-secret + podAnnotations: {} + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + env: + SERVER_HOST: tuto-mono.dev.numerique-interieur.com + secrets: {} + service: + type: ClusterIP + port: 80 + container: + port: 8080 + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + nodeSelector: {} + tolerations: [] + affinity: {} + +server: + replicaCount: 1 + image: + repository: harbor.apps.c6.numerique-interieur.com/mi-test/tuto-server + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + pullSecret: registry-pull-secret + podAnnotations: {} + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + env: {} + secrets: {} + service: + type: ClusterIP + port: 80 + container: + port: 3000 + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5bee899 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "tuto-monorepo", + "version": "1.0.0", + "license": "ISC", + "type": "module", + "scripts": { + "install": "bun install --cwd ./apps/server; bun install --cwd ./apps/client", + "docker:dev": "docker compose -f ./docker/docker-compose.dev.yml up", + "docker:dev:build": "export COMPOSE_FILE=./docker/docker-compose.dev.yml && cd $(dirname $COMPOSE_FILE) && docker buildx bake --file $(basename $COMPOSE_FILE) --load && cd - > /dev/null", + "docker:dev:delete": "docker compose -f ./docker/docker-compose.dev.yml down", + "docker:prod": "docker compose -f ./docker/docker-compose.prod.yml up", + "docker:prod:build": "export COMPOSE_FILE=./docker/docker-compose.prod.yml && cd $(dirname $COMPOSE_FILE) && docker buildx bake --file $(basename $COMPOSE_FILE) --load && cd - > /dev/null", + "docker:prod:delete": "docker compose -f ./docker/docker-compose.prod.yml down", + "kube:init": "ci/kind/run.sh -i -d api.domain.local,doc.domain.local", + "kube:dev:build": "ci/kind/run.sh -c create,build -f ./docker/docker-compose.dev.yml", + "kube:dev:load": "ci/kind/run.sh -c create,load -f ./docker/docker-compose.dev.yml", + "kube:dev:run": "ci/kind/run.sh -c create,dev", + "kube:dev": "bun run kube:dev:build && bun run kube:dev:run", + "kube:prod:build": "ci/kind/run.sh -c create,build -f ./docker/docker-compose.prod.yml", + "kube:prod:load": "ci/kind/run.sh -c create,load -f ./docker/docker-compose.prod.yml", + "kube:prod:run": "ci/kind/run.sh -c create,prod", + "kube:prod": "bun run kube:prod:build && bun run kube:prod:run", + "kube:e2e": "TARGET_HOST=api.domain.local TARGET_PORT=80 bun run kube:dev; bun run test:e2e", + "kube:e2e-ci": "TARGET_HOST=api.domain.local TARGET_PORT=80 bun run kube:prod; bun run test:e2e-ci", + "kube:clean": "ci/kind/run.sh -c clean", + "kube:delete": "ci/kind/run.sh -c delete" + }, + "packageManager": "bun@1.1.8" +}