Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7b285d7
WIP: worker service
samgibsonmoj Jun 11, 2026
ae1d87b
Remove Worker's dependency on Identity + move notify user to command
samgibsonmoj Jun 11, 2026
8cc3d23
Configure appsettings
samgibsonmoj Jun 11, 2026
09d29a6
Add cats worker to infra
samgibsonmoj Jun 11, 2026
4ad88c9
Publish worker container
samgibsonmoj Jun 12, 2026
500b942
This is a test branch that will allow users to be notified when someo…
carlsixsmith-moj Jun 5, 2026
690b95d
Fix users
carlsixsmith-moj Jun 12, 2026
e1da242
Add signlr backplain
carlsixsmith-moj Jun 12, 2026
2dc7829
Add k8s redis container
samgibsonmoj Jun 12, 2026
b9f559e
Update redis connection string name
samgibsonmoj Jun 12, 2026
bf4320c
Add Redis memory cache + supporting infrastructure
samgibsonmoj Jun 16, 2026
0135ebb
Conditionally register presence connector depending on SignalR config
samgibsonmoj Jun 16, 2026
fcd9f25
Configure backplane in Aspire and cloud deployment
samgibsonmoj Jun 16, 2026
5525bad
Fix SmartEnum cache serialization and isolate cache serializer options
samgibsonmoj Jun 16, 2026
0225558
Configure redis as ephemeral
samgibsonmoj Jun 16, 2026
68c191d
Register in memory and redis user state container
samgibsonmoj Jun 17, 2026
81ab7f3
Add redis container when required in Aspire
samgibsonmoj Jun 17, 2026
1c3d876
Add feature to relay user presence notifications
samgibsonmoj Jun 17, 2026
a50406f
Relay notifications in CP
samgibsonmoj Jun 17, 2026
ecfde53
Remove delete job step from deploy and use image SHA's for migrator/seed
samgibsonmoj Jun 17, 2026
8276826
Deploy migrator/seed jobs as pods
samgibsonmoj Jun 17, 2026
625594d
Cleanup migrator/seed pod before next deploy
samgibsonmoj Jun 17, 2026
13220a2
Remove obsolete worker extension
samgibsonmoj Jun 18, 2026
38a9aaf
rename ICommand -> IExternalCommand as this now conflicts with the Me…
carlsixsmith-moj Jun 19, 2026
8c92030
Add PresenceHub feature gating
samgibsonmoj Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ jobs:
/p:ContainerRegistry=${{ steps.login-ecr.outputs.registry }} \
/p:ContainerRepository=${{ vars.ECR_REPOSITORY }} \
/p:ContainerImageTag=cats-${{ github.sha }}

- name: Build and Push Worker Container
run: |
dotnet publish src/Worker/Worker.csproj \
--configuration Release \
--no-build \
/t:PublishContainer \
/p:ContainerRegistry=${{ steps.login-ecr.outputs.registry }} \
/p:ContainerRepository=${{ vars.ECR_REPOSITORY }} \
/p:ContainerImageTag=worker-${{ github.sha }}

- name: Build and Push DatabaseSeeding Container
run: |
Expand Down Expand Up @@ -103,22 +113,31 @@ jobs:

- name: Run database migration
run: |
kubectl -n ${KUBE_NAMESPACE} delete job migrator-job --ignore-not-found=true
kubectl -n ${KUBE_NAMESPACE} apply -f deploy/migrator-job.yml
kubectl -n ${KUBE_NAMESPACE} wait --for=condition=complete --timeout=300s job/migrator-job
kubectl -n ${KUBE_NAMESPACE} delete pod -l app=migrator --wait=false || true
kubectl -n ${KUBE_NAMESPACE} apply -f deploy/migrator-pod.yml
if ! kubectl -n ${KUBE_NAMESPACE} wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=300s pod/migrator-${{ github.sha }}; then
echo "Migration pod did not succeed within timeout."
kubectl -n ${KUBE_NAMESPACE} describe pod/migrator-${{ github.sha }} || true
exit 1
fi
env:
KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }}

- name: Run database seeding
run: |
kubectl -n ${KUBE_NAMESPACE} delete job seeder-job --ignore-not-found=true
kubectl -n ${KUBE_NAMESPACE} apply -f deploy/seeder-job.yml
kubectl -n ${KUBE_NAMESPACE} wait --for=condition=complete --timeout=300s job/seeder-job
kubectl -n ${KUBE_NAMESPACE} delete pod -l app=seeder --wait=false || true
kubectl -n ${KUBE_NAMESPACE} apply -f deploy/seeder-pod.yml
if ! kubectl -n ${KUBE_NAMESPACE} wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=300s pod/seeder-${{ github.sha }}; then
echo "Seeder pod did not succeed within timeout."
kubectl -n ${KUBE_NAMESPACE} describe pod/seeder-${{ github.sha }} || true
exit 1
fi
env:
KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }}

- name: Deploy to Kubernetes
run: |
rm -f deploy/migrator-pod.yml deploy/seeder-pod.yml
kubectl -n ${KUBE_NAMESPACE} apply -f deploy/
env:
KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }}
5 changes: 5 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
<PackageVersion Include="Aspire.Hosting.Kubernetes" Version="13.2.0-preview.1.26170.3" />
<PackageVersion Include="Aspire.Hosting.RabbitMQ" Version="13.4.5" />
<PackageVersion Include="Aspire.Hosting.Redis" Version="13.4.3" />
<PackageVersion Include="Aspire.Hosting.SqlServer" Version="13.4.5" />
<PackageVersion Include="AutoMapper" Version="14.0.0" />
<PackageVersion Include="AWSSDK.Extensions.NETCore.Setup" Version="4.0.4.9" />
Expand Down Expand Up @@ -43,13 +44,15 @@
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="10.0.9" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="7.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
Expand Down Expand Up @@ -85,5 +88,7 @@
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.2" />
<PackageVersion Include="Toolbelt.Blazor.HotKeys2" Version="6.2.1" />
<PackageVersion Include="ZiggyCreatures.FusionCache" Version="2.6.0" />
<PackageVersion Include="ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis" Version="2.6.0" />
<PackageVersion Include="ZiggyCreatures.FusionCache.Serialization.SystemTextJson" Version="2.6.0" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions cats.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<Project Path="src/Domain/Domain.csproj" />
<Project Path="src/Infrastructure/Infrastructure.csproj" />
<Project Path="src/Server.UI/Server.UI.csproj" />
<Project Path="src/Worker/Worker.csproj" />
</Folder>
<Folder Name="/src/Aspire/">
<Project Path="src/Aspire/Cats.AppHost/Cats.AppHost.csproj" />
Expand Down
14 changes: 13 additions & 1 deletion infra/cats-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ spec:
value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;"
- name: ConnectionStrings__rabbit
value: "amqp://$(RABBIT_USER):$(RABBIT_PASS)@rabbitmq-service:5672"
- name: ConnectionStrings__redis
value: "redis-service:6379"
- name: AWS__RootFolder
value: "Files"
- name: Sentry__Dsn
Expand All @@ -98,4 +100,14 @@ spec:
- name: Sentry__Release
value: "${APP_VERSION}"
- name: AppConfigurationSettings__Version
value: "${APP_VERSION}"
value: "${APP_VERSION}"
- name: Features__UseWorkerForJobs
value: "true"
- name: Features__PresenceHub__Enabled
value: "true"
- name: Features__PresenceHub__RelayUserPresenceNotifications
value: "true"
- name: Features__UseSignalRBackplane
value: "true"
- name: WorkerOptions__BaseUrl
value: "http://cats-worker-service:8080"
94 changes: 94 additions & 0 deletions infra/cats-worker-deployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: cats-worker-deployment
labels:
app: cats-worker
spec:
replicas: 1 # Quartz jobs must not run concurrently across multiple pods
selector:
matchLabels:
app: cats-worker
template:
metadata:
labels:
app: cats-worker
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
serviceAccountName: ${NAMESPACE}
containers:
- name: cats-worker
image: ${REGISTRY}/${REPOSITORY}:worker-${IMAGE_TAG}
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /alive
port: 8080
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /alive
port: 8080
periodSeconds: 20
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
privileged: false
capabilities:
drop: ["ALL"]
env:
- name: DATABASE_ADDRESS
valueFrom:
secretKeyRef:
name: rds-mssql-instance-output
key: rds_instance_address
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: rds-mssql-instance-output
key: database_username
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: rds-mssql-instance-output
key: database_password
- name: RABBIT_USER
valueFrom:
secretKeyRef:
name: config
key: RABBIT_USER
- name: RABBIT_PASS
valueFrom:
secretKeyRef:
name: config
key: RABBIT_PASS
- name: DOTNET_ENVIRONMENT
value: "${DOTNET_ENVIRONMENT}"
- name: ConnectionStrings__CatsDb
value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;"
- name: ConnectionStrings__rabbit
value: "amqp://$(RABBIT_USER):$(RABBIT_PASS)@rabbitmq-service:5672"
- name: Sentry__Dsn
valueFrom:
secretKeyRef:
name: config
key: SentryDsn
- name: Sentry__Environment
value: "${DOTNET_ENVIRONMENT}-CloudPlatform"
- name: Sentry__Release
value: "${APP_VERSION}"
- name: AppConfigurationSettings__Version
value: "${APP_VERSION}"
11 changes: 11 additions & 0 deletions infra/cats-worker-service.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: cats-worker-service
spec:
selector:
app: cats-worker
ports:
- name: http
port: 8080
targetPort: 8080
48 changes: 0 additions & 48 deletions infra/migrator-job.yml

This file was deleted.

41 changes: 41 additions & 0 deletions infra/migrator-pod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apiVersion: v1
kind: Pod
metadata:
name: migrator-${IMAGE_TAG}
labels:
app: migrator
spec:
serviceAccountName: ${NAMESPACE}
restartPolicy: OnFailure
securityContext:
seccompProfile:
type: RuntimeDefault
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
containers:
- name: migrator
image: ${REGISTRY}/${REPOSITORY}:migrator-${IMAGE_TAG}
securityContext:
allowPrivilegeEscalation: false
privileged: false
capabilities:
drop: ["ALL"]
env:
- name: DATABASE_ADDRESS
valueFrom:
secretKeyRef:
name: rds-mssql-instance-output
key: rds_instance_address
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: rds-mssql-instance-output
key: database_username
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: rds-mssql-instance-output
key: database_password
- name: ConnectionStrings__CatsDb
value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;"
40 changes: 40 additions & 0 deletions infra/redis-deployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
labels:
app: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis # this should match the selector in service.yml
template:
metadata:
labels:
app: redis # this should match the selector in service.yml
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
serviceAccountName: ${NAMESPACE}
containers:
- name: redis
image: redis:7.4-alpine@sha256:b1addbe72465a718643cff9e60a58e6df1841e29d6d7d60c9a85d8d72f08d1a7
imagePullPolicy: Always
# Used only as a SignalR backplane and Fusion cache, so all data is
# ephemeral and rebuildable. Disable RDB/AOF persistence to avoid the
# MISCONF "stop-writes-on-bgsave-error" failure when /data is not writable.
args:
- --save
- ""
- --appendonly
- "no"
ports:
- containerPort: 6379
securityContext:
allowPrivilegeEscalation: false
privileged: false
11 changes: 11 additions & 0 deletions infra/redis-service.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis # this should match the pod label in deployment.yml
ports:
- name: redis
port: 6379
targetPort: 6379
Loading