diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ba13248 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 9b78208..2f4c6bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ site ? -_site \ No newline at end of file +_site +node_modules/ +build/ +package-lock.json +yarn.lock +template +build +.secrets +tools/ \ No newline at end of file diff --git a/api/ping/ping.yml b/api/ping/ping.yml new file mode 100644 index 0000000..809e533 --- /dev/null +++ b/api/ping/ping.yml @@ -0,0 +1,10 @@ +version: 1.0 +provider: + name: openfaas + gateway: http://127.0.0.1:8080 +functions: + ping: + lang: python3-http + handler: ./ping + image: host.docker.internal:5000/ping:1.0 + diff --git a/api/ping/ping/__init__.py b/api/ping/ping/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/ping/ping/handler.py b/api/ping/ping/handler.py new file mode 100644 index 0000000..5716ff9 --- /dev/null +++ b/api/ping/ping/handler.py @@ -0,0 +1,11 @@ +import json + +def handle(event, context): + """handle a request to the function + Args: + req (str): request body + """ + return { + "statusCode": 200, + "body": str(event.body) + } \ No newline at end of file diff --git a/api/ping/ping/requirements.txt b/api/ping/ping/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/app/.dockerignore b/app/.dockerignore new file mode 100644 index 0000000..fc9a38a --- /dev/null +++ b/app/.dockerignore @@ -0,0 +1,2 @@ +**/node_modules/** +*/yarn.lock \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..5311d24 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,12 @@ +FROM node:21-alpine3.17 as builder + +ADD . /workdir + +WORKDIR /workdir + +RUN yarn install && yarn run build + + +FROM nginx:mainline-alpine +ADD nginx.conf /etc/nginx/nginx.conf +COPY --from=builder /workdir/build /usr/share/nginx/html diff --git a/app/nginx.conf b/app/nginx.conf new file mode 100644 index 0000000..79b946c --- /dev/null +++ b/app/nginx.conf @@ -0,0 +1,24 @@ +worker_processes 5; ## Default: 1 + +events { + worker_connections 4096; ## Default: 1024 +} + +http { + include /etc/nginx/mime.types; + server { + listen 80; + listen [::]:80; + + root /usr/share/nginx/html; + index index.html index.htm; + + location (.*)/config.js$ { + try_files $uri $uri/ /config.js; + } + + location / { + try_files $uri $uri/ /index.html; + } + } +} \ No newline at end of file diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..fe896a0 --- /dev/null +++ b/app/package.json @@ -0,0 +1,49 @@ +{ + "name": "mm-potal", + "version": "0.1.0", + "private": true, + "homepage": "/app", + "dependencies": { + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^13.0.0", + "@testing-library/user-event": "^13.2.1", + "@types/jest": "^27.0.1", + "@types/node": "^16.7.13", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "oidc-client-ts": "^2.4.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.1", + "react-scripts": "5.0.1", + "typescript": "^4.4.2", + "web-vitals": "^2.1.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11" + } +} diff --git a/app/public/config.js b/app/public/config.js new file mode 100644 index 0000000..6059b43 --- /dev/null +++ b/app/public/config.js @@ -0,0 +1,9 @@ +config = { + 'serverUrl': 'http://localhost/api', + 'applicationRoot':'http://localhost/app/', + 'authentication': { + 'clientId': 'mm-portal', + 'serviceUrl': 'http://localhost:8080/realms/master/', + 'scope': 'openid profile email phone roles' + } +} \ No newline at end of file diff --git a/app/public/index.html b/app/public/index.html new file mode 100644 index 0000000..010fb9d --- /dev/null +++ b/app/public/index.html @@ -0,0 +1,13 @@ + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/app/src/app.tsx b/app/src/app.tsx new file mode 100644 index 0000000..f11ac07 --- /dev/null +++ b/app/src/app.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import './index.css'; +import SecureRoute from './components/secureRoute' +import Home from './features/home/view/content'; +import About from './features/about/view/content'; +import NavigationMenu from './components/navigationbar'; +import TitleBar from './components/titlebar'; +import { SettingsService } from './common/services/settingsService'; +import { AuthenticationService } from './common/services/authenticationService'; +import { ApplicationContext } from './common/utilities/applicationContext'; +import { SigninCallback } from './components/signinCallback'; + +const App = () => { + const context = new AppContext(); + return ( + +
+ + + + + } /> + } /> + } /> + + +
+
+ ); +}; + +export default App; + +class AppContext { + private settingsService: SettingsService | undefined; + private authenticationService: AuthenticationService | undefined; + + public getAuthenticationService(): AuthenticationService { + if (!this.authenticationService) { + this.authenticationService = new AuthenticationService(this.getSettingsService().getAuthenticationSettings()); + } + return this.authenticationService; + } + + public getSettingsService(): SettingsService { + if (!this.settingsService) { + this.settingsService = new SettingsService(); + } + return this.settingsService; + } +} \ No newline at end of file diff --git a/app/src/common/services/authenticationService.ts b/app/src/common/services/authenticationService.ts new file mode 100644 index 0000000..d050db4 --- /dev/null +++ b/app/src/common/services/authenticationService.ts @@ -0,0 +1,54 @@ +import { Log, User, UserManager } from 'oidc-client-ts'; +import { AuthenticationSettings } from '../settings/AuthenticationSettings'; +import { JsonHelper } from '../utilities/jsonHelper'; +import { Roles } from '../utilities/roles'; + +export class AuthenticationService { + public userManager: UserManager; + + constructor(settings: AuthenticationSettings) { + const oidcSettings = { + authority: settings.serviceUrl, + client_id: settings.clientId, + redirect_uri: settings.applicationRoot + `signin-callback`, + response_type: 'code', + scope: settings.scope + }; + this.userManager = new UserManager(oidcSettings); + + Log.setLogger(console); + Log.setLevel(Log.INFO); + } + + public getUser(): Promise { + return this.userManager.getUser(); + } + + public login(): Promise { + return this.userManager.signinRedirect(); + } + + public renewToken(): Promise { + return this.userManager.signinSilent(); + } + + public logout(): Promise { + return this.userManager.signoutRedirect({ state: (window.location.pathname + window.location.search).substring(1) }); + } + + public handleSignin(): Promise { + return this.userManager.signinRedirectCallback(); + } + + public async getUserRoles(): Promise { + var user = await this.getUser(); + if (!user) { + throw new ReferenceError("User is not authenticated"); + } + const claims = JsonHelper.parseJwt(user.access_token); + console.log("Claims", claims); + const allowedRoles = [Roles.admin, Roles.user] + const roles = (claims.roles as string[]).filter(r => allowedRoles.includes(r)); + return roles; + } +} \ No newline at end of file diff --git a/app/src/common/services/settingsService.ts b/app/src/common/services/settingsService.ts new file mode 100644 index 0000000..ca5471e --- /dev/null +++ b/app/src/common/services/settingsService.ts @@ -0,0 +1,18 @@ +import { AuthenticationSettings } from '../settings/AuthenticationSettings'; + +export class SettingsService { + public getAuthenticationSettings(): AuthenticationSettings { + let settings = new AuthenticationSettings(); + + settings.clientId = (window as any).config.authentication.clientId; + settings.serviceUrl = (window as any).config.authentication.serviceUrl; + settings.scope = (window as any).config.authentication.clientScope; + settings.applicationRoot = (window as any).config.applicationRoot; + + return settings; + } + + public getServerUrl(): string { + return (window as any).config.serverUrl; + } +} \ No newline at end of file diff --git a/app/src/common/settings/AuthenticationSettings.ts b/app/src/common/settings/AuthenticationSettings.ts new file mode 100644 index 0000000..dfb6be2 --- /dev/null +++ b/app/src/common/settings/AuthenticationSettings.ts @@ -0,0 +1,6 @@ +export class AuthenticationSettings { + clientId: string + serviceUrl: string + scope: string + applicationRoot: string +} \ No newline at end of file diff --git a/app/src/common/utilities/applicationContext.ts b/app/src/common/utilities/applicationContext.ts new file mode 100644 index 0000000..96dc9df --- /dev/null +++ b/app/src/common/utilities/applicationContext.ts @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { AuthenticationService } from '../services/authenticationService'; +import { SettingsService } from '../services/settingsService'; + +export interface IApplicationContext { + getAuthenticationService(): AuthenticationService; + getSettingsService(): SettingsService; +} + +export const ApplicationContext = React.createContext({} as IApplicationContext); + +export function useApplicationContext(): IApplicationContext { + return React.useContext(ApplicationContext); +} \ No newline at end of file diff --git a/app/src/common/utilities/jsonHelper.ts b/app/src/common/utilities/jsonHelper.ts new file mode 100644 index 0000000..dae26a3 --- /dev/null +++ b/app/src/common/utilities/jsonHelper.ts @@ -0,0 +1,105 @@ +export class JsonHelper { + public static toCamelCase(input: any): any { + if (input instanceof Array) { + return JsonHelper.arrayToCamelCase(input); + } else { + return JsonHelper.objectToCamelCase(input); + } + } + + public static toSnakeCase(input: any): any { + if (input instanceof Array) { + return JsonHelper.arrayToSnakeCase(input); + } else { + return JsonHelper.objectToSnakeCase(input); + } + } + + public static parseJwt(token: string): any { + var base64Url = token.split('.')[1]; + var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + + return JSON.parse(jsonPayload); + }; + + private static objectToCamelCase(input: any) { + let result: any = {}; + for (let sourceKey in input) { + if (input.hasOwnProperty(sourceKey)) { + let targetKey = JsonHelper.valueToCamelCase(sourceKey); + let value = input[sourceKey] + if (value instanceof Array || (value !== null && value.constructor === Object)) { + value = JsonHelper.toCamelCase(value) + } + result[targetKey] = value + } + } + return result + } + + private static arrayToCamelCase(input: Array) { + const convertProperty = (value: any) => { + if (typeof value === "object") { + value = JsonHelper.toCamelCase(value) + } + return value + } + return input.map(convertProperty); + } + + private static valueToCamelCase(input: string) { + let result = input.charAt(0).toLowerCase(); + let index = 1; + for (; index < input.length; index++) { + if (input[index] === '_') { + index++; + result += input[index].toUpperCase(); + } else { + result += input[index]; + } + } + return result; + } + + private static objectToSnakeCase(input: any) { + let result: any = {}; + for (let sourceKey in input) { + if (input.hasOwnProperty(sourceKey)) { + let targetKey = JsonHelper.valueToSnakeCase(sourceKey); + let value = input[sourceKey] + if (value instanceof Array || (value !== null && value.constructor === Object)) { + value = JsonHelper.toSnakeCase(value) + } + result[targetKey] = value + } + } + return result + } + + private static arrayToSnakeCase(input: Array) { + const convertProperty = (value: any) => { + if (typeof value === "object") { + value = JsonHelper.toSnakeCase(value) + } + return value + } + return input.map(convertProperty); + } + + private static valueToSnakeCase(input: string) { + let result = input.charAt(0).toLowerCase(); + let index = 1; + for (; index < input.length; index++) { + if (input[index] === input[index].toUpperCase()) { + result += "_"; + result += input[index].toLowerCase(); + } else { + result += input[index]; + } + } + return result; + } +} \ No newline at end of file diff --git a/app/src/common/utilities/roles.ts b/app/src/common/utilities/roles.ts new file mode 100644 index 0000000..5fee29d --- /dev/null +++ b/app/src/common/utilities/roles.ts @@ -0,0 +1,4 @@ +export class Roles { + public static readonly admin = "admin"; + public static readonly user = "user"; +} \ No newline at end of file diff --git a/app/src/components/logo.tsx b/app/src/components/logo.tsx new file mode 100644 index 0000000..1496ca6 --- /dev/null +++ b/app/src/components/logo.tsx @@ -0,0 +1,10 @@ +import logo from "../images/mm-logo.png"; +import * as React from "react"; + +export default function Logo() { + return ( + <> + logo + + ); +} \ No newline at end of file diff --git a/app/src/components/navigationbar.tsx b/app/src/components/navigationbar.tsx new file mode 100644 index 0000000..43001dc --- /dev/null +++ b/app/src/components/navigationbar.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +const NavigationMenu = () => { + return ( + + ); +}; + +export + + default NavigationMenu; \ No newline at end of file diff --git a/app/src/components/secureRoute.tsx b/app/src/components/secureRoute.tsx new file mode 100644 index 0000000..f53b438 --- /dev/null +++ b/app/src/components/secureRoute.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { useApplicationContext } from '../common/utilities/applicationContext'; + +export default function SecureRoute(props: any) { + const context = useApplicationContext(); + const authService = context.getAuthenticationService(); + useEffect(() => { + const loginIfNeeded = async () => { + console.log("Get user"); + try { + const user = await authService.getUser(); + if (!user) { + authService.login(); + } + } + catch (error) { + console.log("Failed to get user"); + console.error(error); + } + + }; + + loginIfNeeded(); + }, [authService]); + return (<>{props.children}); +} \ No newline at end of file diff --git a/app/src/components/signinCallback.tsx b/app/src/components/signinCallback.tsx new file mode 100644 index 0000000..af268d0 --- /dev/null +++ b/app/src/components/signinCallback.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { AuthenticationService } from '../common/services/authenticationService'; +import { SettingsService } from '../common/services/settingsService'; + +export function SigninCallback() { + let authSettings = new SettingsService().getAuthenticationSettings(); + let authService = new AuthenticationService(authSettings); + React.useEffect(() => { + authService.handleSignin() + .then(user => { + if (user) { + let returnUrl = "index"; + if (user.state && (user.state as any).returnUrl) { + returnUrl = (user.state as any).returnUrl; + } + (window as any).location.href = authSettings.applicationRoot + returnUrl; + } + }); + }); + return (<>); +} diff --git a/app/src/components/titlebar.tsx b/app/src/components/titlebar.tsx new file mode 100644 index 0000000..d07f0d2 --- /dev/null +++ b/app/src/components/titlebar.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import Logo from './logo'; + +const TitleBar = () => { + return ( +
+
+ +

Maagan Michal Portal

+
+
+ ); +}; + +export default TitleBar; \ No newline at end of file diff --git a/app/src/features/about/view/content.tsx b/app/src/features/about/view/content.tsx new file mode 100644 index 0000000..00ac896 --- /dev/null +++ b/app/src/features/about/view/content.tsx @@ -0,0 +1,8 @@ +const Content = () => { + console.log("about"); + return (
+        About page
+    
); +}; + +export default Content; \ No newline at end of file diff --git a/app/src/features/home/view/content.tsx b/app/src/features/home/view/content.tsx new file mode 100644 index 0000000..b1762be --- /dev/null +++ b/app/src/features/home/view/content.tsx @@ -0,0 +1,8 @@ +const Content = () => { + console.log("home"); + return (
+        Home page
+    
); +}; + +export default Content; \ No newline at end of file diff --git a/app/src/images/mm-logo.png b/app/src/images/mm-logo.png new file mode 100644 index 0000000..c4753f0 --- /dev/null +++ b/app/src/images/mm-logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f84fe554d5bb19e7c63aca0fa6d78c27678032d722adcb80acf99a5d43eee8ea +size 7865 diff --git a/app/src/index.css b/app/src/index.css new file mode 100644 index 0000000..c362927 --- /dev/null +++ b/app/src/index.css @@ -0,0 +1,45 @@ +/* Global styles */ +body { + font-family: sans-serif; + margin: 0; + padding: 0; +} + +/* Navigation menu styles */ +.navigation-menu { + background-color: #f4f4f4; + padding: 20px; +} + +.navigation-menu ul { + list-style: none; + margin: 0; + padding: 0; +} + +.navigation-menu li { + display: inline-block; + margin-right: 20px; +} + +.navigation-menu a { + color: #000; + text-decoration: none; +} + +/* Title bar styles */ +.title-bar { + background-color: #2196F3; + color: #fff; + padding: 20px; +} + +.title-bar h1 { + margin: 0; + padding: 0; +} + +/* Content styles */ +.content { + padding: 20px; +} \ No newline at end of file diff --git a/app/src/index.tsx b/app/src/index.tsx new file mode 100644 index 0000000..341ee5a --- /dev/null +++ b/app/src/index.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import App from './app'; + +const root = ReactDOM.createRoot(document.getElementById('root')!); +root.render( + + + +); \ No newline at end of file diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 0000000..e926689 --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2016", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "../src/**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/local/docker-compose.yml b/local/docker-compose.yml new file mode 100644 index 0000000..1401569 --- /dev/null +++ b/local/docker-compose.yml @@ -0,0 +1,75 @@ +version: "3.9" +services: + registry: + image: registry:2.8.3 + restart: unless-stopped + ports: + - 5000:5000 + volumes: + - registry:/var/lib/registry + - ./registry/certs:/certs:/certs + environment: + REGISTRY_HTTP_ADDR: "0.0.0.0:5000" + REGISTRY_HTTP_TLS_CERTIFICATE: "/certs/host-docker.internal.crt" + REGISTRY_HTTP_TLS_KEY: "/certs/host-docker.internal.key" + + app: + hostname: app + build: + dockerfile: Dockerfile + context: ../app + + nginx: + image: openresty/openresty:1.21.4.3-1-alpine + restart: unless-stopped + ports: + - 80:80 + volumes: + - ./nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf + + db: + image: postgres:16.1-alpine + hostname: db + restart: unless-stopped + environment: + - POSTGRES_PASSWORD=password + - POSTGRES_USER=admin + - POSTGRES_DB=mm-portal + ports: + - 5432:5432 + volumes: + - pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U admin -d mm-portal"] + interval: 10s + timeout: 5s + retries: 5 + + keycloak: + image: quay.io/keycloak/keycloak:latest + hostname: keycloak + restart: unless-stopped + depends_on: + - db + ports: + - "8081:8080" + environment: + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://db/keycloak + KC_DB_USERNAME: admin + KC_DB_PASSWORD: password + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_METRICS_ENABLED: true + KC_HEALTH_ENABLED: true + healthcheck: + interval: 30s + timeout: 3s + start_period: 10s + retries: 3 + test: curl -k --fail http://localhost/ || exit 1 + command: start-dev + +volumes: + pg_data: + registry: diff --git a/local/install-environment.ps1 b/local/install-environment.ps1 new file mode 100644 index 0000000..30d8e58 --- /dev/null +++ b/local/install-environment.ps1 @@ -0,0 +1,79 @@ +param( + [Parameter()] + [string] + [ValidateSet('full-install', 'cluster', 'openfaas')] + $Action +) + +$helmVersion = "3.13.2" + +function Install-Windows() { + New-Item -ItemType Directory -Path "$PSScriptRoot\tools" -Force + Push-Location "$PSScriptRoot\tools" + try { + $helmVersion = (Invoke-WebRequest "https://api.github.com/repos/helm/helm/releases/latest" | ConvertFrom-Json)[0].tag_name + Invoke-WebRequest -Uri "https://get.helm.sh/helm-$helmVersion-windows-amd64.zip" -OutFile "helm.zip" + Expand-Archive -Path "helm.zip" -DestinationPath "." -Force + $helmExe = Get-ChildItem -Path . -Recurse -Filter "helm.exe" + $helmExe | ForEach-Object { Move-Item $_.FullName "helm.exe" } + Remove-Item "helm.zip" + Remove-Item $helmExe.Directory.FullName -Recurse -Force + $faasCliVersion = (Invoke-WebRequest "https://api.github.com/repos/openfaas/faas-cli/releases/latest" | ConvertFrom-Json)[0].tag_name + Invoke-WebRequest -Uri "https://github.com/openfaas/faas-cli/releases/download/$faasCliVersion/faas-cli.exe" -OutFile "faas-cli.exe" + Invoke-WebRequest -Uri "https://kind.sigs.k8s.io/dl/v0.20.0/kind-windows-amd64" -OutFile "kind.exe" + $env:PATH = "$PSScriptRoot\tools;" + $env:PATH + } + finally { + Pop-Location + } +} + +function Install-OpenFaas() { + kubectl create namespace openfaas + kubectl create namespace openfaas-fn + helm upgrade openfaas --install openfaas/openfaas --namespace openfaas + $password = kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" + [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($password)) | Out-File "password.txt" +} + +function Install-General-Prerequisites() { + helm repo add openfaas https://openfaas.github.io/faas-netes + helm repo update +} + +function Create-Cluster() { + kind create cluster + docker network connect "kind" "local-registry-1" + kubectl apply -f k8s/k8s-registry-config.yml + + docker exec kind-control-plane mkdir /usr/share/ca-certificates/extra/ + docker cp registry/certs/host-docker.internal.crt kind-control-plane:/usr/share/ca-certificates/extra/host-docker.internal.crt + docker exec -i kind-control-plane bash -c "echo 'extra/host-docker.internal.crt' | tee -a /etc/ca-certificates.conf" + docker exec kind-control-plane update-ca-certificates + docker restart kind-control-plane + $nodes = kubectl get nodes -o json | ConvertFrom-Json + while ((-not $nodes) -or (-not $nodes.items) -or $nodes.items.Length -eq 0) { + Start-Sleep -Seconds 1 + $nodes = kubectl get nodes -o json | ConvertFrom-Json + } +} + +function Main() { + if ($Action -eq 'full-install') { + if ($IsWindows) { + Install-Windows + } + else { + + } + Install-General-Prerequisites + } + if ($Action -eq 'full-install' -or $Action -eq 'cluster') { + Create-Cluster + } + if ($Action -eq 'full-install' -or $Action -eq 'openfaas') { + Install-OpenFaas + } +} + +Main \ No newline at end of file diff --git a/local/k8s/k8s-registry-config.yml b/local/k8s/k8s-registry-config.yml new file mode 100644 index 0000000..dc88c3c --- /dev/null +++ b/local/k8s/k8s-registry-config.yml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: local-registry-hosting + namespace: kube-public +data: + localRegistryHosting.v1: | + host: "localhost:5000" + help: "https://kind.sigs.k8s.io/docs/user/local-registry/" \ No newline at end of file diff --git a/local/nginx/nginx.conf b/local/nginx/nginx.conf new file mode 100644 index 0000000..727e650 --- /dev/null +++ b/local/nginx/nginx.conf @@ -0,0 +1,47 @@ +events { + worker_connections 4096; ## Default: 1024 +} + +http { + include /usr/local/openresty/nginx/conf/mime.types; + server { + listen 80; + location ^~/api/ { + access_by_lua_block + { + ngx.req.read_body() + local req = ngx.req.get_body_data() + if (req) + then + local cjson = require "cjson" + local json_body = cjson.decode(req) + ngx.req.set_body_data(cjson.encode({ body = json_body })) + end + } + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://host.docker.internal:8080/; + rewrite /api(.*) /function$1 break; + } + + location ^~/app/ { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-NginX-Proxy true; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + proxy_pass 'http://app'; + rewrite /app(.*) $1 break; + proxy_ssl_session_reuse off; + proxy_set_header Host $http_host; + proxy_cache_bypass $http_upgrade; + proxy_redirect off; + } + + location / { + return 301 $scheme://$host/app/; + } + } +} \ No newline at end of file diff --git a/local/registry/certs/host-docker.internal.crt b/local/registry/certs/host-docker.internal.crt new file mode 100644 index 0000000..ce3cfa0 --- /dev/null +++ b/local/registry/certs/host-docker.internal.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFQDCCAyigAwIBAgIUMY1ucXnHR03xib/LQkMMtsSEf+swDQYJKoZIhvcNAQEL +BQAwHzEdMBsGA1UEAwwUaG9zdC5kb2NrZXIuaW50ZXJuYWwwHhcNMjMxMjA5MTAx +MDQ3WhcNMjQxMjA4MTAxMDQ3WjAfMR0wGwYDVQQDDBRob3N0LmRvY2tlci5pbnRl +cm5hbDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALmOCGYJOVFiACXi +vqmgsoWrhLZR85Qg4MKpnw+Ji4vq9CPKxGdqLgZym+fUV6v3IhiJ72fzR8kPtHP7 +RWN9O9EA1VtMXUhUKKNxOCjn4IkSmw01/NcjmJMx0Wavx/o7oTulce7b13PI4j6f +xy5zrbCHwlG4xd+Ot1GVwfgA1hfqaBCETRNQWYXjWNiE9hcnpuI2LMxapALtMh7Y +VWdCqswF4k9VVZrYazHtmZ4Hp4q5FrmsbsQpNE1qU6uQbeKj9wd8wKBWktxl/q3n +jQ7AhhgDOQyw2tWDqPnDyLvSFcaAxFycUjSpPEp1/pYBKz3tS1gQkvX3sQFU8lgN ++m+KrQafNX7t30VID069/z4SkYIkC65L+cdRYwu85rvaOnjswecHa1xA335LPt9s +wuHvgsJN72UjOsOXdkgZ65u4sQtCDFZvxzXdt6BhE63YNrXYjJJ1Y4DTLXNu//G4 +ttAEWTFXfy9nb7v5Co7GNUvZQaa3OgJAJBbLt40Z8KWMaOpfx5bQaU09+tXksald +8mKoMwONhKIe/DdiQ6TSoutxokCJpx61B+U3NwvNnLPySQYLZivYrNGVyYUsAGQ1 +cHgEYexWp2ba56/bIMMcB/02Ddez6zqev2GeBu7Yw8bhPaOffQVFkLI8y4kWGUJS +O3ai2ePGS5gBdJ3zAjVsCZKLK83RAgMBAAGjdDByMB0GA1UdDgQWBBTWRxQKqeK1 +ud5nKid/7Ogj4w8k2TAfBgNVHSMEGDAWgBTWRxQKqeK1ud5nKid/7Ogj4w8k2TAP +BgNVHRMBAf8EBTADAQH/MB8GA1UdEQQYMBaCFGhvc3QuZG9ja2VyLmludGVybmFs +MA0GCSqGSIb3DQEBCwUAA4ICAQBCx1SW+Y8w4KniYTarAXr3lvzsHw3CYiGSdCuq +6+6bMkm7m+EUVSSFjWFMKk3XX5IZR9a2iGWNRDeVmUbFVVnSPXn1gHoPIQM5GrXz +hs1gcLec1SsO2vNDGt7HzBgSyMiQnjsQ4WyFIV+7PpFQNBWwvhacjqkmGNJCiWUn +JKyxmf/tyJZH8cjp0tNQXX3Z/BlVd/wrvdsde1wzaSDnhjs+F9Wo2GmKREQyVRjq +8h1rjbjiHFkR4KBrbWD5iaTkD5mRnkEo81QVGwEuCu7z65g0HP18S0OqmSbUwUcU +gpzjo/mvf+gC+ty/b3BLjy4ySKPpqZ24rb1bOZiZwY3kWgbiV94xYN+LrUpLp7dI +WcRsRRCDN4ilrnUUY1eeGlZS1olnuBbxAySy7P0xZkCrrL9pFUS19Q4UE64t6iZR +M2I8T4r8IK69R6lDZORb4zrGbLHe9aDj3N6GQhuGxZW8v23PfwE6/KoaIa20nEwx +Qd5Xcw1+KCPAEnzbDdsuywFwUFxsqXsR3gnxcJxOVU0ty9qR1Zk02OCrawzSCqZE +0Td1IbZv/MxbKm+xHLNGo2ZZe0HuT8aE09ZHhvfDZK5YD6+7ZOLFNBnoAyTbUi/a +17s48ZsnP2bsVheS26z90A9hB2KpbTEX7p6WTgXaAk0xFz7hFq4R2zftpcf5A5Jo +rzCixg== +-----END CERTIFICATE----- diff --git a/local/registry/certs/host-docker.internal.key b/local/registry/certs/host-docker.internal.key new file mode 100644 index 0000000..be51572 --- /dev/null +++ b/local/registry/certs/host-docker.internal.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +d9vojSzgdydWnKrGx5MEJ+iIAHgc +-----END PRIVATE KEY-----