diff --git a/.gitignore b/.gitignore index 1091f7ee2..d39c76654 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ riderModule.iml **/MercurialExtensions/ backend/Testing/SyncReverseProxy/TestData.cs .task +deployment/dev/collector-config.yaml +deployment/dev/local.env diff --git a/README.md b/README.md index 8e8bfe4fd..f10b4edb9 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,24 @@ Other files, like `docker-compose.yaml`, should be at the root of the repo, beca * via npm: `npm install -g @go-task/cli` * run setup `task setup` +### Extra setup steps for Windows + +If you're running Windows, you may need to add the following lines to your `C:\Windows\system32\drivers\etc\hosts` file: + +``` +127.0.0.1 resumable.localhost +127.0.0.1 hg.localhost +127.0.0.1 lexbox.localhost +``` + +On Linux, anything with a `.localhost` domain is automatically mapped to 127.0.0.1 so you don't need to edit your `/etc/hosts` file. + +### Optional setup for debugging + +If you want to test out Honeycomb traces, you will need to set the `HONEYCOMB_API_KEY` environment variable in +the `deployment/dev/secrets.yaml` file. +You can get the key from [here](https://ui.honeycomb.io/sil-language-forge/environments/test/api_keys) + #### git note this repo uses a ignore revs file. To configure this repo to use it run this command. It should be executed as part of `task setup` @@ -70,7 +88,8 @@ pnpm should be installed automatically using nodejs corepack, if not you can run --- ### Helpful urls -* http://localhost:5173 - sveltekit frontend +* http://lexbox.localhost - k8s ingress +* http://localhost:3000 - sveltekit frontend * http://localhost:5158/api/swagger - swagger docs for the api * http://localhost:5158/api/graphql/ui - graphiql UI * http://localhost:5158/api/graphql - graphiql endpoint @@ -89,19 +108,6 @@ The following users are available, password for them all is just `pass`: There will also be a single project, Sena 3. There will not be an hg repository however, see optional setup below if this is desired. -### Optional setup - -You may need to add the following line to your `/etc/hosts` or `C:\Windows\system32\drivers\etc\hosts` file: - -``` -127.0.0.1 resumable.localhost -127.0.0.1 hg.localhost -``` - -If you want to test out Honeycomb traces, you will need to set the `HONEYCOMB_API_KEY` environment variable in -the `.env` file. -You can get the key from [here](https://ui.honeycomb.io/sil-language-forge/environments/test/api_keys) - --- ### Hasura workflow In order to modify Hasura table relations and permissions in hasura we need to use the hasura console. diff --git a/Taskfile.yml b/Taskfile.yml index 2aa4c51a7..dcd34d134 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -17,6 +17,7 @@ tasks: deps: [ setup-win, setup-unix ] cmds: - git config blame.ignoreRevsFile .git-blame-ignore-revs + - echo HONEYCOMB_API_KEY=__REPLACE__ >> deployment\dev\local.env setup-win: platforms: [ windows ] cmds: diff --git a/backend/Dockerfile b/backend/Dockerfile index 11b543efd..70e54baeb 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 @@ -12,17 +13,17 @@ COPY . . # # move them into the proper sub folders, based on the name of the project # RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done -RUN dotnet restore "LexBoxApi/LexBoxApi.csproj" +RUN --mount=type=cache,target=/root/.nuget/packages dotnet restore "LexBoxApi/LexBoxApi.csproj" # COPY . . # WORKDIR "/LexBoxApi" ARG APP_VERSION LABEL version=$APP_VERSION -RUN dotnet build /p:InformationalVersion=$APP_VERSION "/LexBoxApi/LexBoxApi.csproj" -c Release -o /app/build +RUN --mount=type=cache,target=/root/.nuget/packages dotnet build /p:InformationalVersion=$APP_VERSION "/LexBoxApi/LexBoxApi.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish /p:InformationalVersion=$APP_VERSION "/LexBoxApi/LexBoxApi.csproj" -c Release -o /app/publish +RUN --mount=type=cache,target=/root/.nuget/packages dotnet publish /p:InformationalVersion=$APP_VERSION "/LexBoxApi/LexBoxApi.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app diff --git a/backend/LexBoxApi/Program.cs b/backend/LexBoxApi/Program.cs index 245e82554..e5072f4a1 100644 --- a/backend/LexBoxApi/Program.cs +++ b/backend/LexBoxApi/Program.cs @@ -60,6 +60,7 @@ var app = builder.Build(); app.Logger.LogInformation("LexBox-api version: {version}", AppVersionService.Version); + app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All, @@ -71,7 +72,6 @@ "*.languageforge.org" } }); - app.Use(async (context, next) => { context.Response.Headers.Add("lexbox-version", AppVersionService.Version); diff --git a/backend/LexBoxApi/Services/DevSchemaWriterService.cs b/backend/LexBoxApi/Services/DevSchemaWriterService.cs new file mode 100644 index 000000000..60af1a7d1 --- /dev/null +++ b/backend/LexBoxApi/Services/DevSchemaWriterService.cs @@ -0,0 +1,27 @@ +using HotChocolate.Execution; + +namespace LexBoxApi.Services; + +public class DevSchemaWriterService : IHostedService +{ + private readonly RequestExecutorProxy _executorProxy; + private readonly ILogger _logger; + public DevSchemaWriterService(IRequestExecutorResolver executorResolver, ILogger logger) + { + _logger = logger; + _executorProxy = new RequestExecutorProxy(executorResolver, Schema.DefaultName); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var schema = await _executorProxy.GetSchemaAsync(cancellationToken); + await using var file = File.Open("../../frontend/schema.graphql", FileMode.Create); + await SchemaPrinter.PrintAsync(schema, file, true, cancellationToken); + _logger.LogInformation("Schema written frontend/schema.graphql"); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/backend/LexBoxApi/dev.Dockerfile b/backend/LexBoxApi/dev.Dockerfile index a4f6bdb76..62ca9e887 100644 --- a/backend/LexBoxApi/dev.Dockerfile +++ b/backend/LexBoxApi/dev.Dockerfile @@ -1,6 +1,15 @@ +# syntax=docker/dockerfile:1 FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build EXPOSE 80 EXPOSE 443 +WORKDIR /src/backend +# Copy the main source project files +COPY */*.csproj *.sln ./ +# move them into the proper sub folders, based on the name of the project +RUN for file in $(ls *.csproj); do dir=${file%.*} mkdir -p ${file%.*}/ && mv $file ${file%.*}/ && dotnet restore ${file%.*}/${file}; done + +COPY . . WORKDIR /src/backend/LexBoxApi +RUN mkdir /src/frontend ENV DockerDev=true CMD dotnet watch run -lp docker --property:InformationalVersion=dockerDev diff --git a/backend/SyncReverseProxy/proxy.appsettings.Development.json b/backend/SyncReverseProxy/proxy.appsettings.Development.json index 6e4cadd22..e750f136d 100644 --- a/backend/SyncReverseProxy/proxy.appsettings.Development.json +++ b/backend/SyncReverseProxy/proxy.appsettings.Development.json @@ -26,6 +26,6 @@ } }, "LexBoxApi": { - "Url": "https://localhost:7075" + "Url": "http://localhost:5158" } } diff --git a/deployment/Taskfile.yml b/deployment/Taskfile.yml index 122fdee5b..7b485000d 100644 --- a/deployment/Taskfile.yml +++ b/deployment/Taskfile.yml @@ -3,6 +3,9 @@ version: '3' tasks: + db-up: + cmds: + - kubectl apply -f ./base/db-deployment.yaml lexbox-update: cmds: - kubectl apply -f ./lexbox-deployment.yaml diff --git a/deployment/config-map.yaml b/deployment/base/config-map.yaml similarity index 77% rename from deployment/config-map.yaml rename to deployment/base/config-map.yaml index bd0a0a407..70dd17ffb 100644 --- a/deployment/config-map.yaml +++ b/deployment/base/config-map.yaml @@ -2,6 +2,6 @@ kind: ConfigMap metadata: name: email-config + namespace: languagedepot data: email-from: "Value" - \ No newline at end of file diff --git a/deployment/db-deployment.yaml b/deployment/base/db-deployment.yaml similarity index 85% rename from deployment/db-deployment.yaml rename to deployment/base/db-deployment.yaml index 662a096a1..98ca79c9e 100644 --- a/deployment/db-deployment.yaml +++ b/deployment/base/db-deployment.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: Service metadata: name: db + namespace: languagedepot labels: app: db spec: @@ -10,21 +11,8 @@ spec: clusterIP: None selector: app: db - ---- - -# https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: db-data -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi - storageClassName: weekly-snapshots-retain-4 # provided by LTOps + ports: + - port: 5432 --- @@ -33,6 +21,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: db + namespace: languagedepot labels: app: db spec: @@ -88,4 +77,4 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: db-data + claimName: db-data # established in pvc.yaml diff --git a/deployment/hg-deployment.yaml b/deployment/base/hg-deployment.yaml similarity index 81% rename from deployment/hg-deployment.yaml rename to deployment/base/hg-deployment.yaml index cf745ff6f..1bffa0867 100644 --- a/deployment/hg-deployment.yaml +++ b/deployment/base/hg-deployment.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: Service metadata: name: hg + namespace: languagedepot labels: app: hg spec: @@ -25,12 +26,14 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: hgresumable-cache + namespace: languagedepot spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi + storageClassName: weekly-snapshots-retain-4 # provided by LTOps --- @@ -39,6 +42,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: hg + namespace: languagedepot labels: app: hg spec: @@ -93,10 +97,18 @@ spec: - name: repos mountPath: /var/vcs/public + initContainers: + - name: populate-test-repos + image: busybox:1.36.1 + command: ['sh', '-c', "if [ ! -d /repos/sena-3 ]; then wget -O sena-3.zip 'https://drive.google.com/uc?export=download&id=1I-hwc0RHoQqW774gbS5qR-GHa1E7BlsS'; unzip -q sena-3.zip -d /repos/; fi"] + volumeMounts: + - name: repos + mountPath: /repos + volumes: - name: cache persistentVolumeClaim: claimName: hgresumable-cache - name: repos persistentVolumeClaim: - claimName: hg-repos # established in hg-repos-pvc.yaml + claimName: hg-repos # established in pvc.yaml diff --git a/deployment/base/ingress-config.yaml b/deployment/base/ingress-config.yaml new file mode 100644 index 000000000..72833d480 --- /dev/null +++ b/deployment/base/ingress-config.yaml @@ -0,0 +1,17 @@ +# template, copy into env folder and change the values, then add as a patch to the kustomization.yaml file + +- op: replace + path: /spec/rules/0/host + value: staging.languagedepot.org +- op: replace + path: /spec/rules/1/host + value: hg-staging.languagedepot.org +- op: replace + path: /spec/rules/2/host + value: resumable-staging.languagedepot.org +- op: replace + path: /spec/tls/0/hosts + value: + - staging.languagedepot.org + - hg-staging.languagedepot.org + - resumable-staging.languagedepot.org diff --git a/deployment/base/kustomization.yaml b/deployment/base/kustomization.yaml new file mode 100644 index 000000000..482779b98 --- /dev/null +++ b/deployment/base/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: languagedepot + +resources: +- config-map.yaml +- secrets.yaml +- pvc.yaml +- db-deployment.yaml +- hg-deployment.yaml +- lexbox-deployment.yaml +- ui-deployment.yaml +- proxy-deployment.yaml diff --git a/deployment/lexbox-deployment.yaml b/deployment/base/lexbox-deployment.yaml similarity index 86% rename from deployment/lexbox-deployment.yaml rename to deployment/base/lexbox-deployment.yaml index f51261fa1..140c22af1 100644 --- a/deployment/lexbox-deployment.yaml +++ b/deployment/base/lexbox-deployment.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: Service metadata: name: lexbox + namespace: languagedepot labels: app: lexbox spec: @@ -13,16 +14,13 @@ spec: ports: - name: http protocol: TCP - port: 80 + port: 5158 - name: otel protocol: TCP port: 4318 - name: otel-grpc protocol: TCP port: 4317 - - name: sveltekit - protocol: TCP - port: 3000 - name: github-deploy protocol: TCP port: 9000 @@ -34,6 +32,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: lexbox + namespace: languagedepot labels: app: lexbox spec: @@ -60,25 +59,27 @@ spec: # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers resources: requests: # TODO: need to establish resource limits, possibly after seeing it in action for some regular and/or load testing - memory: 100Mi + memory: 125Mi limits: - memory: 150Mi + memory: 175Mi startupProbe: httpGet: - port: 80 + port: 5158 path: /api/healthz failureThreshold: 30 periodSeconds: 10 ports: - - containerPort: 80 + - containerPort: 5158 volumeMounts: - name: repos mountPath: /hg-repos env: + - name: DOTNET_URLS + value: http://0.0.0.0:5158 - name: ASPNETCORE_ENVIRONMENT - value: Staging + value: Development - name: POSTGRES_DB valueFrom: secretKeyRef: @@ -96,13 +97,6 @@ spec: secretKeyRef: key: Authentication__Jwt__Secret name: lexbox-api - - name: HasuraConfig__HasuraUrl - value: http://localhost:8080/v1/graphql - - name: HasuraConfig__HasuraSecret - valueFrom: - secretKeyRef: - key: HASURA_GRAPHQL_ADMIN_SECRET - name: hasura - name: HgConfig__RepoPath value: /hg-repos - name: HgConfig__HgWebUrl @@ -134,32 +128,7 @@ spec: # TODO: need to parameterize this value: "Language Depot (Staging) " - name: Email__EmailRenderHost - value: localhost:3000 - - - name: ui - image: ghcr.io/sillsdev/lexbox-ui:develop - imagePullPolicy: Always - # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers - resources: - requests: # TODO: need to establish resource limits, possibly after seeing it in action for some regular and/or load testing - memory: 100Mi - limits: - memory: 150Mi - ports: - - containerPort: 3000 - - env: - - name: PUBLIC_TURNSTILE_SITE_KEY - valueFrom: - secretKeyRef: - key: PUBLIC_TURNSTILE_SITE_KEY - name: ui - - name: PUBLIC_ENV_NAME - value: staging - - name: BACKEND_HOST - value: http://localhost - - name: OTEL_ENDPOINT - value: http://localhost:4318 + value: ui:3000 - name: otel-collector image: otel/opentelemetry-collector:0.73.0 @@ -243,12 +212,35 @@ spec: mode: 365 - key: webhooks path: hooks.yaml + initContainers: + - name: wait-db + image: busybox:1.36.1 + command: ['sh', '-c', "until nc -w 1 -z db.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local 5432; do echo waiting for db; sleep 2; done"] + # command: ['sh', '-c', "until nc -w 1 -z db.languagedepot.svc.cluster.local 5432; do echo waiting for db; sleep 2; done"] + - name: db-migrations + # Should be same image as lexbox-api container + image: ghcr.io/sillsdev/lexbox-api:develop + command: ['sh', '-c', 'dotnet run migrate --environment "Development"'] + env: + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + key: POSTGRES_DB + name: db + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + key: POSTGRES_PASSWORD + name: db + - name: DbConfig__LexBoxConnectionString + value: Host=db;Port=5432;Username=postgres;Password=$(POSTGRES_PASSWORD);Database=$(POSTGRES_DB) --- apiVersion: v1 kind: ConfigMap metadata: name: deploy-config + namespace: languagedepot data: # branch to allow deployments from publish-branch: develop @@ -306,11 +298,13 @@ apiVersion: v1 kind: ServiceAccount metadata: name: lexbox-deployer + namespace: languagedepot --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: deployer + namespace: languagedepot rules: - apiGroups: [ "apps" ] resources: [ "deployments" ] @@ -320,6 +314,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: redepoly + namespace: languagedepot subjects: - kind: ServiceAccount name: lexbox-deployer diff --git a/deployment/base/namespace.yaml b/deployment/base/namespace.yaml new file mode 100644 index 000000000..ad3404ede --- /dev/null +++ b/deployment/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: languagedepot diff --git a/deployment/proxy-deployment.yaml b/deployment/base/proxy-deployment.yaml similarity index 79% rename from deployment/proxy-deployment.yaml rename to deployment/base/proxy-deployment.yaml index 297ef8b6d..df1dd8873 100644 --- a/deployment/proxy-deployment.yaml +++ b/deployment/base/proxy-deployment.yaml @@ -3,16 +3,19 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: proxy + namespace: languagedepot annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" spec: + ingressClassName: nginx defaultBackend: service: - name: lexbox + name: ui port: name: sveltekit rules: - - host: staging.languagedepot.org +# each host here is patched by index, so don't change the order of them in this file without changing the patch files (ingress-config.yaml) + - host: localhost http: paths: - path: /api @@ -43,7 +46,7 @@ spec: name: lexbox port: name: github-deploy - - host: hg-staging.languagedepot.org + - host: hg.localhost http: paths: - path: / @@ -53,7 +56,7 @@ spec: name: lexbox port: name: http - - host: resumable-staging.languagedepot.org + - host: resumable.localhost http: paths: - path: / @@ -66,7 +69,7 @@ spec: tls: - hosts: - - staging.languagedepot.org - - hg-staging.languagedepot.org - - resumable-staging.languagedepot.org + - localhost + - hg.localhost + - resumable.localhost secretName: languagedepot-tls # provided by LTOps diff --git a/deployment/base/pvc.yaml b/deployment/base/pvc.yaml new file mode 100644 index 000000000..3a6037e69 --- /dev/null +++ b/deployment/base/pvc.yaml @@ -0,0 +1,31 @@ +--- + +# https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: hg-repos + namespace: languagedepot +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: weekly-snapshots-retain-4 # provided by LTOps + +--- + +# https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: db-data + namespace: languagedepot +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: weekly-snapshots-retain-4 # provided by LTOps diff --git a/deployment/secrets.yaml b/deployment/base/secrets.yaml similarity index 81% rename from deployment/secrets.yaml rename to deployment/base/secrets.yaml index 36cedb4fa..3d8746cc4 100644 --- a/deployment/secrets.yaml +++ b/deployment/base/secrets.yaml @@ -5,28 +5,18 @@ kind: Secret metadata: name: db namespace: languagedepot -data: +stringData: POSTGRES_DB: 'lexbox' POSTGRES_PASSWORD: '' --- -apiVersion: v1 -kind: Secret -metadata: - name: hasura - namespace: languagedepot -data: - HASURA_GRAPHQL_ADMIN_SECRET: '' - ---- - apiVersion: v1 kind: Secret metadata: name: otel namespace: languagedepot -data: +stringData: HONEYCOMB_API_KEY: '' --- @@ -36,7 +26,7 @@ kind: Secret metadata: name: ui namespace: languagedepot -data: +stringData: PUBLIC_TURNSTILE_SITE_KEY: '' --- @@ -46,7 +36,7 @@ kind: Secret metadata: name: lexbox-api namespace: languagedepot -data: +stringData: Authentication__Jwt__Secret: '' CloudFlare__TurnstileKey: '' @@ -57,7 +47,7 @@ kind: Secret metadata: name: email namespace: languagedepot -data: +stringData: SMTP_USER: '' SMTP_PASSWORD: '' --- @@ -67,5 +57,5 @@ kind: Secret metadata: name: deploy-key namespace: languagedepot -data: - deploy-key: '' \ No newline at end of file +stringData: + deploy-key: '' diff --git a/deployment/base/ui-deployment.yaml b/deployment/base/ui-deployment.yaml new file mode 100644 index 000000000..afc97c251 --- /dev/null +++ b/deployment/base/ui-deployment.yaml @@ -0,0 +1,75 @@ +# https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service +apiVersion: v1 +kind: Service +metadata: + name: ui + namespace: languagedepot + labels: + app: ui +spec: + type: ClusterIP + clusterIP: None + selector: + app: ui + ports: + - name: sveltekit + protocol: TCP + port: 3000 + +--- + +# https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#writing-a-deployment-spec +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui + namespace: languagedepot + labels: + app: ui +spec: + selector: + matchLabels: + app: ui + strategy: + rollingUpdate: + maxSurge: 2 + maxUnavailable: 1 + type: RollingUpdate + template: + # https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + metadata: + labels: + app: ui + spec: + serviceAccountName: lexbox-deployer + containers: + + - name: ui + image: ghcr.io/sillsdev/lexbox-ui:develop + imagePullPolicy: IfNotPresent + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers + resources: + requests: # TODO: need to establish resource limits, possibly after seeing it in action for some regular and/or load testing + memory: 100Mi + limits: + memory: 150Mi + ports: + - containerPort: 3000 + + env: + - name: PUBLIC_TURNSTILE_SITE_KEY + valueFrom: + secretKeyRef: + key: PUBLIC_TURNSTILE_SITE_KEY + name: ui + - name: PUBLIC_ENV_NAME + value: dev + - name: BACKEND_HOST + value: http://lexbox:5158 + - name: OTEL_ENDPOINT + value: http://lexbox:4318 + + # initContainers: + # - name: wait-backend + # image: busybox:1.36.1 + # command: ['sh', '-c', "until wget -O - http://lexbox:5158/api/healthz; do echo waiting for backend; sleep 2; done"] diff --git a/deployment/dev/change-storage-class.patch.yaml b/deployment/dev/change-storage-class.patch.yaml new file mode 100644 index 000000000..3d261ce7e --- /dev/null +++ b/deployment/dev/change-storage-class.patch.yaml @@ -0,0 +1,7 @@ +apiVersion: apps/v1 +kind: PersistentVolumeClaim +metadata: + name: hg-repos + namespace: languagedepot +spec: + storageClassName: hostpath # Because docker-desktop doesn't have a weekly-snapshots-retain-4 storage class diff --git a/deployment/dev/db-secrets.yaml b/deployment/dev/db-secrets.yaml new file mode 100644 index 000000000..533caa472 --- /dev/null +++ b/deployment/dev/db-secrets.yaml @@ -0,0 +1,10 @@ +# https://kubernetes.io/docs/concepts/configuration/secret + +apiVersion: v1 +kind: Secret +metadata: + name: db + namespace: languagedepot +stringData: + POSTGRES_DB: 'lexbox' + POSTGRES_PASSWORD: '972b722e63f549938d07bd8c4ee5086c' diff --git a/deployment/dev/hg-deployment-patch.yaml b/deployment/dev/hg-deployment-patch.yaml new file mode 100644 index 000000000..6b42579f9 --- /dev/null +++ b/deployment/dev/hg-deployment-patch.yaml @@ -0,0 +1,14 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hg + namespace: languagedepot +spec: + template: + spec: + containers: + - name: hgweb + env: + - name: APP_VERSION + value: "dockerDev" + diff --git a/deployment/dev/ingress-deployment.yaml b/deployment/dev/ingress-deployment.yaml new file mode 100644 index 000000000..6dac63865 --- /dev/null +++ b/deployment/dev/ingress-deployment.yaml @@ -0,0 +1,645 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + name: ingress-nginx +--- +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx + namespace: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - coordination.k8s.io + resourceNames: + - ingress-nginx-leader + resources: + - leases + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission + namespace: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx +rules: +- apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + - namespaces + verbs: + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission +rules: +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission + namespace: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx +subjects: +- kind: ServiceAccount + name: ingress-nginx + namespace: ingress-nginx +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission +subjects: +- kind: ServiceAccount + name: ingress-nginx-admission + namespace: ingress-nginx +--- +apiVersion: v1 +data: + allow-snippet-annotations: "true" +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-controller + namespace: ingress-nginx +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + externalTrafficPolicy: Local + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: LoadBalancer +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-controller-admission + namespace: ingress-nginx +spec: + ports: + - appProtocol: https + name: https-webhook + port: 443 + targetPort: webhook + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-controller + namespace: ingress-nginx +spec: + minReadySeconds: 0 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + spec: + containers: + - args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + image: registry.k8s.io/ingress-nginx/controller:v1.8.0@sha256:744ae2afd433a395eeb13dc03d3313facba92e96ad71d9feaafc85925493fee3 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: controller + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 443 + name: https + protocol: TCP + - containerPort: 8443 + name: webhook + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 90Mi + securityContext: + allowPrivilegeEscalation: true + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 + volumeMounts: + - mountPath: /usr/local/certificates/ + name: webhook-cert + readOnly: true + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission-create + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission-create + spec: + containers: + - args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b + imagePullPolicy: IfNotPresent + name: create + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: batch/v1 +kind: Job +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission-patch + namespace: ingress-nginx +spec: + template: + metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission-patch + spec: + containers: + - args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b + imagePullPolicy: IfNotPresent + name: patch + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux + restartPolicy: OnFailure + securityContext: + fsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + serviceAccountName: ingress-nginx-admission +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: nginx +spec: + controller: k8s.io/ingress-nginx +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/component: admission-webhook + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.8.0 + name: ingress-nginx-admission +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: ingress-nginx-controller-admission + namespace: ingress-nginx + path: /networking/v1/ingresses + failurePolicy: Fail + matchPolicy: Equivalent + name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None diff --git a/deployment/dev/kustomization.yaml b/deployment/dev/kustomization.yaml new file mode 100644 index 000000000..28e17c3bd --- /dev/null +++ b/deployment/dev/kustomization.yaml @@ -0,0 +1,30 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: languagedepot + +resources: +- ../base/ +#- secrets.yaml +- ingress-deployment.yaml + +configMapGenerator: +- name: otel-config + files: + - collector-config.yaml + +secretGenerator: + - name: otel + namespace: languagedepot + behavior: replace + envs: + - local.env + +patches: + - target: + version: v1 + kind: PersistentVolumeClaim + path: change-storage-class.patch.yaml + - path: lexbox-deployment.patch.yaml + - path: ui-deployment.patch.yaml + - path: db-secrets.yaml + - path: hg-deployment-patch.yaml diff --git a/deployment/dev/lexbox-deployment.patch.yaml b/deployment/dev/lexbox-deployment.patch.yaml new file mode 100644 index 000000000..d6a0016cd --- /dev/null +++ b/deployment/dev/lexbox-deployment.patch.yaml @@ -0,0 +1,35 @@ +#file: noinspection KubernetesMissingKeys,KubernetesUnknownValues +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lexbox + namespace: languagedepot + labels: + app: lexbox +spec: + template: + spec: + containers: + - name: lexbox-api + imagePullPolicy: IfNotPresent + resources: + requests: + memory: 200Mi + limits: + memory: 1Gi + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" + - name: Authentication__Jwt__Secret + value: "dev-secret_but-it-must-be-32-characters-long" +# intentionally set this to no value + valueFrom: + - name: CloudFlare__TurnstileKey + value: "1x0000000000000000000000000000000AA" + valueFrom: + - name: Email__SmtpUser + value: 'maildev' + valueFrom: + - name: Email__SmtpPassword + value: 'maildev' + valueFrom: diff --git a/deployment/dev/ui-deployment.patch.yaml b/deployment/dev/ui-deployment.patch.yaml new file mode 100644 index 000000000..12ce21d98 --- /dev/null +++ b/deployment/dev/ui-deployment.patch.yaml @@ -0,0 +1,21 @@ +#file: noinspection KubernetesMissingKeys +#file is a patch file that will be applied to the base deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui + namespace: languagedepot +spec: + template: + spec: + containers: + - name: ui + resources: + requests: + memory: 750Mi + limits: + memory: 1250Mi + env: + - name: PUBLIC_TURNSTILE_SITE_KEY + value: 1x0000000000000000000000000000000AA + valueFrom: diff --git a/deployment/hg-repos-pvc.yaml b/deployment/hg-repos-pvc.yaml deleted file mode 100644 index 9bc14ca5d..000000000 --- a/deployment/hg-repos-pvc.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: hg-repos -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 10Gi - storageClassName: weekly-snapshots-retain-4 # provided by LTOps diff --git a/deployment/production/kustomization.yaml b/deployment/production/kustomization.yaml new file mode 100644 index 000000000..1ce5fd84b --- /dev/null +++ b/deployment/production/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: languagedepot + +resources: + - ../base +patches: + - path: lexbox-deployment.patch.yaml + - path: ui-deployment.patch.yaml diff --git a/deployment/production/lexbox-deployment.patch.yaml b/deployment/production/lexbox-deployment.patch.yaml new file mode 100644 index 000000000..315830171 --- /dev/null +++ b/deployment/production/lexbox-deployment.patch.yaml @@ -0,0 +1,16 @@ +#file: noinspection KubernetesMissingKeys +#file is a patch file that will be applied to the base deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lexbox + namespace: languagedepot +spec: + template: + spec: + containers: + - name: lexbox-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + diff --git a/deployment/production/ui-deployment.patch.yaml b/deployment/production/ui-deployment.patch.yaml new file mode 100644 index 000000000..fa6385676 --- /dev/null +++ b/deployment/production/ui-deployment.patch.yaml @@ -0,0 +1,15 @@ +#file: noinspection KubernetesMissingKeys +#file is a patch file that will be applied to the base deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui + namespace: languagedepot +spec: + template: + spec: + containers: + - name: ui + env: + - name: PUBLIC_ENV_NAME + value: production diff --git a/deployment/staging/ingress-config-staging.yaml b/deployment/staging/ingress-config-staging.yaml new file mode 100644 index 000000000..b96ae9c33 --- /dev/null +++ b/deployment/staging/ingress-config-staging.yaml @@ -0,0 +1,15 @@ +- op: replace + path: /spec/rules/0/host + value: staging.languagedepot.org +- op: replace + path: /spec/rules/1/host + value: hg-staging.languagedepot.org +- op: replace + path: /spec/rules/2/host + value: resumable-staging.languagedepot.org +- op: replace + path: /spec/tls/0/hosts + value: + - staging.languagedepot.org + - hg-staging.languagedepot.org + - resumable-staging.languagedepot.org diff --git a/deployment/staging/kustomization.yaml b/deployment/staging/kustomization.yaml new file mode 100644 index 000000000..ae8a1e77b --- /dev/null +++ b/deployment/staging/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: languagedepot + +resources: + - ../base +patches: + - path: lexbox-deployment.patch.yaml + - path: ui-deployment.patch.yaml + - path: ingress-config-staging.yaml + target: + kind: Ingress + name: proxy + namespace: languagedepot diff --git a/deployment/staging/lexbox-deployment.patch.yaml b/deployment/staging/lexbox-deployment.patch.yaml new file mode 100644 index 000000000..48104b4ee --- /dev/null +++ b/deployment/staging/lexbox-deployment.patch.yaml @@ -0,0 +1,16 @@ +#file: noinspection KubernetesMissingKeys +#file is a patch file that will be applied to the base deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lexbox + namespace: languagedepot +spec: + template: + spec: + containers: + - name: lexbox-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Staging" + diff --git a/deployment/staging/ui-deployment.patch.yaml b/deployment/staging/ui-deployment.patch.yaml new file mode 100644 index 000000000..c41f3a4a6 --- /dev/null +++ b/deployment/staging/ui-deployment.patch.yaml @@ -0,0 +1,15 @@ +#file: noinspection KubernetesMissingKeys +#file is a patch file that will be applied to the base deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui + namespace: languagedepot +spec: + template: + spec: + containers: + - name: ui + env: + - name: PUBLIC_ENV_NAME + value: staging diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 89c32c5d7..f8b5b76ae 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,12 +1,13 @@ +# syntax=docker/dockerfile:1 # TODO: can't use vanilla alpine version since python is needed for gql-codegen stuff. -FROM node AS builder +FROM node:20 AS builder WORKDIR /app COPY package.json pnpm-lock.yaml /app/ RUN corepack enable -RUN pnpm install +RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install COPY . /app/ COPY src /app/src @@ -14,9 +15,9 @@ COPY static /app/static ARG APP_VERSION ENV VITE_APP_VERSION=$APP_VERSION -RUN pnpm run build +RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm run build -FROM node:alpine +FROM node:20-alpine # container would not receive SIGTERM from docker when shutting down so Docker would force kill # the container after 10s. This will make shutdown faster because the SIGTERM will be handled appropriately. # https://maximorlov.com/process-signals-inside-docker-containers/ diff --git a/frontend/dev.Dockerfile b/frontend/dev.Dockerfile new file mode 100644 index 000000000..ea793271b --- /dev/null +++ b/frontend/dev.Dockerfile @@ -0,0 +1,16 @@ +# syntax=docker/dockerfile:1 +# TODO: can't use vanilla alpine version since python is needed for gql-codegen stuff. +FROM node:20.2 AS builder + +WORKDIR /app + +COPY package.json pnpm-lock.yaml /app/ + +RUN corepack enable +RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install + +COPY . /app/ +COPY src /app/src +COPY static /app/static +ENV DockerDev=true +CMD [ "pnpm", "run", "dev", "--port", "3000", "--host", "0.0.0.0" ] diff --git a/frontend/gql-codegen.ts b/frontend/gql-codegen.ts index f0592e920..5cf447e36 100644 --- a/frontend/gql-codegen.ts +++ b/frontend/gql-codegen.ts @@ -3,12 +3,6 @@ import type { Options } from 'vite-plugin-graphql-codegen'; //config passed into vite instead of via codegen file, works the same though import type { TypeScriptPluginConfig } from '@graphql-codegen/typescript/typings/config'; -const devSchema: NonNullable['schema'] = { - 'http://localhost:5158/api/graphql/schema.graphql': { - - } -}; - const generationConfig: TypeScriptPluginConfig = { useTypeImports: true, skipTypename: true, @@ -28,11 +22,6 @@ const generationConfig: TypeScriptPluginConfig = { }; type ConfiguredOutput = NonNullable['generates'][string]; const schemaPath = 'schema.graphql'; -const schemaGeneration: Record = { - [schemaPath]: { - plugins: ['schema-ast'] - } -}; const clientGeneration: Record = { './src/lib/gql/generated/': { preset: 'client', @@ -43,22 +32,10 @@ const clientGeneration: Record = { export const gqlOptions: Options = { config: { + schema: schemaPath, documents: ['src/**/*.{ts,graphql}', '!src/lib/gql/generated/**/*'], ignoreNoDocuments: true, // for better experience with the watcher // verbose: true, - generates: { - ...schemaGeneration, - ...clientGeneration - } - }, - configOverrideWatcher: { - schema: devSchema - }, - configOverrideOnStart: { - schema: devSchema - }, - configOverrideOnBuild: { - schema: schemaPath, generates: { ...clientGeneration } diff --git a/frontend/package.json b/frontend/package.json index dfe2df406..8ba4b1237 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,7 +2,7 @@ "name": "frontend", "version": "0.0.1", "private": true, - "packageManager": "pnpm@8.5.1", + "packageManager": "pnpm@8.6.2", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 9e1007b9a..9e12ca6ed 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -1,4 +1,4 @@ -directive @allowAnonymous repeatable on FIELD_DEFINITION +directive @allowAnonymous repeatable on FIELD_DEFINITION directive @authorize( """ diff --git a/frontend/src/lib/components/TrainTracks.svelte b/frontend/src/lib/components/TrainTracks.svelte index e7d46c928..072fbf733 100644 --- a/frontend/src/lib/components/TrainTracks.svelte +++ b/frontend/src/lib/components/TrainTracks.svelte @@ -78,11 +78,11 @@ let curves: SVGStroke[] = []; let svgDots: SVGDot[] = []; - function rowHeight(rowIdx: number): number { + $: rowHeight = (rowIdx: number): number => { return cumulativeHeights[rowIdx] ? cumulativeHeights[rowIdx] : rowHeightDefault * rowIdx + firstRowOffset; } - function colWidth(colIdx: number): number { + $: colWidth = (colIdx: number): number => { return colWidthDefault * colIdx + firstColOffset; } diff --git a/frontend/src/lib/error/index.ts b/frontend/src/lib/error/index.ts index b15bfa55f..496ce0c46 100644 --- a/frontend/src/lib/error/index.ts +++ b/frontend/src/lib/error/index.ts @@ -7,7 +7,7 @@ export const error: Writable = writable(); export const dismiss = (): void => error.set(null); -export const goesToErrorPage = (error: App.Error | null): boolean => error?.handler.endsWith('-hook') ?? false; +export const goesToErrorPage = (error: App.Error | null): boolean => error?.handler?.endsWith('-hook') ?? false; if (browser) { /** diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6fdc3fecd..6395614b8 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -21,9 +21,9 @@ export default defineConfig({ include: ['src/**/*.{test,spec}.ts'], }, server: { - port: 5173, + port: 3000, strictPort: true, - proxy: { + proxy: process.env['DockerDev'] ? undefined : { '/v1/traces': { target: 'http://localhost:4318' }, diff --git a/kustomization.yaml b/kustomization.yaml new file mode 100644 index 000000000..bccd273a8 --- /dev/null +++ b/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: languagedepot + +resources: +- deployment/base/config-map.yaml +- deployment/base/hg-deployment.yaml +- deployment/base/lexbox-deployment.yaml +- deployment/base/ui-deployment.yaml +- deployment/base/proxy-deployment.yaml + +configMapGenerator: +- name: otel-config + files: + - otel/collector-config.yaml diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 000000000..0922ee209 --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,133 @@ +apiVersion: skaffold/v4beta6 +kind: Config +metadata: + name: lexbox +deploy: + kubeContext: docker-desktop +build: + artifacts: + - image: ghcr.io/sillsdev/lexbox-api + context: backend + docker: + dockerfile: Dockerfile + sync: + manual: + - src: '**' + dest: /src/backend + - image: ghcr.io/sillsdev/lexbox-ui + context: frontend + docker: + dockerfile: Dockerfile + sync: + manual: + - src: '**' + dest: /app + - image: ghcr.io/sillsdev/hgresume + context: hgresumable + docker: + dockerfile: Dockerfile + - image: ghcr.io/sillsdev/lexbox-hgweb + context: hgweb + docker: + dockerfile: Dockerfile + local: + useBuildkit: true +portForward: + # Backend + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 5158 + # OpenTelemetry + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 4318 + # Svelte-Kit frontend + - resourceType: Service + resourceName: ui + namespace: languagedepot + port: 3000 + - resourceType: Service + resourceName: db + namespace: languagedepot + port: 5432 + localPort: 5433 + +manifests: + kustomize: + paths: + - deployment/dev + hooks: + before: + - host: + command: ["sh", "-c", "cp otel/collector-config.yaml deployment/dev/collector-config.yaml"] + os: [darwin, linux] + - host: + command: ["powershell", "-Command", "copy otel/collector-config.yaml deployment/dev/collector-config.yaml"] + os: [windows] + +profiles: + - name: dev + activation: + - command: dev + patches: + - op: replace + path: /build/artifacts/0/docker/dockerfile + value: LexBoxApi/dev.Dockerfile + - op: replace + path: /build/artifacts/1/docker/dockerfile + value: dev.Dockerfile + - name: infra + patches: + - op: replace + path: /build/artifacts/0/docker/dockerfile + value: LexBoxApi/dev.Dockerfile + - op: replace + path: /build/artifacts/1/docker/dockerfile + value: dev.Dockerfile + portForward: + # Postgres + - resourceType: Service + resourceName: db + namespace: languagedepot + port: 5432 + localPort: 5433 + # OpenTelemetry + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 4318 # http + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 4317 # grpc + # hg + - resourceType: Service + resourceName: hg + namespace: languagedepot + port: 8088 # web + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 80 # resumable + localPort: 8034 + - name: no-frontend + patches: + - op: replace + path: /build/artifacts/0/docker/dockerfile + value: LexBoxApi/dev.Dockerfile + - op: replace + path: /build/artifacts/1/docker/dockerfile + value: dev.Dockerfile + portForward: + # Backend + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 5158 + # OpenTelemetry + - resourceType: Service + resourceName: lexbox + namespace: languagedepot + port: 4318