mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Compare commits
2 commits
main
...
cloudrun-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ade10cad0 | ||
|
|
e40fad0287 |
9 changed files with 111 additions and 22 deletions
13
.github/workflows/deploy-latest.yml
vendored
13
.github/workflows/deploy-latest.yml
vendored
|
|
@ -4,7 +4,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- 1.0-dev
|
- cloudrun-fix
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -101,12 +101,13 @@ jobs:
|
||||||
# jq '.plugins |= . + {"datasette-ephemeral-tables": {"table_ttl": 900}}' \
|
# jq '.plugins |= . + {"datasette-ephemeral-tables": {"table_ttl": 900}}' \
|
||||||
# > metadata.json
|
# > metadata.json
|
||||||
# cat metadata.json
|
# cat metadata.json
|
||||||
- name: Set up Cloud Run
|
- id: auth
|
||||||
uses: google-github-actions/setup-gcloud@v0
|
name: Authenticate to Google Cloud
|
||||||
|
uses: google-github-actions/auth@v2
|
||||||
with:
|
with:
|
||||||
version: '318.0.0'
|
credentials_json: ${{ secrets.GCP_SA_KEY }}
|
||||||
service_account_email: ${{ secrets.GCP_SA_EMAIL }}
|
- name: Set up Cloud SDK
|
||||||
service_account_key: ${{ secrets.GCP_SA_KEY }}
|
uses: google-github-actions/setup-gcloud@v3
|
||||||
- name: Deploy to Cloud Run
|
- name: Deploy to Cloud Run
|
||||||
env:
|
env:
|
||||||
LATEST_DATASETTE_SECRET: ${{ secrets.LATEST_DATASETTE_SECRET }}
|
LATEST_DATASETTE_SECRET: ${{ secrets.LATEST_DATASETTE_SECRET }}
|
||||||
|
|
|
||||||
11
.github/workflows/publish.yml
vendored
11
.github/workflows/publish.yml
vendored
|
|
@ -73,12 +73,13 @@ jobs:
|
||||||
DISABLE_SPHINX_INLINE_TABS=1 sphinx-build -b xml . _build
|
DISABLE_SPHINX_INLINE_TABS=1 sphinx-build -b xml . _build
|
||||||
sphinx-to-sqlite ../docs.db _build
|
sphinx-to-sqlite ../docs.db _build
|
||||||
cd ..
|
cd ..
|
||||||
- name: Set up Cloud Run
|
- id: auth
|
||||||
uses: google-github-actions/setup-gcloud@v0
|
name: Authenticate to Google Cloud
|
||||||
|
uses: google-github-actions/auth@v2
|
||||||
with:
|
with:
|
||||||
version: '318.0.0'
|
credentials_json: ${{ secrets.GCP_SA_KEY }}
|
||||||
service_account_email: ${{ secrets.GCP_SA_EMAIL }}
|
- name: Set up Cloud SDK
|
||||||
service_account_key: ${{ secrets.GCP_SA_KEY }}
|
uses: google-github-actions/setup-gcloud@v3
|
||||||
- name: Deploy stable-docs.datasette.io to Cloud Run
|
- name: Deploy stable-docs.datasette.io to Cloud Run
|
||||||
run: |-
|
run: |-
|
||||||
gcloud config set run/region us-central1
|
gcloud config set run/region us-central1
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.11.0-slim-bullseye as build
|
FROM python:3.14-slim-trixie as build
|
||||||
|
|
||||||
# Version of Datasette to install, e.g. 0.55
|
# Version of Datasette to install, e.g. 0.55
|
||||||
# docker build . -t datasette --build-arg VERSION=0.55
|
# docker build . -t datasette --build-arg VERSION=0.55
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import click
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from subprocess import check_call, check_output
|
from subprocess import CalledProcessError, check_call, check_output
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
add_common_publish_arguments_and_options,
|
add_common_publish_arguments_and_options,
|
||||||
|
|
@ -55,13 +55,32 @@ def publish_subcommand(publish):
|
||||||
@click.option(
|
@click.option(
|
||||||
"--max-instances",
|
"--max-instances",
|
||||||
type=int,
|
type=int,
|
||||||
help="Maximum Cloud Run instances",
|
default=1,
|
||||||
|
show_default=True,
|
||||||
|
help="Maximum Cloud Run instances (use 0 to remove the limit)",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--min-instances",
|
"--min-instances",
|
||||||
type=int,
|
type=int,
|
||||||
help="Minimum Cloud Run instances",
|
help="Minimum Cloud Run instances",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--artifact-repository",
|
||||||
|
default="datasette",
|
||||||
|
show_default=True,
|
||||||
|
help="Artifact Registry repository to store the image",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--artifact-region",
|
||||||
|
default="us",
|
||||||
|
show_default=True,
|
||||||
|
help="Artifact Registry location (region or multi-region)",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--artifact-project",
|
||||||
|
default=None,
|
||||||
|
help="Project ID for Artifact Registry (defaults to the active project)",
|
||||||
|
)
|
||||||
def cloudrun(
|
def cloudrun(
|
||||||
files,
|
files,
|
||||||
metadata,
|
metadata,
|
||||||
|
|
@ -91,6 +110,9 @@ def publish_subcommand(publish):
|
||||||
apt_get_extras,
|
apt_get_extras,
|
||||||
max_instances,
|
max_instances,
|
||||||
min_instances,
|
min_instances,
|
||||||
|
artifact_repository,
|
||||||
|
artifact_region,
|
||||||
|
artifact_project,
|
||||||
):
|
):
|
||||||
"Publish databases to Datasette running on Cloud Run"
|
"Publish databases to Datasette running on Cloud Run"
|
||||||
fail_if_publish_binary_not_installed(
|
fail_if_publish_binary_not_installed(
|
||||||
|
|
@ -100,6 +122,21 @@ def publish_subcommand(publish):
|
||||||
"gcloud config get-value project", shell=True, universal_newlines=True
|
"gcloud config get-value project", shell=True, universal_newlines=True
|
||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
|
artifact_project = artifact_project or project
|
||||||
|
|
||||||
|
# Ensure Artifact Registry exists for the target image
|
||||||
|
_ensure_artifact_registry(
|
||||||
|
artifact_project=artifact_project,
|
||||||
|
artifact_region=artifact_region,
|
||||||
|
artifact_repository=artifact_repository,
|
||||||
|
)
|
||||||
|
|
||||||
|
artifact_host = (
|
||||||
|
artifact_region
|
||||||
|
if artifact_region.endswith("-docker.pkg.dev")
|
||||||
|
else f"{artifact_region}-docker.pkg.dev"
|
||||||
|
)
|
||||||
|
|
||||||
if not service:
|
if not service:
|
||||||
# Show the user their current services, then prompt for one
|
# Show the user their current services, then prompt for one
|
||||||
click.echo("Please provide a service name for this deployment\n")
|
click.echo("Please provide a service name for this deployment\n")
|
||||||
|
|
@ -117,6 +154,11 @@ def publish_subcommand(publish):
|
||||||
click.echo("")
|
click.echo("")
|
||||||
service = click.prompt("Service name", type=str)
|
service = click.prompt("Service name", type=str)
|
||||||
|
|
||||||
|
image_id = (
|
||||||
|
f"{artifact_host}/{artifact_project}/"
|
||||||
|
f"{artifact_repository}/datasette-{service}"
|
||||||
|
)
|
||||||
|
|
||||||
extra_metadata = {
|
extra_metadata = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"license": license,
|
"license": license,
|
||||||
|
|
@ -173,7 +215,6 @@ def publish_subcommand(publish):
|
||||||
print(fp.read())
|
print(fp.read())
|
||||||
print("\n====================\n")
|
print("\n====================\n")
|
||||||
|
|
||||||
image_id = f"gcr.io/{project}/datasette-{service}"
|
|
||||||
check_call(
|
check_call(
|
||||||
"gcloud builds submit --tag {}{}".format(
|
"gcloud builds submit --tag {}{}".format(
|
||||||
image_id, " --timeout {}".format(timeout) if timeout else ""
|
image_id, " --timeout {}".format(timeout) if timeout else ""
|
||||||
|
|
@ -187,7 +228,7 @@ def publish_subcommand(publish):
|
||||||
("--max-instances", max_instances),
|
("--max-instances", max_instances),
|
||||||
("--min-instances", min_instances),
|
("--min-instances", min_instances),
|
||||||
):
|
):
|
||||||
if value:
|
if value is not None:
|
||||||
extra_deploy_options.append("{} {}".format(option, value))
|
extra_deploy_options.append("{} {}".format(option, value))
|
||||||
check_call(
|
check_call(
|
||||||
"gcloud run deploy --allow-unauthenticated --platform=managed --image {} {}{}".format(
|
"gcloud run deploy --allow-unauthenticated --platform=managed --image {} {}{}".format(
|
||||||
|
|
@ -199,6 +240,52 @@ def publish_subcommand(publish):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_artifact_registry(artifact_project, artifact_region, artifact_repository):
|
||||||
|
"""Ensure Artifact Registry API is enabled and the repository exists."""
|
||||||
|
|
||||||
|
enable_cmd = (
|
||||||
|
"gcloud services enable artifactregistry.googleapis.com "
|
||||||
|
f"--project {artifact_project} --quiet"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
check_call(enable_cmd, shell=True)
|
||||||
|
except CalledProcessError as exc:
|
||||||
|
raise click.ClickException(
|
||||||
|
"Failed to enable artifactregistry.googleapis.com. "
|
||||||
|
"Please ensure you have permissions to manage services."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
describe_cmd = (
|
||||||
|
"gcloud artifacts repositories describe {repo} --project {project} "
|
||||||
|
"--location {location} --quiet"
|
||||||
|
).format(
|
||||||
|
repo=artifact_repository,
|
||||||
|
project=artifact_project,
|
||||||
|
location=artifact_region,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
check_call(describe_cmd, shell=True)
|
||||||
|
return
|
||||||
|
except CalledProcessError:
|
||||||
|
create_cmd = (
|
||||||
|
"gcloud artifacts repositories create {repo} --repository-format=docker "
|
||||||
|
'--location {location} --project {project} --description "Datasette Cloud Run images" --quiet'
|
||||||
|
).format(
|
||||||
|
repo=artifact_repository,
|
||||||
|
location=artifact_region,
|
||||||
|
project=artifact_project,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
check_call(create_cmd, shell=True)
|
||||||
|
click.echo(f"Created Artifact Registry repository '{artifact_repository}'")
|
||||||
|
except CalledProcessError as exc:
|
||||||
|
raise click.ClickException(
|
||||||
|
"Failed to create Artifact Registry repository. "
|
||||||
|
"Use --artifact-repository/--artifact-region to point to an existing repo "
|
||||||
|
"or create one manually."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def get_existing_services():
|
def get_existing_services():
|
||||||
services = json.loads(
|
services = json.loads(
|
||||||
check_output(
|
check_output(
|
||||||
|
|
|
||||||
|
|
@ -412,7 +412,7 @@ def make_dockerfile(
|
||||||
"/usr/lib/x86_64-linux-gnu/mod_spatialite.so"
|
"/usr/lib/x86_64-linux-gnu/mod_spatialite.so"
|
||||||
)
|
)
|
||||||
return """
|
return """
|
||||||
FROM python:3.11.0-slim-bullseye
|
FROM python:3.14-slim-trixie
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
{apt_get_extras}
|
{apt_get_extras}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.11.0-slim-bullseye
|
FROM python:3.14-slim-trixie
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y apache2 supervisor && \
|
apt-get install -y apache2 supervisor && \
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ Here's example output for the package command::
|
||||||
|
|
||||||
datasette package parlgov.db --extra-options="--setting sql_time_limit_ms 2500"
|
datasette package parlgov.db --extra-options="--setting sql_time_limit_ms 2500"
|
||||||
Sending build context to Docker daemon 4.459MB
|
Sending build context to Docker daemon 4.459MB
|
||||||
Step 1/7 : FROM python:3.11.0-slim-bullseye
|
Step 1/7 : FROM python:3.14-slim-trixie
|
||||||
---> 79e1dc9af1c1
|
---> 79e1dc9af1c1
|
||||||
Step 2/7 : COPY . /app
|
Step 2/7 : COPY . /app
|
||||||
---> Using cache
|
---> Using cache
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class CaptureDockerfile:
|
||||||
|
|
||||||
|
|
||||||
EXPECTED_DOCKERFILE = """
|
EXPECTED_DOCKERFILE = """
|
||||||
FROM python:3.11.0-slim-bullseye
|
FROM python:3.14-slim-trixie
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ def test_publish_cloudrun_plugin_secrets(
|
||||||
)
|
)
|
||||||
expected = textwrap.dedent(
|
expected = textwrap.dedent(
|
||||||
r"""
|
r"""
|
||||||
FROM python:3.11.0-slim-bullseye
|
FROM python:3.14-slim-trixie
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
@ -309,7 +309,7 @@ def test_publish_cloudrun_apt_get_install(
|
||||||
)
|
)
|
||||||
expected = textwrap.dedent(
|
expected = textwrap.dedent(
|
||||||
r"""
|
r"""
|
||||||
FROM python:3.11.0-slim-bullseye
|
FROM python:3.14-slim-trixie
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue