Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soft multi org #1

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
.postgres-data
.postgres-*
.tool-versions
.env
.envrc
.swc
node_modules
ca-cert*
.data/*

dist
dist
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cSpell.words": ["awilix"]
}
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Multi tenancy sketches

This project is a synthetic test project for implementing multi tenancy.
Its a functional graphql api backend, that is easiest to access from the apollo graphql explorer.
The api and the explorer are available by default at `http://localhost:4000` by default,
after the app and its dependencies have been started

## Project setup

This project is using [`pnpm`](https://pnpm.io/) as its package manager.
To start the required databases or other dependencies, run `docker compose up -d`

## About Postgres setup

I had to change the `wal_level` on my local postgres instances
it is done with running the SQL command below, and restating the database server:

```sql
ALTER SYSTEM SET wal_level = logical;
```

When registering a new region on a DigitalOcean postgres server, the default user doesn't have the required roles to set up a subscription.
On DO we can use [aiven-extras](https://github.com/aiven/aiven-extras) to create subs without root access.
The current branch is utilizing just that. But it needs a setup step executed on each database, that is registered as a region

Run this in a `psql` shell

```sql
CREATE EXTENSION aiven_extras;
```

Note: Postgres subscriptions (which we use) in the same db server don't work that easily; easiest way to get things going is to set up multiple db servers locally.

## Project description

The app has these basic concepts:

### User

A user of the system (obviously). User authn is not implemented, authz is very simplified.

### Resource

This is an abstract object representing a project, that multiple users might work on.
The notion of work on is currently implemented as the comment create action.
A resource might belong to an organization or belong to the default (null) organization.

### Comment

A text note, that belongs to a given resource, created by a user.

### Region

A geo-located data storage region, currently implemented as a PostgresSQL database server.
When providing a connection url to a region, make sure to not include a database name or any trailing `/`-s in the url.

### Organization

A collection of users and an owner of resource. Any user may create organizations.
Organizations may be granted access to any given region. That action creates a new database in the region DB server. migrates it to the latest DB schema and sets up user and resource publish and subscribe mechanisms.

## Steps to flex this POC

Using the exposed graphql explorer, you can go ahead and

- create a user
- create an organisation
- add the user to the organisation
- create regions & associate them with an organisation
- create a resource in the default organisation, or for a specific organisation & region
- etc.
25 changes: 25 additions & 0 deletions aiven_postgres/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM postgres:14.5-alpine as builder

RUN apk add --no-cache 'git=~2.36' \
'build-base=~0.5' \
'clang=~13.0' \
'llvm13=~13.0'

WORKDIR /
RUN git clone --branch 1.1.9 https://github.com/aiven/aiven-extras.git aiven-extras

WORKDIR /aiven-extras
RUN git checkout 36598ab \
&& git clean -df \
&& make \
&& make install

FROM postgres:14.5-alpine

COPY --from=builder /aiven-extras/aiven_extras.control /usr/local/share/postgresql/extension/aiven_extras.control
COPY --from=builder /aiven-extras/sql/aiven_extras.sql /usr/local/share/postgresql/extension/aiven_extras--1.1.9.sql
COPY --from=builder /aiven-extras/aiven_extras.so /usr/local/lib/postgresql/aiven_extras.so

EXPOSE 5432

CMD ["postgres"]
50 changes: 45 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,53 @@
version: "3.9"
version: '3.9'

services:
postgres:
image: postgres:16-alpine
main-db:
build:
context: aiven_postgres
dockerfile: Dockerfile
volumes:
- ./.data/main-db:/var/lib/postgresql/data
ports:
- 5454:5432
environment:
- POSTGRES_PASSWORD=speckle
- POSTGRES_USER=speckle
- POSTGRES_DB=speckle
extra_hosts:
- host.docker.internal:host-gateway

region-1-db:
build:
context: aiven_postgres
dockerfile: Dockerfile
volumes:
- ./.data/region-1-db:/var/lib/postgresql/data
ports:
- 5455:5432
environment:
- POSTGRES_PASSWORD=speckle
- POSTGRES_USER=speckle
- POSTGRES_DB=speckle
depends_on:
- main-db

extra_hosts:
- host.docker.internal:host-gateway

region-2-db:
build:
context: aiven_postgres
dockerfile: Dockerfile
volumes:
- ./.postgres-data:/var/lib/postgresql/data
- ./.data/region-2-db:/var/lib/postgresql/data
ports:
- 5456:5432
environment:
- POSTGRES_PASSWORD=speckle
- POSTGRES_USER=speckle
- POSTGRES_DB=speckle_main
- POSTGRES_DB=speckle
depends_on:
- main-db

extra_hosts:
- host.docker.internal:host-gateway
22 changes: 20 additions & 2 deletions knexfile.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
export default {
import { Knex } from 'knex'
import fs from 'fs'
import path from 'path'

console.log(`foobar ${process.env.POSTGRES_CA_CERT_PATH}`)

const config: Knex.Config = {
client: 'pg',
connection: process.env.POSTGRES_URL,
connection: {
connectionString: process.env.POSTGRES_URL,
ssl: process.env.POSTGRES_CA_CERT_PATH
? {
ca: fs.readFileSync(
path.resolve(__dirname, process.env.POSTGRES_CA_CERT_PATH)
),
rejectUnauthorized: true
}
: undefined
},
migrations: {
directory: 'src/migrations',
extension: 'ts'
}
}

export default config
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "src/app.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "vitest",
"lint": "ts-standard",
"tsx": "tsx",
"lint:fix": "ts-standard --fix",
Expand All @@ -26,10 +26,14 @@
"ts-node": "^10.9.2",
"ts-standard": "^12.0.2",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"vitest": "^1.2.2"
},
"dependencies": {
"@apollo/server": "^4.10.0",
"awilix": "^11.0.0",
"crypto-random-string": "^3.0.0",
"dataloader": "^2.2.2",
"dotenv": "^16.4.1",
"graphql": "^16.8.1",
"graphql-scalars": "^1.22.4",
Expand Down
8 changes: 8 additions & 0 deletions parse_cert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pathlib import Path
import json

cert = Path("./ca-cert").read_text()

cert_json = json.dumps({"cert": cert})

Path("./ca-cert.json").write_text(cert_json)
Loading