523 lines
18 KiB
Python
Executable File
523 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
from __future__ import print_function
|
|
import os
|
|
import re
|
|
|
|
|
|
def compile(service, version, variable, alias, image, scheme, ports, sponsors, unimplemented, dokku_version):
|
|
prefix = "\n\n".join([
|
|
header(service),
|
|
description(service, image, version),
|
|
])
|
|
|
|
if len(sponsors) > 0:
|
|
prefix += "\n\n"
|
|
prefix += sponsors_section(service, sponsors)
|
|
|
|
return (
|
|
"\n\n".join(
|
|
[
|
|
prefix,
|
|
requirements_section(dokku_version),
|
|
installation_section(service, dokku_version),
|
|
commands_section(service, variable, alias, image, scheme, ports, unimplemented),
|
|
usage_section(service, variable, alias, image, scheme, ports, unimplemented),
|
|
]
|
|
)
|
|
.replace("\n\n\n\n\n", "\n")
|
|
.replace("\n\n\n\n", "\n")
|
|
.replace("\n\n\n", "\n\n")
|
|
)
|
|
|
|
|
|
def header(service):
|
|
return " ".join(
|
|
[
|
|
f"# dokku {service}",
|
|
f'[](https://github.com/dokku/dokku-{service}/actions/workflows/ci.yml?query=branch%3Amaster)',
|
|
f'[](https://webchat.libera.chat/?channels=dokku)',
|
|
]
|
|
)
|
|
|
|
|
|
def description(service, full_image, version):
|
|
base = "_"
|
|
image = full_image
|
|
if "/" in full_image:
|
|
base = "r/" + full_image.split("/")[0]
|
|
image = full_image.split("/")[1]
|
|
|
|
return f"Official {service} plugin for dokku. Currently defaults to installing [{full_image} {version}](https://hub.docker.com/{base}/{image}/)."
|
|
|
|
|
|
def sponsors_section(service, sponsors):
|
|
if len(sponsors) == 0:
|
|
return ""
|
|
|
|
sponsor_data = ["## Sponsors", "", f"The {service} plugin was generously sponsored by the following:", ""]
|
|
sponsor_data.extend([f"- [{s}](https://github.com/{s})" for s in sponsors])
|
|
|
|
return "\n".join(
|
|
sponsor_data
|
|
)
|
|
|
|
|
|
def requirements_section(dokku_version):
|
|
return "\n".join(
|
|
["## Requirements", "", f"- dokku {dokku_version}", "- docker 1.8.x",]
|
|
)
|
|
|
|
|
|
def installation_section(service, dokku_version):
|
|
return "\n".join(
|
|
[
|
|
"## Installation",
|
|
"",
|
|
"```shell",
|
|
f"# on {dokku_version}",
|
|
f"sudo dokku plugin:install https://github.com/dokku/dokku-{service}.git {service}",
|
|
"```",
|
|
]
|
|
)
|
|
|
|
|
|
def commands_section(service, variable, alias, image, scheme, ports, unimplemented):
|
|
content = [
|
|
"## Commands",
|
|
"",
|
|
"```",
|
|
]
|
|
|
|
subcommands = os.listdir("subcommands")
|
|
subcommands.sort()
|
|
|
|
command_list = []
|
|
descriptions = []
|
|
for filename in subcommands:
|
|
if filename in unimplemented:
|
|
continue
|
|
data = command_data(filename, service, variable, alias, image, scheme, ports)
|
|
description = data["description"]
|
|
arguments = data["arguments_string"]
|
|
|
|
command_list.append(f"{service}:{filename} {arguments}")
|
|
descriptions.append(description)
|
|
|
|
maxlen = max(map(len, command_list))
|
|
if maxlen > 50:
|
|
maxlen = 50
|
|
for command, description in zip(command_list, descriptions):
|
|
space_count = maxlen - len(command)
|
|
content.append("{0}{1} # {2}".format(command, " " * space_count, description))
|
|
|
|
content.append("```")
|
|
return "\n".join(content)
|
|
|
|
|
|
def usage_section(service, variable, alias, image, scheme, ports, unimplemented):
|
|
return "\n\n".join(
|
|
[
|
|
"## Usage",
|
|
f"Help for any commands can be displayed by specifying the command as an argument to {service}:help. Please consult the `{service}:help` command for any undocumented commands.",
|
|
usage_intro(service, variable, alias, image, scheme, ports, unimplemented),
|
|
usage_lifecycle(service, variable, alias, image, scheme, ports, unimplemented),
|
|
usage_automation(service, variable, alias, image, scheme, ports, unimplemented),
|
|
usage_data_management(service, variable, alias, image, scheme, ports, unimplemented),
|
|
usage_backup(service, variable, alias, image, scheme, ports, unimplemented),
|
|
usage_docker_pull(service, variable, alias, image, scheme, ports, unimplemented),
|
|
]
|
|
)
|
|
|
|
|
|
def usage_intro(service, variable, alias, image, scheme, ports, unimplemented):
|
|
commands = ["create", "info", "list", "logs", "link", "unlink"]
|
|
content = ["### Basic Usage"]
|
|
|
|
return fetch_commands_content(
|
|
service, variable, alias, image, scheme, ports, unimplemented, commands, content
|
|
)
|
|
|
|
|
|
def usage_lifecycle(service, variable, alias, image, scheme, ports, unimplemented):
|
|
commands = [
|
|
"connect",
|
|
"enter",
|
|
"expose",
|
|
"unexpose",
|
|
"promote",
|
|
"start",
|
|
"stop",
|
|
"restart",
|
|
"upgrade",
|
|
]
|
|
content = [
|
|
"### Service Lifecycle",
|
|
"",
|
|
"The lifecycle of each service can be managed through the following commands:",
|
|
"",
|
|
]
|
|
|
|
return fetch_commands_content(
|
|
service, variable, alias, image, scheme, ports, unimplemented, commands, content
|
|
)
|
|
|
|
|
|
def usage_automation(service, variable, alias, image, scheme, ports, unimplemented):
|
|
commands = ["app-links", "clone", "exists", "linked", "links"]
|
|
content = [
|
|
"### Service Automation",
|
|
"",
|
|
"Service scripting can be executed using the following commands:",
|
|
"",
|
|
]
|
|
|
|
return fetch_commands_content(
|
|
service, variable, alias, image, scheme, ports, unimplemented, commands, content
|
|
)
|
|
|
|
|
|
def usage_data_management(service, variable, alias, image, scheme, ports, unimplemented):
|
|
commands = ["import", "export"]
|
|
content = [
|
|
"### Data Management",
|
|
"",
|
|
"The underlying service data can be imported and exported with the following commands:",
|
|
"",
|
|
]
|
|
|
|
return fetch_commands_content(
|
|
service, variable, alias, image, scheme, ports, unimplemented, commands, content
|
|
)
|
|
|
|
|
|
def usage_backup(service, variable, alias, image, scheme, ports, unimplemented):
|
|
commands = [
|
|
"backup-auth",
|
|
"backup-deauth",
|
|
"backup",
|
|
"backup-set-encryption",
|
|
"backup-unset-encryption",
|
|
"backup-schedule",
|
|
"backup-schedule-cat",
|
|
"backup-unschedule",
|
|
]
|
|
content = [
|
|
"### Backups",
|
|
"",
|
|
"Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio).",
|
|
"",
|
|
"You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command.",
|
|
"",
|
|
"Backups can be performed using the backup commands:",
|
|
"",
|
|
]
|
|
|
|
return fetch_commands_content(
|
|
service, variable, alias, image, scheme, ports, unimplemented, commands, content
|
|
)
|
|
|
|
|
|
def usage_docker_pull(service, variable, alias, image, scheme, ports, unimplemented):
|
|
service_prefix = service.upper()
|
|
return "\n".join(
|
|
[
|
|
"### Disabling `docker pull` calls",
|
|
"",
|
|
f"If you wish to disable the `docker pull` calls that the plugin triggers, you may set the `{service_prefix}_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output.",
|
|
"",
|
|
"Please ensure the proper images are in place when `docker pull` is disabled.",
|
|
]
|
|
)
|
|
|
|
|
|
def fetch_commands_content(
|
|
service, variable, alias, image, scheme, ports, unimplemented, commands, content
|
|
):
|
|
i = 0
|
|
for command in commands:
|
|
output = command_help(command, service, variable, alias, image, scheme, ports, unimplemented)
|
|
if output == "":
|
|
continue
|
|
content.append(output)
|
|
i += 1
|
|
|
|
if i == 0:
|
|
return ""
|
|
|
|
return "\n".join(content)
|
|
|
|
|
|
def parse_args(line):
|
|
line = line.strip()
|
|
arguments = []
|
|
for arg in re.findall("([A-Z_]+)", line):
|
|
arg = arg.replace("_", "-").lower()
|
|
if arg.endswith("optional-flag"):
|
|
arg = arg.replace("-optional-flag", "")
|
|
arguments.append(f"[--{arg}]")
|
|
elif arg.endswith("-flag"):
|
|
if arg == "info-flag":
|
|
arguments.append(f"[--single-info-flag]")
|
|
else:
|
|
arg = arg.replace("-flag", "")
|
|
first_letter = arg[0]
|
|
arguments.append(f"[-{first_letter}|--{arg}]")
|
|
elif arg.endswith("-flags-list"):
|
|
arg = arg.replace("-list", "")
|
|
arguments.append(f"[--{arg}...]")
|
|
elif arg.endswith("list"):
|
|
arg = arg.replace("-list", "")
|
|
arguments.append(f"<{arg}...>")
|
|
else:
|
|
arguments.append(f"<{arg}>")
|
|
return " ".join(arguments)
|
|
|
|
|
|
def command_help(command, service, variable, alias, image, scheme, ports, unimplemented):
|
|
if command in unimplemented:
|
|
return ""
|
|
|
|
data = command_data(command, service, variable, alias, image, scheme, ports)
|
|
content = [
|
|
f"### {data['description']}",
|
|
"",
|
|
"```shell",
|
|
"# usage",
|
|
f"dokku {service}:{command} {data['arguments_string']}",
|
|
"```",
|
|
]
|
|
|
|
# if len(data["arguments"]) > 0:
|
|
# content.append("")
|
|
# content.append("arguments:")
|
|
# content.append("")
|
|
# for argument in data["arguments"]:
|
|
# content.append(f"- {argument}")
|
|
|
|
if len(data["flags"]) > 0:
|
|
content.append("")
|
|
content.append("flags:")
|
|
content.append("")
|
|
for flag in data["flags"]:
|
|
content.append(f"- {flag}")
|
|
|
|
if len(data["examples"]) > 0:
|
|
content.append("")
|
|
content.append(data["examples"])
|
|
|
|
return "\n" + "\n".join(content)
|
|
|
|
|
|
def command_data(command, service, variable, alias, image, scheme, ports):
|
|
description = None
|
|
arguments = []
|
|
arguments_string = ""
|
|
example_lines = []
|
|
flags = []
|
|
with open(os.path.join("subcommands", command)) as f:
|
|
for line in f.readlines():
|
|
line = line.strip()
|
|
line = line.replace("$PLUGIN_SERVICE", service)
|
|
line = line.replace("$PLUGIN_COMMAND_PREFIX", service)
|
|
line = line.replace("${PLUGIN_COMMAND_PREFIX}", service)
|
|
line = line.replace("${PLUGIN_VARIABLE}", variable)
|
|
line = line.replace("${PLUGIN_DEFAULT_ALIAS}", alias)
|
|
line = line.replace("${PLUGIN_IMAGE}", image)
|
|
line = line.replace("${PLUGIN_SCHEME}", scheme)
|
|
line = line.replace("${PLUGIN_DATASTORE_PORTS[0]}", ports[0])
|
|
line = line.replace("${PLUGIN_DATASTORE_PORTS[@]}", " ".join(ports))
|
|
|
|
if "declare desc" in line:
|
|
description = re.search('"(.+)"', line).group(1)
|
|
elif "$1" in line:
|
|
arguments_string = parse_args(line)
|
|
elif line.startswith("#A "):
|
|
argument = line.replace("#A ", "")
|
|
parts = [a.strip() for a in argument.split(",", 1)]
|
|
arguments.append(f"`{parts[0]}`: {parts[1]}")
|
|
elif line.startswith("#F "):
|
|
flag = line.replace("#F ", "")
|
|
parts = [a.strip() for a in flag.split(",", 1)]
|
|
flags.append(f"`{parts[0]}`: {parts[1]}")
|
|
elif line.startswith("#E "):
|
|
example_lines.append(line.replace("#E ", ""))
|
|
|
|
examples = []
|
|
sentence_lines = []
|
|
command_lines = []
|
|
codeblock_lines = []
|
|
blockquote_lines = []
|
|
for line in example_lines:
|
|
if line.startswith("export") or line.startswith("dokku"):
|
|
if len(blockquote_lines) > 0:
|
|
examples.append("\n" + process_blockquote(blockquote_lines))
|
|
blockquote_lines = []
|
|
if len(codeblock_lines) > 0:
|
|
examples.append("\n" + process_codeblock(codeblock_lines))
|
|
codeblock_lines = []
|
|
if len(sentence_lines) > 0:
|
|
examples.append("\n" + process_sentence(sentence_lines))
|
|
sentence_lines = []
|
|
|
|
command_lines.append(line)
|
|
elif line.startswith(" "):
|
|
if len(blockquote_lines) > 0:
|
|
examples.append("\n" + process_blockquote(blockquote_lines))
|
|
blockquote_lines = []
|
|
if len(command_lines) > 0:
|
|
examples.append("\n" + process_command(command_lines))
|
|
command_lines = []
|
|
if len(sentence_lines) > 0:
|
|
examples.append("\n" + process_sentence(sentence_lines))
|
|
sentence_lines = []
|
|
|
|
codeblock_lines.append(line.strip())
|
|
elif line.startswith(">"):
|
|
if len(codeblock_lines) > 0:
|
|
examples.append("\n" + process_codeblock(codeblock_lines))
|
|
codeblock_lines = []
|
|
if len(command_lines) > 0:
|
|
examples.append("\n" + process_command(command_lines))
|
|
command_lines = []
|
|
if len(sentence_lines) > 0:
|
|
examples.append("\n" + process_sentence(sentence_lines))
|
|
sentence_lines = []
|
|
|
|
blockquote_lines.append(line)
|
|
else:
|
|
if len(blockquote_lines) > 0:
|
|
examples.append("\n" + process_blockquote(blockquote_lines))
|
|
blockquote_lines = []
|
|
if len(codeblock_lines) > 0:
|
|
examples.append("\n" + process_codeblock(codeblock_lines))
|
|
codeblock_lines = []
|
|
if len(command_lines) > 0:
|
|
examples.append("\n" + process_command(command_lines))
|
|
command_lines = []
|
|
|
|
sentence_lines.append(line)
|
|
|
|
if len(blockquote_lines) > 0:
|
|
examples.append("\n" + process_blockquote(blockquote_lines))
|
|
blockquote_lines = []
|
|
if len(codeblock_lines) > 0:
|
|
examples.append("\n" + process_codeblock(codeblock_lines))
|
|
codeblock_lines = []
|
|
if len(command_lines) > 0:
|
|
examples.append("\n" + process_command(command_lines))
|
|
command_lines = []
|
|
if len(sentence_lines) > 0:
|
|
examples.append("\n" + process_sentence(sentence_lines))
|
|
sentence_lines = []
|
|
|
|
return {
|
|
"description": description,
|
|
"arguments_string": arguments_string,
|
|
"arguments": arguments,
|
|
"flags": flags,
|
|
"examples": "\n".join(examples).strip(),
|
|
}
|
|
|
|
|
|
def process_sentence(sentence_lines):
|
|
sentence_lines = " ".join(sentence_lines)
|
|
sentences = ". ".join(
|
|
upperfirst(i.strip()) for i in sentence_lines.split(". ")
|
|
).strip()
|
|
if not sentences.endswith(".") and not sentences.endswith(":"):
|
|
sentences += ":"
|
|
|
|
text = []
|
|
for sentence in sentences.split(". "):
|
|
parts = []
|
|
for word in sentence.strip().split(" "):
|
|
if word.isupper() and len(word) > 1:
|
|
for ending in [':', '.']:
|
|
if word.endswith(ending):
|
|
word = '`{0}`{1}'.format(word[:-1], ending)
|
|
else:
|
|
word = '`{0}`'.format(word)
|
|
parts.append(word)
|
|
text.append(" ".join(parts))
|
|
|
|
text = ". ".join(text)
|
|
|
|
# some cleanup
|
|
text = text.replace("(0.0.0.0)", "(`0.0.0.0`)")
|
|
text = text.replace("'", "`")
|
|
text = text.replace("`s", "'s")
|
|
text = text.replace("``", "`")
|
|
text = text.strip(" ")
|
|
|
|
return text
|
|
|
|
|
|
def upperfirst(x):
|
|
return x[:1].upper() + x[1:]
|
|
|
|
|
|
def process_blockquote(blockquote_lines):
|
|
return "\n".join(blockquote_lines)
|
|
|
|
|
|
def process_command(command_lines):
|
|
command_lines = "\n".join(command_lines)
|
|
return f"```shell\n{command_lines}\n```"
|
|
|
|
|
|
def process_codeblock(codeblock_lines):
|
|
codeblock_lines = "\n".join(codeblock_lines)
|
|
return f"```\n{codeblock_lines}\n```"
|
|
|
|
|
|
def main():
|
|
service = None
|
|
version = None
|
|
variable = None
|
|
image = None
|
|
alias = None
|
|
unimplemented = []
|
|
|
|
with open("Dockerfile") as f:
|
|
for line in f.readlines():
|
|
if "FROM " in line:
|
|
image, version = line.split(" ")[1].split(":")
|
|
image = image.strip()
|
|
version = version.strip()
|
|
|
|
with open("config") as f:
|
|
for line in f.readlines():
|
|
if "PLUGIN_COMMAND_PREFIX=" in line:
|
|
service = re.search('"(.+)"', line).group(1)
|
|
if "PLUGIN_DEFAULT_ALIAS=" in line:
|
|
alias = re.search('"(.+)"', line).group(1)
|
|
if "PLUGIN_VARIABLE=" in line:
|
|
variable = re.search('"(.+)"', line).group(1)
|
|
if "PLUGIN_SCHEME=" in line:
|
|
scheme = re.search('"(.+)"', line).group(1)
|
|
if "PLUGIN_DATASTORE_PORTS=" in line:
|
|
ports = re.search("\((.+)\)", line).group(1).split(" ")
|
|
if "PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=" in line:
|
|
match = re.search("\((.+)\)", line)
|
|
if match is not None:
|
|
unimplemented = [s.strip('"') for s in match.group(1).split(" ")]
|
|
|
|
sponsors = []
|
|
with open("plugin.toml") as f:
|
|
for line in f.readlines():
|
|
if line.startswith("sponsors"):
|
|
sponsors = re.search("\[([\"\w\s,_-]+)\]", line).group(1)
|
|
sponsors = [s.strip("\"") for s in sponsors.split(",")]
|
|
|
|
text = compile(service, version, variable, alias, image, scheme, ports, sponsors, unimplemented, "0.19.x+")
|
|
|
|
base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
readme_file = os.path.join(base_path, "README.md")
|
|
with open(readme_file, "w") as f:
|
|
f.write(text + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|