├── pr-db-mongo ├── requirements.txt ├── uninstall ├── post-app-clone ├── post-delete ├── plugin.toml ├── install └── pr_db_mongo.py ├── requirements-dev.txt ├── .vscode └── settings.json ├── .github └── dependabot.yml ├── .pre-commit-config.yaml ├── Makefile ├── README.md └── .gitignore /pr-db-mongo/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pre-commit===4.5.0 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.autopep8" 4 | }, 5 | "python.formatting.provider": "none" 6 | } -------------------------------------------------------------------------------- /pr-db-mongo/uninstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 4 | 5 | PLUGIN="$1" 6 | 7 | [[ "$PLUGIN" = "pr-db-mongo" ]] && rm -rf "$DOKKU_ROOT/.venvs/pr-db-mongo" 8 | -------------------------------------------------------------------------------- /pr-db-mongo/post-app-clone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 4 | 5 | APP="$2"; 6 | 7 | source "$DOKKU_ROOT/.venvs/pr-db-mongo/bin/activate" 8 | 9 | python "$PLUGIN_AVAILABLE_PATH/pr-db-mongo/pr_db_mongo.py" $APP SETUP 10 | 11 | deactivate 12 | -------------------------------------------------------------------------------- /pr-db-mongo/post-delete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 4 | 5 | APP="$1"; 6 | 7 | source "$DOKKU_ROOT/.venvs/pr-db-mongo/bin/activate" 8 | 9 | python "$PLUGIN_AVAILABLE_PATH/pr-db-mongo/pr_db_mongo.py" $APP DELETE 10 | 11 | deactivate 12 | -------------------------------------------------------------------------------- /pr-db-mongo/plugin.toml: -------------------------------------------------------------------------------- 1 | [plugin] 2 | name = "pr-db-mongo" 3 | description = "Setup & tear Mongo DB for reviews apps" 4 | version = "0.0.1" 5 | maintainer_name = "Code for Africa" 6 | maintainer_email = "tech@codeforafrica.org" 7 | website_url = "https://github.com/CodeForAfrica/dokku-plugins" 8 | 9 | [plugin.config] 10 | 11 | [plugin.compatibility] 12 | -------------------------------------------------------------------------------- /pr-db-mongo/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 4 | 5 | # Creating venv in $PLUGIN_AVAILABLE_PATH causes issue for Dokku 6 | # hence we'll create it in user dokku home dir 7 | mkdir -p "$DOKKU_ROOT/.venvs" 8 | python3 -m venv "$DOKKU_ROOT/.venvs/pr-db-mongo" 9 | source "$DOKKU_ROOT/.venvs/pr-db-mongo/bin/activate" 10 | 11 | python -m pip install --upgrade pip 12 | python -m pip install -r "$PLUGIN_AVAILABLE_PATH/pr-db-mongo/requirements.txt" 13 | 14 | deactivate 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: requirements-txt-fixer 8 | - id: trailing-whitespace 9 | args: [--markdown-linebreak-ext=md] 10 | 11 | - repo: https://github.com/PyCQA/isort 12 | rev: 5.12.0 13 | hooks: 14 | - id: isort 15 | 16 | - repo: https://github.com/psf/black 17 | rev: 23.7.0 18 | hooks: 19 | - id: black 20 | language_version: python3 21 | 22 | - repo: https://github.com/astral-sh/ruff-pre-commit 23 | rev: v0.0.278 24 | hooks: 25 | - id: ruff 26 | 27 | - repo: https://github.com/pre-commit/mirrors-mypy 28 | rev: v1.4.1 29 | hooks: 30 | - id: mypy 31 | args: [--ignore-missing-imports] 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR := $(shell dirname "$(realpath $(firstword $(MAKEFILE_LIST)))") 2 | 3 | all: build 4 | 5 | ## display this message 6 | help: 7 | @echo '' 8 | @echo 'Usage:' 9 | @echo 'make ' 10 | @echo '' 11 | @echo 'Targets:' 12 | @awk '/^##/{c=substr($$0,3);next}c&&/^[[:alpha:]][[:alnum:]_-]+:/{print substr($$1,1,index($$1,":")),c}1{c=0}' $(MAKEFILE_LIST) | column -s: -t 13 | @echo '' 14 | 15 | ## tidy up local dev environment 16 | clean: 17 | rm -rf __pycache__ .mypy_cache build 18 | 19 | ## build all plugins 20 | build: build-pr-db-mongo 21 | 22 | ## build 'pr-db-mongo' plugin 23 | build-pr-db-mongo: make-build-dir 24 | rm -rf pr-db-mongo/venv 25 | tar --create --no-xattrs --disable-copyfile --file build/pr-db-mongo.tgz pr-db-mongo 26 | 27 | # create 'build' output dir 28 | make-build-dir: 29 | mkdir -p build 30 | 31 | ## check and format code 32 | lint: 33 | pre-commit run --all-files 34 | 35 | .PHONY: all build build-pr-db-mongo clean help lint make-build-dir 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dokku Plugins 2 | 3 | Various [Dokku](https://dokku.com) plugins used to develop and manage software at [Code for Africa](https://codeforafrica.org). 4 | 5 | ## Available Plugins 6 | 7 | ### `pr-db-mongo` 8 | 9 | ## Prerequisites 10 | 11 | - [Dokku](https://dokku.com/docs/development/plugin-triggers) 12 | - [Python](https://www.python.org/downloads/) 13 | - Mongo Database Tools. 14 | > To install mongo database tools, 15 | 16 | ```sh 17 | wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add - 18 | echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list 19 | sudo apt-get update 20 | sudo apt-get install -y mongodb-org 21 | ``` 22 | 23 | ## Installation 24 | 25 | The .tgz installation via dokku plugin:install file:// or https:// seem to fail. We shall therefore use the manual installation until a fix is found. 26 | 27 | ### Copy the plugin(s) to dev 28 | 29 | Copy the .tgz from the local/CI instance to the dev machine (running Dokku) 30 | 31 | ```bash 32 | scp plugin.tgz user@remotehost:/path-to-copy-file 33 | ``` 34 | 35 | For example, if our plugin is called `pr-db-mongo`, then: 36 | 37 | ```bash 38 | scp build/pr-db-mongo.tgz ubuntu@dev.codeforafrica.org:/~ 39 | ``` 40 | 41 | ### Make the plugin available to Dokku 42 | 43 | ssh to the dev machine 44 | 45 | ```bash 46 | ssh ubuntu@dev.codeforafrica.org: 47 | ``` 48 | 49 | Extract and copy plugin into `$PLUGIN_AVAILABLE_PATH` 50 | 51 | ``` 52 | tar -xf pr-db-mongo.tgz 53 | chown dokku:dokku -R pr-db-mongo 54 | mv pr-db-mongo /var/lib/dokku/plugins/available 55 | ``` 56 | 57 | ### Enable the plugin 58 | 59 | ```bash 60 | sudo dokku plugin:enable plugin-name 61 | ``` 62 | 63 | ### Install Plugin 64 | 65 | ```bash 66 | sudo dokku plugin:install 67 | ``` 68 | 69 | ### Install Dependencies 70 | 71 | ```bash 72 | sudo dokku plugin:install-dependencies 73 | ``` 74 | 75 | That's it. Happy coding! 76 | 77 | ## Usage 78 | 79 | ## Contributing 80 | -------------------------------------------------------------------------------- /pr-db-mongo/pr_db_mongo.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | from urllib.parse import quote_plus, urlsplit 4 | 5 | PLUGIN_NAME = "pr-db-mongo" 6 | 7 | 8 | def execute_bash(command, **kwargs): 9 | return subprocess.run(command, check=True, capture_output=True, text=True, *kwargs) 10 | 11 | 12 | def get_uri(db_url, db_name=""): 13 | split_url = urlsplit(db_url) 14 | username = split_url.username 15 | password = split_url.password 16 | scheme = split_url.scheme 17 | hostname = split_url.hostname 18 | port = split_url.port 19 | query = split_url.query 20 | credentials = ":".join([quote_plus(c) for c in (username, password)]) 21 | host = f"{hostname}:{port}" if port else hostname 22 | options = f"/{db_name}?{query}" if query else "" 23 | 24 | return f"{scheme}://{credentials}@{host}{options}" 25 | 26 | 27 | def configure_pr_app(original_mongodb_url, app_name): 28 | mongodb_url = get_uri(original_mongodb_url, app_name) 29 | app_url = f"PAYLOAD_PUBLIC_APP_URL=https://{app_name}.dev.codeforafrica.org" 30 | commands = [ 31 | ["dokku", "config:set", "--no-restart", app_name, app_url], 32 | ["dokku", "config:set", "--no-restart", 33 | app_name, f"MONGODB_URL={mongodb_url}"], 34 | # TODO: get main domain from original app and modify it 35 | ["dokku", "domains:add", app_name, 36 | f"{app_name}.dev.codeforafrica.org"], 37 | ["dokku", "letsencrypt:enable", app_name], 38 | ] 39 | for command in commands: 40 | execute_bash(command) 41 | 42 | 43 | def clone_pr_database(original_mongodb_url, app_name): 44 | split_url = urlsplit(original_mongodb_url) 45 | source = split_url.path.lstrip("/") 46 | uri = get_uri(original_mongodb_url) 47 | command1 = [f"mongodump", "-vvvvv", "--archive", 48 | f"--uri={uri}", f"--db={source}",] 49 | command2 = [f"mongorestore", "-vvvvv", f"--uri={uri}", "--archive", 50 | f"--nsFrom={source}.*", f"--nsTo={app_name}.*", f"--nsInclude={source}.*"] 51 | proc1 = subprocess.Popen(command1, stdout=subprocess.PIPE) 52 | proc2 = subprocess.Popen(command2, stdin=proc1.stdout) 53 | 54 | proc1.stdout.close() 55 | proc2.communicate() 56 | 57 | 58 | def setup_pr_app(original_mongodb_url, app_name): 59 | print(f"{PLUGIN_NAME}: setting up '{app_name}' ... ", end="") 60 | try: 61 | clone_pr_database(original_mongodb_url, app_name) 62 | configure_pr_app(original_mongodb_url, app_name) 63 | 64 | print("done") 65 | except Exception as e: 66 | print("failed") 67 | print(f"{PLUGIN_NAME}:", e) 68 | sys.exit(1) 69 | 70 | 71 | def delete_pr_database(original_mongodb_url, db_name): 72 | print(f"{PLUGIN_NAME}: deleting database '{db_name}' ... ", end="") 73 | try: 74 | uri = get_uri(original_mongodb_url, db_name) 75 | command = ["mongosh", uri, "--eval", "db.dropDatabase()"] 76 | execute_bash(command) 77 | 78 | print("done") 79 | except Exception as e: 80 | print("failed") 81 | print(f"{PLUGIN_NAME}:", e) 82 | sys.exit(1) 83 | 84 | 85 | if __name__ == "__main__": 86 | if len(sys.argv) != 3: 87 | sys.exit(f"{PLUGIN_NAME} ({sys.argv})") 88 | action = sys.argv[2].lower() 89 | if action not in ["setup", "delete"]: 90 | sys.exit(f"{PLUGIN_NAME} ") 91 | app_name = sys.argv[1] 92 | app_name_pr_number = app_name.split("-pr-") 93 | if len(app_name_pr_number) == 2: 94 | original_app_name = app_name_pr_number[0].strip() 95 | command = ["dokku", "config:get", original_app_name, "MONGODB_URL"] 96 | original_mongodb_url = execute_bash(command).stdout.strip() 97 | if original_mongodb_url: 98 | if action == "setup": 99 | setup_pr_app(original_mongodb_url, app_name) 100 | else: # action == "delete": 101 | delete_pr_database(original_mongodb_url, app_name) 102 | # else: no MONGODB_URL found, silently skip 103 | # else: app doesn't follow naming convention, silently skip 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,macos,linux 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode,macos,linux 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | # General 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | 25 | # Icon must end with two \r 26 | Icon 27 | 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### macOS Patch ### 49 | # iCloud generated files 50 | *.icloud 51 | 52 | ### Python ### 53 | # Byte-compiled / optimized / DLL files 54 | __pycache__/ 55 | *.py[cod] 56 | *$py.class 57 | 58 | # C extensions 59 | *.so 60 | 61 | # Distribution / packaging 62 | .Python 63 | build/ 64 | develop-eggs/ 65 | dist/ 66 | downloads/ 67 | eggs/ 68 | .eggs/ 69 | lib/ 70 | lib64/ 71 | parts/ 72 | sdist/ 73 | var/ 74 | wheels/ 75 | share/python-wheels/ 76 | *.egg-info/ 77 | .installed.cfg 78 | *.egg 79 | MANIFEST 80 | 81 | # PyInstaller 82 | # Usually these files are written by a python script from a template 83 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 84 | *.manifest 85 | *.spec 86 | 87 | # Installer logs 88 | pip-log.txt 89 | pip-delete-this-directory.txt 90 | 91 | # Unit test / coverage reports 92 | htmlcov/ 93 | .tox/ 94 | .nox/ 95 | .coverage 96 | .coverage.* 97 | .cache 98 | nosetests.xml 99 | coverage.xml 100 | *.cover 101 | *.py,cover 102 | .hypothesis/ 103 | .pytest_cache/ 104 | cover/ 105 | 106 | # Translations 107 | *.mo 108 | *.pot 109 | 110 | # Django stuff: 111 | *.log 112 | local_settings.py 113 | db.sqlite3 114 | db.sqlite3-journal 115 | 116 | # Flask stuff: 117 | instance/ 118 | .webassets-cache 119 | 120 | # Scrapy stuff: 121 | .scrapy 122 | 123 | # Sphinx documentation 124 | docs/_build/ 125 | 126 | # PyBuilder 127 | .pybuilder/ 128 | target/ 129 | 130 | # Jupyter Notebook 131 | .ipynb_checkpoints 132 | 133 | # IPython 134 | profile_default/ 135 | ipython_config.py 136 | 137 | # pyenv 138 | # For a library or package, you might want to ignore these files since the code is 139 | # intended to run in multiple environments; otherwise, check them in: 140 | # .python-version 141 | 142 | # pipenv 143 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 144 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 145 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 146 | # install all needed dependencies. 147 | #Pipfile.lock 148 | 149 | # poetry 150 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 151 | # This is especially recommended for binary packages to ensure reproducibility, and is more 152 | # commonly ignored for libraries. 153 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 154 | #poetry.lock 155 | 156 | # pdm 157 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 158 | #pdm.lock 159 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 160 | # in version control. 161 | # https://pdm.fming.dev/#use-with-ide 162 | .pdm.toml 163 | 164 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 165 | __pypackages__/ 166 | 167 | # Celery stuff 168 | celerybeat-schedule 169 | celerybeat.pid 170 | 171 | # SageMath parsed files 172 | *.sage.py 173 | 174 | # Environments 175 | .env 176 | .venv 177 | env/ 178 | venv/ 179 | ENV/ 180 | env.bak/ 181 | venv.bak/ 182 | 183 | # Spyder project settings 184 | .spyderproject 185 | .spyproject 186 | 187 | # Rope project settings 188 | .ropeproject 189 | 190 | # mkdocs documentation 191 | /site 192 | 193 | # mypy 194 | .mypy_cache/ 195 | .dmypy.json 196 | dmypy.json 197 | 198 | # Pyre type checker 199 | .pyre/ 200 | 201 | # pytype static type analyzer 202 | .pytype/ 203 | 204 | # Cython debug symbols 205 | cython_debug/ 206 | 207 | # PyCharm 208 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 209 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 210 | # and can be added to the global gitignore or merged into this file. For a more nuclear 211 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 212 | #.idea/ 213 | 214 | ### Python Patch ### 215 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 216 | poetry.toml 217 | 218 | # ruff 219 | .ruff_cache/ 220 | 221 | # LSP config files 222 | pyrightconfig.json 223 | 224 | ### VisualStudioCode ### 225 | .vscode/* 226 | !.vscode/settings.json 227 | !.vscode/tasks.json 228 | !.vscode/launch.json 229 | !.vscode/extensions.json 230 | !.vscode/*.code-snippets 231 | 232 | # Local History for Visual Studio Code 233 | .history/ 234 | 235 | # Built Visual Studio Code Extensions 236 | *.vsix 237 | 238 | ### VisualStudioCode Patch ### 239 | # Ignore all local history of files 240 | .history 241 | .ionide 242 | 243 | # End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,macos,linux 244 | --------------------------------------------------------------------------------