forked from pulumi/examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add google-native-ts-k8s-python-postgresql example
- Loading branch information
Showing
29 changed files
with
1,146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Poetry's virtual env | ||
/.venv | ||
# hidden folders in the root directory | ||
/.* | ||
!/.gitignore | ||
!/.pre-commit-config.yaml |
61 changes: 61 additions & 0 deletions
61
google-native-ts-k8s-python-postgresql/.pre-commit-config.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
exclude: "^.venv/.*|.html" | ||
repos: | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: "v4.0.1" | ||
hooks: | ||
- id: trailing-whitespace | ||
exclude: ^VERSION.txt$ | ||
- id: check-yaml | ||
- id: pretty-format-json | ||
args: ["--autofix"] | ||
- id: check-merge-conflict | ||
- repo: https://github.com/pre-commit/mirrors-prettier | ||
rev: "v2.5.1" | ||
hooks: | ||
- id: prettier | ||
name: Run prettier on infra source code | ||
types_or: [ts] | ||
- repo: local | ||
hooks: | ||
- id: lockfile | ||
name: Update poetry.lock | ||
language: system | ||
entry: poetry lock --no-update | ||
files: ^pyproject.toml|poetry.lock$ | ||
pass_filenames: false | ||
- id: version | ||
name: Update VERSION.txt -> pyproject.toml | ||
language: system | ||
entry: bash -c 'cat VERSION.txt | xargs poetry version' | ||
files: ^pyproject.toml|VERSION.txt$ | ||
pass_filenames: false | ||
- id: requirements.txt | ||
name: Export app dependencies to app/requirements.txt | ||
language: system | ||
entry: poetry export -f requirements.txt --output app/requirements.txt | ||
files: ^pyproject.toml|poetry.lock$ | ||
pass_filenames: false | ||
- id: isort | ||
name: Run isort on app source code | ||
language: system | ||
entry: poetry run isort | ||
types: [python] | ||
pass_filenames: true | ||
- id: black | ||
name: Run black on app source code | ||
language: system | ||
entry: poetry run black | ||
types: [python] | ||
pass_filenames: true | ||
- id: flake8 | ||
name: Run flake8 on app source code | ||
language: system | ||
entry: poetry run flake8 | ||
types: [python] | ||
pass_filenames: true | ||
- id: mypy | ||
name: Run mypy on app source code | ||
language: system | ||
entry: poetry run mypy | ||
types: [python] | ||
pass_filenames: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
.PHONY: install-poetry .clean test test-mutation docs-build docs-serve | ||
|
||
GIT_SHA = $(shell git rev-parse --short HEAD) | ||
PACKAGE_VERSION = $(shell poetry version -s | cut -d+ -f1) | ||
|
||
.install-poetry: | ||
@echo "---- 👷 Installing build dependencies ----" | ||
deactivate > /dev/null 2>&1 || true | ||
pip install -U pip wheel | ||
poetry -V || pip install -U poetry | ||
touch .install-poetry | ||
|
||
install-poetry: .install-poetry | ||
|
||
.init: .install-poetry | ||
@echo "---- 📦 Building package ----" | ||
rm -rf .venv | ||
python -m pip install -U pip wheel | ||
poetry install | ||
git init . | ||
poetry run pre-commit install --install-hooks | ||
touch .init | ||
|
||
.clean: | ||
rm -rf .init .mypy_cache .pytest_cache | ||
poetry -V || rm -rf .install-poetry | ||
|
||
init: .clean .init | ||
@echo ---- 🔧 Re-initialized project ---- | ||
|
||
lint: .init | ||
@echo ---- ⏳ Running linters ---- | ||
@(poetry run pre-commit run --all-files && echo "---- ✅ Linting passed ----" && exit 0|| echo "---- ❌ Linting failed ----" && exit 1) | ||
|
||
test: .init | ||
@echo ---- ⏳ Running tests ---- | ||
@(poetry run pytest -v --cov --cov-report term && echo "---- ✅ Tests passed ----" && exit 0 || echo "---- ❌ Tests failed ----" && exit 1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
# Containerized Python Xpresso app, deployed to GKE via Pulumi | ||
|
||
This example is a full end to end example of delivering a containerized Xpresso app. | ||
|
||
Using an infrastructure as code approach, running this repo will: | ||
|
||
- Provision a GKE cluster | ||
- Provisions a fully managed Google Cloud SQL PostgreSQL database | ||
- Builds a containerized Xpresso app, and it to the Google Artifact Registry | ||
- Deploys that container image as a Kubernetes Service inside of the provisioned GKE cluster | ||
|
||
## Prerequisites | ||
|
||
Before trying to deploy this example, please make sure you have performed all of the following tasks: | ||
|
||
- [downloaded and installed the Pulumi CLI](https://www.pulumi.com/docs/get-started/install/). | ||
- [downloaded and installed Docker](https://docs.docker.com/install/) | ||
- [signed up for Google Cloud](https://cloud.google.com/free/) | ||
- [followed the instructions here](https://www.pulumi.com/docs/intro/cloud-providers/gcp/setup/) to connect Pulumi to your Google Cloud account. | ||
|
||
This example assumes that you have Google Cloud's `gcloud` CLI on your path. | ||
This is installed as part of the | ||
[Google Cloud SDK](https://cloud.google.com/sdk/). | ||
|
||
As part of this example, we will setup and deploy a Kubernetes cluster on GKE. | ||
You may also want to install [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) if you would like to directly interact with the underlying Kubernetes cluster. | ||
|
||
## Deploying the Example | ||
|
||
### Set up your GCP Project | ||
|
||
You'll need to create a new GCP project (or use an existing one). | ||
Enable the following APIs in GCP if they are not already enabled: | ||
|
||
- [Artifact Registry](https://cloud.google.com/artifact-registry/docs/enable-service#enable) | ||
- [Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin) | ||
- [Cloud SQL](https://cloud.google.com/sql/docs/mysql/admin-api#enable_the_api) | ||
|
||
If you've configured `gcloud` locally and pointed it at your project you can run: | ||
|
||
```shell | ||
gcloud services enable artifactregistry.googleapis.com | ||
gcloud services enable sqladmin.googleapis.com | ||
gcloud services enable container.googleapis.com | ||
``` | ||
|
||
### Configure Docker | ||
|
||
We'll be pushing a docker image to Artifact Registry, so configure docker for authentication: | ||
|
||
```shell | ||
gcloud auth configure-docker | ||
``` | ||
|
||
### Configure Pulumi | ||
|
||
Now you're ready to get started with the repo. | ||
Clone the repo then cd into the infra directory: | ||
|
||
```shell | ||
cd infra | ||
``` | ||
|
||
Now set up the Pulumi stack: | ||
|
||
```shell | ||
pulumi stack init dev | ||
``` | ||
|
||
Set the required configuration variables for this program: | ||
|
||
```shell | ||
pulumi config set xpresso-gke-demo:project [your-gcp-project-here] | ||
pulumi config set xpresso-gke-demo:region us-west1 # any valid region | ||
``` | ||
|
||
### Deploy | ||
|
||
Deploy everything with the `pulumi up` command. | ||
This provisions all the GCP resources necessary, including your GKE cluster and database, as well as building and publishing your container image, all in a single gesture: | ||
|
||
```shell | ||
pulumi up | ||
``` | ||
|
||
This will show you a preview, ask for confirmation, and then chug away at provisioning your cluster. | ||
|
||
```shell | ||
pulumi destroy | ||
pulumi stack rm | ||
``` | ||
|
||
## Local Development | ||
|
||
This package comes set up with some basic facilities for local development: | ||
|
||
- Make targets for bootstrapping, testing and linting | ||
- Git hooks (via [pre-commit](https://pre-commit.com)) to do code formatting and syncing of derived files | ||
|
||
To set up locally you'll need to have Python 3.10 installed. | ||
If you're using [pyenv](https://github.com/pyenv/pyenv), remember to select the right Python version. | ||
|
||
### Bootstrapping | ||
|
||
Run: | ||
|
||
```shell | ||
make init | ||
``` | ||
|
||
This will: | ||
|
||
- Create a virtual environment using [Poetry](https://python-poetry.org) and install all of the app's dependencies. | ||
- Install git hooks via pre-commit. | ||
|
||
### Testing | ||
|
||
Run: | ||
|
||
```shell | ||
make test | ||
``` | ||
|
||
### Linting | ||
|
||
Linting will auto-run on each commit. | ||
To disable this for a single commit, run: | ||
|
||
```shell | ||
git commit -m "<commit message>" --no-verify | ||
``` | ||
|
||
To disable this permanently: | ||
|
||
```shell | ||
poetry run pre-commit --uninstall | ||
``` | ||
|
||
To run linting manually (without committing): | ||
|
||
```shell | ||
make lint | ||
``` | ||
|
||
### Versioning | ||
|
||
So that we can include info about the project version in our infra (in particular, we want the version in the image tag) we keep the source of truth in a `VERSION.txt` file. | ||
This is also convenient to programmatically check for version bumps (for example in CI). | ||
|
||
This version is synced to the Python package version (in `pyproject.toml`) via a pre-commit hook. | ||
|
||
### Dependency specification | ||
|
||
Dependencies are specified in `pyproject.toml` and managed by Poetry. | ||
But we don't want to have to install Poetry to build the image, so we export Poetry's lockfile to a `app/requirements.txt` via a pre-commit hook. | ||
Then when we build the image we can just `pip install -r app/requirements.txt`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0.1.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
** | ||
|
||
!/app | ||
!/requirements.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM python:3.10-slim | ||
RUN mkdir /opt/project | ||
WORKDIR /opt/project | ||
COPY ./requirements.txt . | ||
RUN pip install --no-cache-dir -U pip wheel && \ | ||
pip install --no-cache-dir -r requirements.txt && \ | ||
rm -rf requirements.txt | ||
COPY app ./app/ | ||
ENV PYTHONPATH "${PYTHONPATH}:/opt/project" | ||
CMD ["python", "app/main.py"] |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from typing import Literal | ||
|
||
from pydantic import BaseSettings, SecretStr | ||
|
||
|
||
class Config(BaseSettings): | ||
app_port: int | ||
app_host: str | ||
db_username: str | ||
db_password: SecretStr | None = None | ||
db_host: str | ||
db_port: int | ||
db_database_name: str | ||
log_level: Literal["DEBUG", "INFO"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import asyncpg # type: ignore[import] | ||
|
||
|
||
class ConnectionHealth: | ||
def __init__(self, pool: asyncpg.Pool) -> None: | ||
self.pool = pool | ||
|
||
async def is_connected(self) -> bool: | ||
conn: asyncpg.Connection | ||
async with self.pool.acquire() as conn: | ||
return await conn.fetchval("SELECT 1") == 1 # type: ignore |
42 changes: 42 additions & 0 deletions
42
google-native-ts-k8s-python-postgresql/app/app/dependencies.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from typing import Annotated, AsyncGenerator | ||
|
||
import asyncpg # type: ignore[import] | ||
from xpresso import Depends | ||
|
||
from app.config import Config | ||
from app.db import ConnectionHealth as DatabaseConnectionHealth | ||
|
||
|
||
|
||
async def get_pool(config: Config) -> AsyncGenerator[asyncpg.Pool, None]: | ||
# password is optional: | ||
# - cloudsql proxy won't work with it | ||
# - docker run postgres won't work without it | ||
connection_kwargs = dict( | ||
host=config.db_host, | ||
port=config.db_port, | ||
database=config.db_database_name, | ||
user=config.db_username | ||
) | ||
if config.db_password is not None: | ||
connection_kwargs["password"] = config.db_password.get_secret_value() | ||
async with asyncpg.create_pool(**connection_kwargs) as pool: # type: ignore | ||
yield pool | ||
|
||
|
||
InjectDBConnectionPool = Annotated[asyncpg.Pool, Depends(get_pool, scope="app")] | ||
|
||
|
||
async def get_connection(pool: InjectDBConnectionPool) -> AsyncGenerator[asyncpg.Connection, None]: | ||
async with pool.acquire() as conn: # type: ignore | ||
yield conn | ||
|
||
|
||
InjectDBConnection = Annotated[asyncpg.Connection, Depends(get_connection)] | ||
|
||
|
||
def get_db_health(pool: InjectDBConnectionPool) -> DatabaseConnectionHealth: | ||
return DatabaseConnectionHealth(pool) | ||
|
||
|
||
InjectDBHealth = Annotated[DatabaseConnectionHealth, Depends(get_db_health, scope="app")] |
11 changes: 11 additions & 0 deletions
11
google-native-ts-k8s-python-postgresql/app/app/lifespan.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from contextlib import asynccontextmanager | ||
from typing import AsyncGenerator | ||
|
||
from app.dependencies import InjectDBHealth | ||
|
||
|
||
@asynccontextmanager | ||
async def lifespan(db_health: InjectDBHealth) -> AsyncGenerator[None, None]: | ||
if not (await db_health.is_connected()): | ||
raise RuntimeError("Failed to connect to DB") | ||
yield |
Oops, something went wrong.