Compare commits

...

10 commits

Author SHA1 Message Date
Simon Willison
ccf80604e9 Documentation for --plugin-secret option 2019-07-07 18:55:55 -07:00
Simon Willison
c2df6f3482 --plugin-secret support for Cloud Run 2019-07-07 18:55:42 -07:00
Simon Willison
ba69d520ae Unit test for heroku --plugin-secret 2019-07-07 18:27:43 -07:00
Simon Willison
741760d05c Unit tests for now --plugin-secret option
Also added new --show-files option to  - useful for debugging.
2019-07-07 16:57:04 -07:00
Simon Willison
18bf0675f5 Fix nav display on 500 page, closes #545 2019-07-07 13:25:38 -07:00
Simon Willison
08dd0d96ed white-space: pre-wrap for table SQL, closes #505 2019-07-07 13:21:50 -07:00
Simon Willison
c92349e7ee Applied Black 2019-07-07 13:18:15 -07:00
Simon Willison
4c2f4dbd60 --plugin-secret support for Heroku 2019-07-07 13:06:36 -07:00
Simon Willison
4e5f6b4d11 Fix escaping of ENV 2019-07-06 15:35:45 -07:00
Simon Willison
c207453e03 --plugin-secret option, refs #543 2019-07-06 15:17:14 -07:00
16 changed files with 394 additions and 89 deletions

View file

@ -1,6 +1,7 @@
from datasette import hookimpl from datasette import hookimpl
import click import click
import json import json
import os
from subprocess import check_call, check_output from subprocess import check_call, check_output
from .common import ( from .common import (
@ -24,6 +25,11 @@ def publish_subcommand(publish):
"--service", default="", help="Cloud Run service to deploy (or over-write)" "--service", default="", help="Cloud Run service to deploy (or over-write)"
) )
@click.option("--spatialite", is_flag=True, help="Enable SpatialLite extension") @click.option("--spatialite", is_flag=True, help="Enable SpatialLite extension")
@click.option(
"--show-files",
is_flag=True,
help="Output the generated Dockerfile and metadata.json",
)
def cloudrun( def cloudrun(
files, files,
metadata, metadata,
@ -33,6 +39,7 @@ def publish_subcommand(publish):
plugins_dir, plugins_dir,
static, static,
install, install,
plugin_secret,
version_note, version_note,
title, title,
license, license,
@ -44,6 +51,7 @@ def publish_subcommand(publish):
name, name,
service, service,
spatialite, spatialite,
show_files,
): ):
fail_if_publish_binary_not_installed( fail_if_publish_binary_not_installed(
"gcloud", "Google Cloud", "https://cloud.google.com/sdk/" "gcloud", "Google Cloud", "https://cloud.google.com/sdk/"
@ -52,6 +60,30 @@ 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()
extra_metadata = {
"title": title,
"license": license,
"license_url": license_url,
"source": source,
"source_url": source_url,
"about": about,
"about_url": about_url,
}
environment_variables = {}
if plugin_secret:
extra_metadata["plugins"] = {}
for plugin_name, plugin_setting, setting_value in plugin_secret:
environment_variable = (
"{}_{}".format(plugin_name, plugin_setting)
.upper()
.replace("-", "_")
)
environment_variables[environment_variable] = setting_value
extra_metadata["plugins"].setdefault(plugin_name, {})[
plugin_setting
] = {"$env": environment_variable}
with temporary_docker_directory( with temporary_docker_directory(
files, files,
name, name,
@ -64,16 +96,17 @@ def publish_subcommand(publish):
install, install,
spatialite, spatialite,
version_note, version_note,
{ extra_metadata,
"title": title, environment_variables,
"license": license,
"license_url": license_url,
"source": source,
"source_url": source_url,
"about": about,
"about_url": about_url,
},
): ):
if show_files:
if os.path.exists("metadata.json"):
print("=== metadata.json ===\n")
print(open("metadata.json").read())
print("\n==== Dockerfile ====\n")
print(open("Dockerfile").read())
print("\n====================\n")
image_id = "gcr.io/{project}/{name}".format(project=project, name=name) image_id = "gcr.io/{project}/{name}".format(project=project, name=name)
check_call("gcloud builds submit --tag {}".format(image_id), shell=True) check_call("gcloud builds submit --tag {}".format(image_id), shell=True)
check_call( check_call(

View file

@ -41,6 +41,14 @@ def add_common_publish_arguments_and_options(subcommand):
help="Additional packages (e.g. plugins) to install", help="Additional packages (e.g. plugins) to install",
multiple=True, multiple=True,
), ),
click.option(
"--plugin-secret",
nargs=3,
type=(str, str, str),
callback=validate_plugin_secret,
multiple=True,
help="Secrets to pass to plugins, e.g. --plugin-secret datasette-auth-github client_id xxx",
),
click.option( click.option(
"--version-note", help="Additional note to show on /-/versions" "--version-note", help="Additional note to show on /-/versions"
), ),
@ -76,3 +84,10 @@ def fail_if_publish_binary_not_installed(binary, publish_target, install_link):
err=True, err=True,
) )
sys.exit(1) sys.exit(1)
def validate_plugin_secret(ctx, param, value):
for plugin_name, plugin_setting, setting_value in value:
if "'" in setting_value:
raise click.BadParameter("--plugin-secret cannot contain single quotes")
return value

View file

@ -33,6 +33,7 @@ def publish_subcommand(publish):
plugins_dir, plugins_dir,
static, static,
install, install,
plugin_secret,
version_note, version_note,
title, title,
license, license,
@ -61,6 +62,30 @@ def publish_subcommand(publish):
) )
call(["heroku", "plugins:install", "heroku-builds"]) call(["heroku", "plugins:install", "heroku-builds"])
extra_metadata = {
"title": title,
"license": license,
"license_url": license_url,
"source": source,
"source_url": source_url,
"about": about,
"about_url": about_url,
}
environment_variables = {}
if plugin_secret:
extra_metadata["plugins"] = {}
for plugin_name, plugin_setting, setting_value in plugin_secret:
environment_variable = (
"{}_{}".format(plugin_name, plugin_setting)
.upper()
.replace("-", "_")
)
environment_variables[environment_variable] = setting_value
extra_metadata["plugins"].setdefault(plugin_name, {})[
plugin_setting
] = {"$env": environment_variable}
with temporary_heroku_directory( with temporary_heroku_directory(
files, files,
name, name,
@ -72,15 +97,7 @@ def publish_subcommand(publish):
static, static,
install, install,
version_note, version_note,
{ extra_metadata,
"title": title,
"license": license,
"license_url": license_url,
"source": source,
"source_url": source_url,
"about": about,
"about_url": about_url,
},
): ):
app_name = None app_name = None
if name: if name:
@ -104,6 +121,11 @@ def publish_subcommand(publish):
create_output = check_output(cmd).decode("utf8") create_output = check_output(cmd).decode("utf8")
app_name = json.loads(create_output)["name"] app_name = json.loads(create_output)["name"]
for key, value in environment_variables.items():
call(
["heroku", "config:set", "-a", app_name, "{}={}".format(key, value)]
)
call(["heroku", "builds:create", "-a", app_name, "--include-vcs-ignore"]) call(["heroku", "builds:create", "-a", app_name, "--include-vcs-ignore"])

View file

@ -1,6 +1,7 @@
from datasette import hookimpl from datasette import hookimpl
import click import click
import json import json
import os
from subprocess import run, PIPE from subprocess import run, PIPE
from .common import ( from .common import (
@ -24,6 +25,11 @@ def publish_subcommand(publish):
@click.option("--token", help="Auth token to use for deploy") @click.option("--token", help="Auth token to use for deploy")
@click.option("--alias", multiple=True, help="Desired alias e.g. yoursite.now.sh") @click.option("--alias", multiple=True, help="Desired alias e.g. yoursite.now.sh")
@click.option("--spatialite", is_flag=True, help="Enable SpatialLite extension") @click.option("--spatialite", is_flag=True, help="Enable SpatialLite extension")
@click.option(
"--show-files",
is_flag=True,
help="Output the generated Dockerfile and metadata.json",
)
def nowv1( def nowv1(
files, files,
metadata, metadata,
@ -33,6 +39,7 @@ def publish_subcommand(publish):
plugins_dir, plugins_dir,
static, static,
install, install,
plugin_secret,
version_note, version_note,
title, title,
license, license,
@ -46,6 +53,7 @@ def publish_subcommand(publish):
token, token,
alias, alias,
spatialite, spatialite,
show_files,
): ):
fail_if_publish_binary_not_installed("now", "Zeit Now", "https://zeit.co/now") fail_if_publish_binary_not_installed("now", "Zeit Now", "https://zeit.co/now")
if extra_options: if extra_options:
@ -54,6 +62,30 @@ def publish_subcommand(publish):
extra_options = "" extra_options = ""
extra_options += "--config force_https_urls:on" extra_options += "--config force_https_urls:on"
extra_metadata = {
"title": title,
"license": license,
"license_url": license_url,
"source": source,
"source_url": source_url,
"about": about,
"about_url": about_url,
}
environment_variables = {}
if plugin_secret:
extra_metadata["plugins"] = {}
for plugin_name, plugin_setting, setting_value in plugin_secret:
environment_variable = (
"{}_{}".format(plugin_name, plugin_setting)
.upper()
.replace("-", "_")
)
environment_variables[environment_variable] = setting_value
extra_metadata["plugins"].setdefault(plugin_name, {})[
plugin_setting
] = {"$env": environment_variable}
with temporary_docker_directory( with temporary_docker_directory(
files, files,
name, name,
@ -66,15 +98,8 @@ def publish_subcommand(publish):
install, install,
spatialite, spatialite,
version_note, version_note,
{ extra_metadata,
"title": title, environment_variables,
"license": license,
"license_url": license_url,
"source": source,
"source_url": source_url,
"about": about,
"about_url": about_url,
},
): ):
now_json = {"version": 1} now_json = {"version": 1}
open("now.json", "w").write(json.dumps(now_json, indent=4)) open("now.json", "w").write(json.dumps(now_json, indent=4))
@ -88,6 +113,13 @@ def publish_subcommand(publish):
else: else:
done = run("now", stdout=PIPE) done = run("now", stdout=PIPE)
deployment_url = done.stdout deployment_url = done.stdout
if show_files:
if os.path.exists("metadata.json"):
print("=== metadata.json ===\n")
print(open("metadata.json").read())
print("\n==== Dockerfile ====\n")
print(open("Dockerfile").read())
print("\n====================\n")
if alias: if alias:
# I couldn't get --target=production working, so I call # I couldn't get --target=production working, so I call
# 'now alias' with arguments directly instead - but that # 'now alias' with arguments directly instead - but that

View file

@ -312,3 +312,7 @@ a.not-underlined {
font-style: normal; font-style: normal;
font-size: 0.8em; font-size: 0.8em;
} }
pre.wrapped-sql {
white-space: pre-wrap;
}

View file

@ -2,8 +2,14 @@
{% block title %}{% if title %}{{ title }}{% else %}Error {{ status }}{% endif %}{% endblock %} {% block title %}{% if title %}{{ title }}{% else %}Error {{ status }}{% endif %}{% endblock %}
{% block nav %}
<p class="crumbs">
<a href="/">home</a>
</p>
{{ super() }}
{% endblock %}
{% block content %} {% block content %}
<div class="hd"><a href="/">home</a></div>
<h1>{% if title %}{{ title }}{% else %}Error {{ status }}{% endif %}</h1> <h1>{% if title %}{{ title }}{% else %}Error {{ status }}{% endif %}</h1>

View file

@ -184,11 +184,11 @@
{% endif %} {% endif %}
{% if table_definition %} {% if table_definition %}
<pre>{{ table_definition }}</pre> <pre class="wrapped-sql">{{ table_definition }}</pre>
{% endif %} {% endif %}
{% if view_definition %} {% if view_definition %}
<pre>{{ view_definition }}</pre> <pre class="wrapped-sql">{{ view_definition }}</pre>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -272,6 +272,7 @@ def make_dockerfile(
install, install,
spatialite, spatialite,
version_note, version_note,
environment_variables=None,
): ):
cmd = ["datasette", "serve", "--host", "0.0.0.0"] cmd = ["datasette", "serve", "--host", "0.0.0.0"]
for filename in files: for filename in files:
@ -307,11 +308,18 @@ FROM python:3.6
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
{spatialite_extras} {spatialite_extras}
{environment_variables}
RUN pip install -U {install_from} RUN pip install -U {install_from}
RUN datasette inspect {files} --inspect-file inspect-data.json RUN datasette inspect {files} --inspect-file inspect-data.json
ENV PORT 8001 ENV PORT 8001
EXPOSE 8001 EXPOSE 8001
CMD {cmd}""".format( CMD {cmd}""".format(
environment_variables="\n".join(
[
"ENV {} '{}'".format(key, value)
for key, value in (environment_variables or {}).items()
]
),
files=" ".join(files), files=" ".join(files),
cmd=cmd, cmd=cmd,
install_from=" ".join(install), install_from=" ".join(install),
@ -333,6 +341,7 @@ def temporary_docker_directory(
spatialite, spatialite,
version_note, version_note,
extra_metadata=None, extra_metadata=None,
environment_variables=None,
): ):
extra_metadata = extra_metadata or {} extra_metadata = extra_metadata or {}
tmp = tempfile.TemporaryDirectory() tmp = tempfile.TemporaryDirectory()
@ -361,6 +370,7 @@ def temporary_docker_directory(
install, install,
spatialite, spatialite,
version_note, version_note,
environment_variables,
) )
os.chdir(datasette_dir) os.chdir(datasette_dir)
if metadata_content: if metadata_content:

View file

@ -3,22 +3,26 @@ $ datasette publish cloudrun --help
Usage: datasette publish cloudrun [OPTIONS] [FILES]... Usage: datasette publish cloudrun [OPTIONS] [FILES]...
Options: Options:
-m, --metadata FILENAME Path to JSON file containing metadata to publish -m, --metadata FILENAME Path to JSON file containing metadata to publish
--extra-options TEXT Extra options to pass to datasette serve --extra-options TEXT Extra options to pass to datasette serve
--branch TEXT Install datasette from a GitHub branch e.g. master --branch TEXT Install datasette from a GitHub branch e.g. master
--template-dir DIRECTORY Path to directory containing custom templates --template-dir DIRECTORY Path to directory containing custom templates
--plugins-dir DIRECTORY Path to directory containing custom plugins --plugins-dir DIRECTORY Path to directory containing custom plugins
--static STATIC MOUNT mountpoint:path-to-directory for serving static files --static STATIC MOUNT mountpoint:path-to-directory for serving static files
--install TEXT Additional packages (e.g. plugins) to install --install TEXT Additional packages (e.g. plugins) to install
--version-note TEXT Additional note to show on /-/versions --plugin-secret <TEXT TEXT TEXT>...
--title TEXT Title for metadata Secrets to pass to plugins, e.g. --plugin-secret
--license TEXT License label for metadata datasette-auth-github client_id xxx
--license_url TEXT License URL for metadata --version-note TEXT Additional note to show on /-/versions
--source TEXT Source label for metadata --title TEXT Title for metadata
--source_url TEXT Source URL for metadata --license TEXT License label for metadata
--about TEXT About label for metadata --license_url TEXT License URL for metadata
--about_url TEXT About URL for metadata --source TEXT Source label for metadata
-n, --name TEXT Application name to use when building --source_url TEXT Source URL for metadata
--service TEXT Cloud Run service to deploy (or over-write) --about TEXT About label for metadata
--spatialite Enable SpatialLite extension --about_url TEXT About URL for metadata
--help Show this message and exit. -n, --name TEXT Application name to use when building
--service TEXT Cloud Run service to deploy (or over-write)
--spatialite Enable SpatialLite extension
--show-files Output the generated Dockerfile and metadata.json
--help Show this message and exit.

View file

@ -3,20 +3,23 @@ $ datasette publish heroku --help
Usage: datasette publish heroku [OPTIONS] [FILES]... Usage: datasette publish heroku [OPTIONS] [FILES]...
Options: Options:
-m, --metadata FILENAME Path to JSON file containing metadata to publish -m, --metadata FILENAME Path to JSON file containing metadata to publish
--extra-options TEXT Extra options to pass to datasette serve --extra-options TEXT Extra options to pass to datasette serve
--branch TEXT Install datasette from a GitHub branch e.g. master --branch TEXT Install datasette from a GitHub branch e.g. master
--template-dir DIRECTORY Path to directory containing custom templates --template-dir DIRECTORY Path to directory containing custom templates
--plugins-dir DIRECTORY Path to directory containing custom plugins --plugins-dir DIRECTORY Path to directory containing custom plugins
--static STATIC MOUNT mountpoint:path-to-directory for serving static files --static STATIC MOUNT mountpoint:path-to-directory for serving static files
--install TEXT Additional packages (e.g. plugins) to install --install TEXT Additional packages (e.g. plugins) to install
--version-note TEXT Additional note to show on /-/versions --plugin-secret <TEXT TEXT TEXT>...
--title TEXT Title for metadata Secrets to pass to plugins, e.g. --plugin-secret
--license TEXT License label for metadata datasette-auth-github client_id xxx
--license_url TEXT License URL for metadata --version-note TEXT Additional note to show on /-/versions
--source TEXT Source label for metadata --title TEXT Title for metadata
--source_url TEXT Source URL for metadata --license TEXT License label for metadata
--about TEXT About label for metadata --license_url TEXT License URL for metadata
--about_url TEXT About URL for metadata --source TEXT Source label for metadata
-n, --name TEXT Application name to use when deploying --source_url TEXT Source URL for metadata
--help Show this message and exit. --about TEXT About label for metadata
--about_url TEXT About URL for metadata
-n, --name TEXT Application name to use when deploying
--help Show this message and exit.

View file

@ -3,24 +3,28 @@ $ datasette publish nowv1 --help
Usage: datasette publish nowv1 [OPTIONS] [FILES]... Usage: datasette publish nowv1 [OPTIONS] [FILES]...
Options: Options:
-m, --metadata FILENAME Path to JSON file containing metadata to publish -m, --metadata FILENAME Path to JSON file containing metadata to publish
--extra-options TEXT Extra options to pass to datasette serve --extra-options TEXT Extra options to pass to datasette serve
--branch TEXT Install datasette from a GitHub branch e.g. master --branch TEXT Install datasette from a GitHub branch e.g. master
--template-dir DIRECTORY Path to directory containing custom templates --template-dir DIRECTORY Path to directory containing custom templates
--plugins-dir DIRECTORY Path to directory containing custom plugins --plugins-dir DIRECTORY Path to directory containing custom plugins
--static STATIC MOUNT mountpoint:path-to-directory for serving static files --static STATIC MOUNT mountpoint:path-to-directory for serving static files
--install TEXT Additional packages (e.g. plugins) to install --install TEXT Additional packages (e.g. plugins) to install
--version-note TEXT Additional note to show on /-/versions --plugin-secret <TEXT TEXT TEXT>...
--title TEXT Title for metadata Secrets to pass to plugins, e.g. --plugin-secret
--license TEXT License label for metadata datasette-auth-github client_id xxx
--license_url TEXT License URL for metadata --version-note TEXT Additional note to show on /-/versions
--source TEXT Source label for metadata --title TEXT Title for metadata
--source_url TEXT Source URL for metadata --license TEXT License label for metadata
--about TEXT About label for metadata --license_url TEXT License URL for metadata
--about_url TEXT About URL for metadata --source TEXT Source label for metadata
-n, --name TEXT Application name to use when deploying --source_url TEXT Source URL for metadata
--force Pass --force option to now --about TEXT About label for metadata
--token TEXT Auth token to use for deploy --about_url TEXT About URL for metadata
--alias TEXT Desired alias e.g. yoursite.now.sh -n, --name TEXT Application name to use when deploying
--spatialite Enable SpatialLite extension --force Pass --force option to now
--help Show this message and exit. --token TEXT Auth token to use for deploy
--alias TEXT Desired alias e.g. yoursite.now.sh
--spatialite Enable SpatialLite extension
--show-files Output the generated Dockerfile and metadata.json
--help Show this message and exit.

View file

@ -219,6 +219,8 @@ Here is an example of some plugin configuration for a specific table::
This tells the ``datasette-cluster-map`` column which latitude and longitude columns should be used for a table called ``Street_Tree_List`` inside a database file called ``sf-trees.db``. This tells the ``datasette-cluster-map`` column which latitude and longitude columns should be used for a table called ``Street_Tree_List`` inside a database file called ``sf-trees.db``.
.. _plugins_configuration_secret:
Secret configuration values Secret configuration values
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -236,7 +238,6 @@ Any values embedded in ``metadata.json`` will be visible to anyone who views the
} }
} }
**As values in separate files**. Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this:: **As values in separate files**. Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this::
{ {
@ -249,6 +250,14 @@ Any values embedded in ``metadata.json`` will be visible to anyone who views the
} }
} }
If you are publishing your data using the :ref:`datasette publish <cli_publish>` family of commands, you can use the ``--plugin-secret`` option to set these secrets at publish time. For example, using Heroku you might run the following command::
$ datasette publish heroku my_database.db \
--name my-heroku-app-demo \
--install=datasette-auth-github \
--plugin-secret datasette-auth-github client_id your_client_id \
--plugin-secret datasette-auth-github client_secret your_client_secret
Writing plugins that accept configuration Writing plugins that accept configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -6,6 +6,8 @@
Datasette includes tools for publishing and deploying your data to the internet. The ``datasette publish`` command will deploy a new Datasette instance containing your databases directly to a Heroku, Google Cloud or Zeit Now hosting account. You can also use ``datasette package`` to create a Docker image that bundles your databases together with the datasette application that is used to serve them. Datasette includes tools for publishing and deploying your data to the internet. The ``datasette publish`` command will deploy a new Datasette instance containing your databases directly to a Heroku, Google Cloud or Zeit Now hosting account. You can also use ``datasette package`` to create a Docker image that bundles your databases together with the datasette application that is used to serve them.
.. _cli_publish:
datasette publish datasette publish
================= =================
@ -99,6 +101,13 @@ You can also specify plugins you would like to install. For example, if you want
datasette publish nowv1 mydatabase.db --install=datasette-vega datasette publish nowv1 mydatabase.db --install=datasette-vega
If a plugin has any :ref:`plugins_configuration_secret` you can use the ``--plugin-secret`` option to set those secrets at publish time. For example, using Heroku with `datasette-auth-github <https://github.com/simonw/datasette-auth-github>`__ you might run the following command::
$ datasette publish heroku my_database.db \
--name my-heroku-app-demo \
--install=datasette-auth-github \
--plugin-secret datasette-auth-github client_id your_client_id \
--plugin-secret datasette-auth-github client_secret your_client_secret
datasette package datasette package
================= =================

View file

@ -1,6 +1,7 @@
from click.testing import CliRunner from click.testing import CliRunner
from datasette import cli from datasette import cli
from unittest import mock from unittest import mock
import json
@mock.patch("shutil.which") @mock.patch("shutil.which")
@ -46,3 +47,56 @@ def test_publish_cloudrun(mock_call, mock_output, mock_which):
), ),
] ]
) )
@mock.patch("shutil.which")
@mock.patch("datasette.publish.cloudrun.check_output")
@mock.patch("datasette.publish.cloudrun.check_call")
def test_publish_cloudrun_plugin_secrets(mock_call, mock_output, mock_which):
mock_which.return_value = True
mock_output.return_value = "myproject"
runner = CliRunner()
with runner.isolated_filesystem():
open("test.db", "w").write("data")
result = runner.invoke(
cli.cli,
[
"publish",
"cloudrun",
"test.db",
"--plugin-secret",
"datasette-auth-github",
"client_id",
"x-client-id",
"--show-files",
],
)
dockerfile = (
result.output.split("==== Dockerfile ====\n")[1]
.split("\n====================\n")[0]
.strip()
)
expected = """FROM python:3.6
COPY . /app
WORKDIR /app
ENV DATASETTE_AUTH_GITHUB_CLIENT_ID 'x-client-id'
RUN pip install -U datasette
RUN datasette inspect test.db --inspect-file inspect-data.json
ENV PORT 8001
EXPOSE 8001
CMD datasette serve --host 0.0.0.0 -i test.db --cors --inspect-file inspect-data.json --metadata metadata.json --port $PORT""".strip()
assert expected == dockerfile
metadata = (
result.output.split("=== metadata.json ===\n")[1]
.split("\n==== Dockerfile ====\n")[0]
.strip()
)
assert {
"plugins": {
"datasette-auth-github": {
"client_id": {"$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID"}
}
}
} == json.loads(metadata)

View file

@ -46,7 +46,7 @@ def test_publish_heroku_invalid_database(mock_which):
@mock.patch("datasette.publish.heroku.check_output") @mock.patch("datasette.publish.heroku.check_output")
@mock.patch("datasette.publish.heroku.call") @mock.patch("datasette.publish.heroku.call")
def test_publish_heroku(mock_call, mock_check_output, mock_which): def test_publish_heroku(mock_call, mock_check_output, mock_which):
mock_which.return_varue = True mock_which.return_value = True
mock_check_output.side_effect = lambda s: { mock_check_output.side_effect = lambda s: {
"['heroku', 'plugins']": b"heroku-builds", "['heroku', 'plugins']": b"heroku-builds",
"['heroku', 'apps:list', '--json']": b"[]", "['heroku', 'apps:list', '--json']": b"[]",
@ -60,3 +60,47 @@ def test_publish_heroku(mock_call, mock_check_output, mock_which):
mock_call.assert_called_once_with( mock_call.assert_called_once_with(
["heroku", "builds:create", "-a", "f", "--include-vcs-ignore"] ["heroku", "builds:create", "-a", "f", "--include-vcs-ignore"]
) )
@mock.patch("shutil.which")
@mock.patch("datasette.publish.heroku.check_output")
@mock.patch("datasette.publish.heroku.call")
def test_publish_heroku_plugin_secrets(mock_call, mock_check_output, mock_which):
mock_which.return_value = True
mock_check_output.side_effect = lambda s: {
"['heroku', 'plugins']": b"heroku-builds",
"['heroku', 'apps:list', '--json']": b"[]",
"['heroku', 'apps:create', 'datasette', '--json']": b'{"name": "f"}',
}[repr(s)]
runner = CliRunner()
with runner.isolated_filesystem():
open("test.db", "w").write("data")
result = runner.invoke(
cli.cli,
[
"publish",
"heroku",
"test.db",
"--plugin-secret",
"datasette-auth-github",
"client_id",
"x-client-id",
],
)
assert 0 == result.exit_code, result.output
mock_call.assert_has_calls(
[
mock.call(
[
"heroku",
"config:set",
"-a",
"f",
"DATASETTE_AUTH_GITHUB_CLIENT_ID=x-client-id",
]
),
mock.call(
["heroku", "builds:create", "-a", "f", "--include-vcs-ignore"]
),
]
)

View file

@ -1,6 +1,7 @@
from click.testing import CliRunner from click.testing import CliRunner
from datasette import cli from datasette import cli
from unittest import mock from unittest import mock
import json
import subprocess import subprocess
@ -105,3 +106,58 @@ def test_publish_now_multiple_aliases(mock_run, mock_which):
), ),
] ]
) )
@mock.patch("shutil.which")
@mock.patch("datasette.publish.now.run")
def test_publish_now_plugin_secrets(mock_run, mock_which):
mock_which.return_value = True
mock_run.return_value = mock.Mock(0)
mock_run.return_value.stdout = b"https://demo.example.com/"
runner = CliRunner()
with runner.isolated_filesystem():
open("test.db", "w").write("data")
result = runner.invoke(
cli.cli,
[
"publish",
"now",
"test.db",
"--token",
"XXX",
"--plugin-secret",
"datasette-auth-github",
"client_id",
"x-client-id",
"--show-files",
],
)
dockerfile = (
result.output.split("==== Dockerfile ====\n")[1]
.split("\n====================\n")[0]
.strip()
)
expected = """FROM python:3.6
COPY . /app
WORKDIR /app
ENV DATASETTE_AUTH_GITHUB_CLIENT_ID 'x-client-id'
RUN pip install -U datasette
RUN datasette inspect test.db --inspect-file inspect-data.json
ENV PORT 8001
EXPOSE 8001
CMD datasette serve --host 0.0.0.0 -i test.db --cors --inspect-file inspect-data.json --metadata metadata.json --config force_https_urls:on --port $PORT""".strip()
assert expected == dockerfile
metadata = (
result.output.split("=== metadata.json ===\n")[1]
.split("\n==== Dockerfile ====\n")[0]
.strip()
)
assert {
"plugins": {
"datasette-auth-github": {
"client_id": {"$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID"}
}
}
} == json.loads(metadata)