#!/usr/bin/env python
from __future__ import annotations
import argparse
import logging
import subprocess as sp
from pathlib import Path
from pprint import pformat
import pymongo
from arrakis.logger import logger
from pymongo.database import Database
logger.setLevel(logging.INFO)
[docs]
class MongodError(Exception):
pass
[docs]
def start_mongod(
dbpath: Path,
logpath: Path,
host: str = "localhost",
port: int | None = None,
auth: bool = False,
):
cmd = f"mongod --fork --dbpath {dbpath} --logpath {logpath} --bind_ip {host}"
if auth:
cmd += " --auth"
if port is not None:
cmd += f"--port {port}"
logger.info(f"Running command: {cmd}")
try:
proc = sp.check_output(cmd.split())
except sp.CalledProcessError as e:
if e.returncode == 48:
logger.error(
f"mongod already running - try shutting down first with `mongod --dbpath {dbpath} --shutdown`"
)
logger.error(f"{e}")
raise MongodError(f"Failed to start mongod. Command was: {cmd}")
logger.info(proc.decode())
logger.info("Started mongod")
[docs]
def stop_mongod(
dbpath: Path,
):
logger.info(f"Stopping mongod with dbpath {dbpath}")
cmd = f"mongod --dbpath {dbpath} --shutdown"
logger.info(f"Running command: {cmd}")
try:
proc = sp.check_output(cmd.split())
except sp.CalledProcessError as e:
logger.error(f"{e}")
raise MongodError(f"Failed to stop mongod. Command was: {cmd}")
logger.info(proc.decode())
logger.info("Stopped mongod")
[docs]
def create_or_update_user(
db: Database,
username: str,
password: str,
roles: list,
):
try:
return db.command(
"createUser",
username,
pwd=password,
roles=roles,
)
except pymongo.errors.OperationFailure as e:
if e.code == 51003:
logger.warning(f"User {username} already exists - updating...")
return db.command(
"updateUser",
username,
pwd=password,
roles=roles,
)
raise e
[docs]
def create_admin_user(
host: str,
password: str,
port: int | None = None,
username: str = "admin",
):
logger.info(f"Creating admin user {username} on {host}:{port}")
client = pymongo.MongoClient(host, port)
db = client.admin
res = create_or_update_user(
db=db,
username=username,
password=password,
roles=["userAdminAnyDatabase", "readWriteAnyDatabase"],
)
logger.info(pformat(res))
[docs]
def create_read_only_user(
host: str,
password: str,
port: int | None = None,
username: str = "reader",
):
logger.info(f"Creating read-only user {username} on {host}:{port}")
client = pymongo.MongoClient(host, port)
db = client.admin
res = create_or_update_user(
db=db,
username=username,
password=password,
roles=["readAnyDatabase"],
)
logger.info(pformat(res))
[docs]
def main(
dbpath: Path,
admin_password: str,
reader_password: str,
host: str = "localhost",
port: int | None = None,
admin_username: str | None = "admin",
reader_username: str | None = "reader",
):
logpath = dbpath.parent / "mongod.log"
start_mongod(
dbpath=dbpath,
logpath=logpath,
host=host,
port=port,
auth=False,
)
create_admin_user(
host=host,
port=port,
password=admin_password,
username=admin_username,
)
create_read_only_user(
host=host,
port=port,
password=reader_password,
username=reader_username,
)
stop_mongod(dbpath=dbpath)
start_mongod(
dbpath=dbpath,
port=port,
logpath=logpath,
host=host,
auth=True,
)
logger.info("Done!")
[docs]
def cli():
parser = argparse.ArgumentParser()
parser.add_argument("--dbpath", type=Path, required=True)
parser.add_argument("--admin-password", type=str, required=True)
parser.add_argument("--reader-password", type=str, required=True)
parser.add_argument("--host", type=str, default="localhost")
parser.add_argument("--port", type=int, default=None)
parser.add_argument("--admin-username", type=str, default="admin")
parser.add_argument("--reader-username", type=str, default="reader")
args = parser.parse_args()
main(
dbpath=args.dbpath,
admin_password=args.admin_password,
reader_password=args.reader_password,
host=args.host,
port=args.port,
admin_username=args.admin_username,
reader_username=args.reader_username,
)
if __name__ == "__main__":
cli()