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 = `
+
+`
+
+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"
+}