2019-05-16 04:32:23 +00:00
|
|
|
from contextlib import contextmanager
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
from datasette import hookimpl
|
|
|
|
import click
|
|
|
|
import json
|
2019-05-16 04:32:23 +00:00
|
|
|
import os
|
2022-11-19 00:34:33 +00:00
|
|
|
import pathlib
|
2019-05-16 04:32:23 +00:00
|
|
|
import shlex
|
2022-11-19 00:34:33 +00:00
|
|
|
import shutil
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
from subprocess import call, check_output
|
2019-05-16 04:32:23 +00:00
|
|
|
import tempfile
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
|
|
|
|
from .common import (
|
|
|
|
add_common_publish_arguments_and_options,
|
|
|
|
fail_if_publish_binary_not_installed,
|
|
|
|
)
|
2020-04-02 19:30:53 +00:00
|
|
|
from datasette.utils import link_or_copy, link_or_copy_directory, parse_metadata
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
@hookimpl
|
|
|
|
def publish_subcommand(publish):
|
|
|
|
@publish.command()
|
|
|
|
@add_common_publish_arguments_and_options
|
|
|
|
@click.option(
|
|
|
|
"-n",
|
|
|
|
"--name",
|
|
|
|
default="datasette",
|
|
|
|
help="Application name to use when deploying",
|
|
|
|
)
|
2020-10-08 23:30:46 +00:00
|
|
|
@click.option(
|
|
|
|
"--tar",
|
|
|
|
help="--tar option to pass to Heroku, e.g. --tar=/usr/local/bin/gtar",
|
|
|
|
)
|
2022-11-19 00:34:33 +00:00
|
|
|
@click.option(
|
|
|
|
"--generate-dir",
|
|
|
|
type=click.Path(dir_okay=True, file_okay=False),
|
|
|
|
help="Output generated application files and stop without deploying",
|
|
|
|
)
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
def heroku(
|
|
|
|
files,
|
|
|
|
metadata,
|
|
|
|
extra_options,
|
|
|
|
branch,
|
|
|
|
template_dir,
|
|
|
|
plugins_dir,
|
|
|
|
static,
|
|
|
|
install,
|
2019-07-08 02:06:31 +00:00
|
|
|
plugin_secret,
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
version_note,
|
2020-06-11 16:02:03 +00:00
|
|
|
secret,
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
title,
|
|
|
|
license,
|
|
|
|
license_url,
|
|
|
|
source,
|
|
|
|
source_url,
|
2019-03-10 21:37:11 +00:00
|
|
|
about,
|
|
|
|
about_url,
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
name,
|
2020-10-08 23:30:46 +00:00
|
|
|
tar,
|
2022-11-19 00:34:33 +00:00
|
|
|
generate_dir,
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
):
|
2022-01-14 00:12:54 +00:00
|
|
|
"Publish databases to Datasette running on Heroku"
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
fail_if_publish_binary_not_installed(
|
|
|
|
"heroku", "Heroku", "https://cli.heroku.com"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Check for heroku-builds plugin
|
|
|
|
plugins = [
|
|
|
|
line.split()[0] for line in check_output(["heroku", "plugins"]).splitlines()
|
|
|
|
]
|
|
|
|
if b"heroku-builds" not in plugins:
|
|
|
|
click.echo(
|
|
|
|
"Publishing to Heroku requires the heroku-builds plugin to be installed."
|
|
|
|
)
|
|
|
|
click.confirm(
|
|
|
|
"Install it? (this will run `heroku plugins:install heroku-builds`)",
|
|
|
|
abort=True,
|
|
|
|
)
|
|
|
|
call(["heroku", "plugins:install", "heroku-builds"])
|
|
|
|
|
2019-07-08 02:06:31 +00:00
|
|
|
extra_metadata = {
|
|
|
|
"title": title,
|
|
|
|
"license": license,
|
|
|
|
"license_url": license_url,
|
|
|
|
"source": source,
|
|
|
|
"source_url": source_url,
|
|
|
|
"about": about,
|
|
|
|
"about_url": about_url,
|
|
|
|
}
|
|
|
|
|
2020-10-08 23:16:55 +00:00
|
|
|
environment_variables = {}
|
2019-07-08 02:06:31 +00:00
|
|
|
if plugin_secret:
|
|
|
|
extra_metadata["plugins"] = {}
|
|
|
|
for plugin_name, plugin_setting, setting_value in plugin_secret:
|
|
|
|
environment_variable = (
|
2020-11-15 23:24:22 +00:00
|
|
|
f"{plugin_name}_{plugin_setting}".upper().replace("-", "_")
|
2019-07-08 02:06:31 +00:00
|
|
|
)
|
|
|
|
environment_variables[environment_variable] = setting_value
|
|
|
|
extra_metadata["plugins"].setdefault(plugin_name, {})[
|
|
|
|
plugin_setting
|
|
|
|
] = {"$env": environment_variable}
|
|
|
|
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
with temporary_heroku_directory(
|
|
|
|
files,
|
|
|
|
name,
|
|
|
|
metadata,
|
|
|
|
extra_options,
|
|
|
|
branch,
|
|
|
|
template_dir,
|
|
|
|
plugins_dir,
|
|
|
|
static,
|
|
|
|
install,
|
|
|
|
version_note,
|
2020-06-11 16:02:03 +00:00
|
|
|
secret,
|
2019-07-08 02:06:31 +00:00
|
|
|
extra_metadata,
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
):
|
2022-11-19 00:34:33 +00:00
|
|
|
if generate_dir:
|
|
|
|
# Recursively copy files from current working directory to it
|
|
|
|
if pathlib.Path(generate_dir).exists():
|
|
|
|
raise click.ClickException("Directory already exists")
|
|
|
|
shutil.copytree(".", generate_dir)
|
|
|
|
click.echo(
|
|
|
|
f"Generated files written to {generate_dir}, stopping without deploying",
|
|
|
|
err=True,
|
|
|
|
)
|
|
|
|
return
|
publish_subcommand hook + default plugins mechanism, used for publish heroku/now (#349)
This change introduces a new plugin hook, publish_subcommand, which can be
used to implement new subcommands for the "datasette publish" command family.
I've used this new hook to refactor out the "publish now" and "publish heroku"
implementations into separate modules. I've also added unit tests for these
two publishers, mocking the subprocess.call and subprocess.check_output
functions.
As part of this, I introduced a mechanism for loading default plugins. These
are defined in the new "default_plugins" list inside datasette/app.py
Closes #217 (Plugin support for datasette publish)
Closes #348 (Unit tests for "datasette publish")
Refs #14, #59, #102, #103, #146, #236, #347
2018-07-26 05:15:59 +00:00
|
|
|
app_name = None
|
|
|
|
if name:
|
|
|
|
# Check to see if this app already exists
|
|
|
|
list_output = check_output(["heroku", "apps:list", "--json"]).decode(
|
|
|
|
"utf8"
|
|
|
|
)
|
|
|
|
apps = json.loads(list_output)
|
|
|
|
|
|
|
|
for app in apps:
|
|
|
|
if app["name"] == name:
|
|
|
|
app_name = name
|
|
|
|
break
|
|
|
|
|
|
|
|
if not app_name:
|
|
|
|
# Create a new app
|
|
|
|
cmd = ["heroku", "apps:create"]
|
|
|
|
if name:
|
|
|
|
cmd.append(name)
|
|
|
|
cmd.append("--json")
|
|
|
|
create_output = check_output(cmd).decode("utf8")
|
|
|
|
app_name = json.loads(create_output)["name"]
|
|
|
|
|
2019-07-08 02:06:31 +00:00
|
|
|
for key, value in environment_variables.items():
|
2020-11-15 23:24:22 +00:00
|
|
|
call(["heroku", "config:set", "-a", app_name, f"{key}={value}"])
|
2020-10-08 23:30:46 +00:00
|
|
|
tar_option = []
|
|
|
|
if tar:
|
|
|
|
tar_option = ["--tar", tar]
|
|
|
|
call(
|
|
|
|
["heroku", "builds:create", "-a", app_name, "--include-vcs-ignore"]
|
|
|
|
+ tar_option
|
|
|
|
)
|
2019-05-16 04:32:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def temporary_heroku_directory(
|
|
|
|
files,
|
|
|
|
name,
|
|
|
|
metadata,
|
|
|
|
extra_options,
|
|
|
|
branch,
|
|
|
|
template_dir,
|
|
|
|
plugins_dir,
|
|
|
|
static,
|
|
|
|
install,
|
|
|
|
version_note,
|
2020-06-11 16:02:03 +00:00
|
|
|
secret,
|
2019-05-16 04:32:23 +00:00
|
|
|
extra_metadata=None,
|
|
|
|
):
|
|
|
|
extra_metadata = extra_metadata or {}
|
|
|
|
tmp = tempfile.TemporaryDirectory()
|
|
|
|
saved_cwd = os.getcwd()
|
|
|
|
|
|
|
|
file_paths = [os.path.join(saved_cwd, file_path) for file_path in files]
|
|
|
|
file_names = [os.path.split(f)[-1] for f in files]
|
|
|
|
|
|
|
|
if metadata:
|
2020-04-02 19:30:53 +00:00
|
|
|
metadata_content = parse_metadata(metadata.read())
|
2019-05-16 04:32:23 +00:00
|
|
|
else:
|
|
|
|
metadata_content = {}
|
|
|
|
for key, value in extra_metadata.items():
|
|
|
|
if value:
|
|
|
|
metadata_content[key] = value
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.chdir(tmp.name)
|
|
|
|
|
|
|
|
if metadata_content:
|
2021-03-11 16:15:49 +00:00
|
|
|
with open("metadata.json", "w") as fp:
|
|
|
|
fp.write(json.dumps(metadata_content, indent=2))
|
2019-05-16 04:32:23 +00:00
|
|
|
|
2021-03-11 16:15:49 +00:00
|
|
|
with open("runtime.txt", "w") as fp:
|
2022-11-19 00:44:46 +00:00
|
|
|
fp.write("python-3.11.0")
|
2019-05-16 04:32:23 +00:00
|
|
|
|
|
|
|
if branch:
|
|
|
|
install = [
|
2020-11-15 23:24:22 +00:00
|
|
|
f"https://github.com/simonw/datasette/archive/{branch}.zip"
|
2019-05-16 04:32:23 +00:00
|
|
|
] + list(install)
|
|
|
|
else:
|
|
|
|
install = ["datasette"] + list(install)
|
|
|
|
|
2021-03-11 16:15:49 +00:00
|
|
|
with open("requirements.txt", "w") as fp:
|
|
|
|
fp.write("\n".join(install))
|
2019-05-16 04:32:23 +00:00
|
|
|
os.mkdir("bin")
|
2021-03-11 16:15:49 +00:00
|
|
|
with open("bin/post_compile", "w") as fp:
|
|
|
|
fp.write("datasette inspect --inspect-file inspect-data.json")
|
2019-05-16 04:32:23 +00:00
|
|
|
|
|
|
|
extras = []
|
|
|
|
if template_dir:
|
|
|
|
link_or_copy_directory(
|
|
|
|
os.path.join(saved_cwd, template_dir),
|
|
|
|
os.path.join(tmp.name, "templates"),
|
|
|
|
)
|
|
|
|
extras.extend(["--template-dir", "templates/"])
|
|
|
|
if plugins_dir:
|
|
|
|
link_or_copy_directory(
|
|
|
|
os.path.join(saved_cwd, plugins_dir), os.path.join(tmp.name, "plugins")
|
|
|
|
)
|
|
|
|
extras.extend(["--plugins-dir", "plugins/"])
|
|
|
|
if version_note:
|
|
|
|
extras.extend(["--version-note", version_note])
|
|
|
|
if metadata_content:
|
|
|
|
extras.extend(["--metadata", "metadata.json"])
|
|
|
|
if extra_options:
|
|
|
|
extras.extend(extra_options.split())
|
|
|
|
for mount_point, path in static:
|
|
|
|
link_or_copy_directory(
|
|
|
|
os.path.join(saved_cwd, path), os.path.join(tmp.name, mount_point)
|
|
|
|
)
|
2020-11-15 23:24:22 +00:00
|
|
|
extras.extend(["--static", f"{mount_point}:{mount_point}"])
|
2019-05-16 04:32:23 +00:00
|
|
|
|
2019-05-19 22:53:34 +00:00
|
|
|
quoted_files = " ".join(
|
|
|
|
["-i {}".format(shlex.quote(file_name)) for file_name in file_names]
|
|
|
|
)
|
2019-05-16 04:32:23 +00:00
|
|
|
procfile_cmd = "web: datasette serve --host 0.0.0.0 {quoted_files} --cors --port $PORT --inspect-file inspect-data.json {extras}".format(
|
|
|
|
quoted_files=quoted_files, extras=" ".join(extras)
|
|
|
|
)
|
2021-03-11 16:15:49 +00:00
|
|
|
with open("Procfile", "w") as fp:
|
|
|
|
fp.write(procfile_cmd)
|
2019-05-16 04:32:23 +00:00
|
|
|
|
|
|
|
for path, filename in zip(file_paths, file_names):
|
|
|
|
link_or_copy(path, os.path.join(tmp.name, filename))
|
|
|
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
finally:
|
|
|
|
tmp.cleanup()
|
|
|
|
os.chdir(saved_cwd)
|