def deploy(self): CLI.info('Deploying...') self.clean() self.upload() self.pull() self.reload() self.logs() self.status()
def restart_proxy(self): CLI.info('Restarting proxy...') steps = 1 CLI.step(1, steps, 'Reloading proxy container...') os.system( f'docker-compose {self.docker_ssh} -f configs/docker/docker-compose.{self.environment_id}.proxy.yml --project-name=reverse up -d' )
def pg_dump(self): now = datetime.datetime.now() # filename = now.strftime("%Y%m%d%H%M%S") filename = now.strftime(f"{self.PROJECT_NAME}_%Y%m%d_%H%M.pg") CLI.info(f'Backuping database into file {filename}') env = self.load_environment() os.system( f'docker {self.docker_ssh} exec -it {self.CONTAINER_DB} bash -c \'pg_dump -Fc -h {env["POSTGRES_HOST"]} -U {env["POSTGRES_USER"]} {env["POSTGRES_DBNAME"]} -W > /backups/{filename}\'' )
def pg_restore(self, params): CLI.info(f'Restoring database from file {params}') CLI.underline( "Don't forget to drop database at first to prevent constraints collisions!" ) env = self.load_environment() os.system( f'docker {self.docker_ssh} exec -it {self.CONTAINER_DB} bash -c \'pg_restore -h {env["POSTGRES_HOST"]} -U {env["POSTGRES_USER"]} -d {env["POSTGRES_DBNAME"]} -W < /backups/{params}\'' )
def status(self): if self.SWARM: # todo remove containers as well ? CLI.info('Getting status...') os.system(f'docker stack services {self.PROJECT_NAME}') else: CLI.info('Getting status...') steps = 2 CLI.step(1, steps, 'List of Docker images') os.system(f'docker {self.docker_ssh} image ls') CLI.step(2, steps, 'Docker containers') os.system(f'docker {self.docker_ssh} container ls -a --size')
def main(): # check params params = parse_args() # print(params) if len(params['commands']) == 0: CLI.error('Missing commands') environment_id = params['environment_id'] commands = params['commands'] mode = params['settings'].get('mode', 'docker-host') hostname = os.popen('hostname').read().rstrip("\n") # setup manager manager = Mantis(environment_id=environment_id, mode=mode) print(f'Mantis (v{VERSION}) attached to ' f'{Colors.BOLD}{manager.environment_id}{Colors.ENDC}: ' f'{Colors.RED}{manager.host}{Colors.ENDC}, ' f'mode: {Colors.GREEN}{manager.mode}{Colors.ENDC}, ' f'hostname: {Colors.BLUE}{hostname}{Colors.ENDC}') if mode == 'ssh': cmds = [ f'cd {manager.project_path}', f'time mantis {environment_id} --mode=host {" ".join(commands)}' ] cmd = ';'.join(cmds) exec = f"ssh -t {manager.user}@{manager.host} -p {manager.port} '{cmd}'" os.system(exec) else: # execute all commands for command in commands: if ':' in command: command, params = command.split(':') else: params = None execute(manager, command, params)
def networks(self): # todo for swarm CLI.info('Getting networks...') steps = 1 CLI.step(1, steps, 'List of Docker networks') networks = os.popen(f'docker {self.docker_ssh} network ls').read() networks = networks.strip().split('\n') for index, network in enumerate(networks): network_data = list(filter(lambda x: x != '', network.split(' '))) network_name = network_data[1] if index == 0: print(f'{network}\tCONTAINERS') else: containers = os.popen( f'docker {self.docker_ssh} network inspect -f \'{{{{ range $key, $value := .Containers }}}}{{{{ .Name }}}} {{{{ end }}}}\' {network_name}' ).read() containers = ', '.join(containers.split()) print(f'{network}\t{containers}'.strip())
def push(self): CLI.info(f'Pushing...') steps = 2 CLI.step(1, steps, 'Tagging Docker image...') os.system( f'docker tag {self.IMAGE_NAME} {self.DOCKER_REPOSITORY}:{self.DOCKER_TAG}' ) print( f'Successfully tagged {self.DOCKER_REPOSITORY}:{self.DOCKER_TAG}') CLI.step(2, steps, 'Pushing Docker image...') os.system(f'docker push {self.DOCKER_REPOSITORY}:{self.DOCKER_TAG}')
def logs(self, params=None): if self.SWARM: CLI.info('Reading logs...') services = params.split(' ') if params else self.get_services() lines = '-f' if params else '--tail 10' steps = len(services) for index, service in enumerate(services): CLI.step(index + 1, steps, f'{service} logs') os.system(f'docker service logs {service} {lines}') else: CLI.info('Reading logs...') containers = params.split(' ') if params else self.get_containers() lines = '-f' if params else '--tail 10' steps = len(containers) for index, container in enumerate(containers): CLI.step(index + 1, steps, f'{container} logs') os.system(f'docker {self.docker_ssh} logs {container} {lines}')
def remove(self, params=''): if self.SWARM: # todo remove containers as well ? CLI.info('Removing services...') os.system(f'docker stack rm {self.PROJECT_NAME}') else: CLI.info('Removing containers...') containers = self.get_containers( ) if params == '' else params.split(' ') steps = len(containers) for index, container in enumerate(containers): CLI.step(index + 1, steps, f'Removing {container}') os.system(f'docker {self.docker_ssh} container rm {container}')
def stop(self, params=None): if self.SWARM: # todo can stop service ? CLI.info('Removing services...') os.system(f'docker stack rm {self.PROJECT_NAME}') else: CLI.info('Stopping containers...') containers = self.get_containers() if not params else params.split( ' ') steps = len(containers) for index, container in enumerate(containers): CLI.step(index + 1, steps, f'Stopping {container}') os.system( f'docker {self.docker_ssh} container stop {container}')
def start(self, params): if self.SWARM: CLI.info('Starting services...') os.system( f'docker stack deploy -c configs/docker/{self.COMPOSE_PREFIX}.yml -c configs/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml {self.PROJECT_NAME}' ) else: CLI.info('Starting containers...') containers = self.get_containers() if not params else params.split( ' ') steps = len(containers) for index, container in enumerate(containers): CLI.step(index + 1, steps, f'Starting {container}') os.system( f'docker {self.docker_ssh} container start {container}')
def build(self, params=''): CLI.info(f'Building...') CLI.info(f'Params = {params}') CLI.info(f'Dockerfile = {self.configs_path}/docker/{self.DOCKER_FILE}') steps = 1 CLI.step(1, steps, 'Building Docker image...') build_args = self.config['build']['args'] build_args = ','.join(map('='.join, build_args.items())) build_kit = self.config['build']['kit'] build_kit = 'DOCKER_BUILDKIT=1' if build_kit else '' if build_args != '': build_args = build_args.split(',') build_args = [f'--build-arg {arg}' for arg in build_args] build_args = ' '.join(build_args) CLI.info(f'Kit = {build_kit}') CLI.info(f'Args = {build_args}') os.system( f'time {build_kit} docker build . {build_args} -t {self.IMAGE_NAME} -f {self.configs_path}/docker/{self.DOCKER_FILE} {params}' )
def send_test_email(self): CLI.info('Sending test email...') os.system( f'docker {self.docker_ssh} exec -i {self.CONTAINER_APP} python manage.py sendtestemail --admins' )
def restart(self): CLI.info('Restarting...') steps = 4 if self.SWARM: CLI.step(1, steps, 'Stopping and removing Docker app service...') for service in self.get_services(): if service == self.CONTAINER_APP: os.system(f'docker service rm {service}') CLI.step(2, steps, 'Recreating Docker swarm stack...') os.system( f'docker stack deploy -c configs/docker/{self.COMPOSE_PREFIX}.yml -c configs/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml {self.PROJECT_NAME}' ) CLI.step( 3, steps, 'Prune Docker images and volumes') # todo prune on every node os.system( f'docker {self.docker_ssh} system prune --volumes --force') CLI.step(4, steps, 'Collecting static files') # todo collect static app_container = self.get_containers_starts_with(self.CONTAINER_APP) if app_container: os.system( f'docker {self.docker_ssh} exec -i {app_container[0]} python manage.py collectstatic --noinput --verbosity 0' ) else: CLI.step(1, steps, 'Stopping and removing Docker containers...') for service in self.config['containers']['deploy'][ 'zero_downtime'] + self.config['containers']['deploy'][ 'restart']: container = self.get_container_name(service) os.popen(f'docker {self.docker_ssh} container stop {container}' ).read() os.system(f'docker {self.docker_ssh} container rm {container}') CLI.step(2, steps, 'Recreating Docker containers...') os.system( f'docker-compose {self.docker_ssh} -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.yml -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml --project-name={self.PROJECT_NAME} up -d' ) # os.system(f'docker-compose {self.docker_ssh} -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.yml -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml --project-name={self.PROJECT_NAME} up --remove-orphans -d') CLI.step(3, steps, 'Prune Docker images and volumes') os.system( f'docker {self.docker_ssh} system prune --volumes --force') CLI.step(4, steps, 'Collecting static files') os.system( f'docker {self.docker_ssh} exec -i {self.CONTAINER_APP} python manage.py collectstatic --noinput --verbosity 0' )
def reload(self): # todo deploy swarm CLI.info('Reloading containers...') zero_downtime_services = self.config['containers']['deploy'][ 'zero_downtime'] restart_services = self.config['containers']['deploy']['restart'] steps = 5 step = 1 CLI.step(step, steps, f'Zero downtime services: {zero_downtime_services}') for service in zero_downtime_services: container = self.get_container_name(service) os.system( f'docker-compose {self.docker_ssh} -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.yml -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml --project-name={self.PROJECT_NAME} run -d --service-ports --name={container}_new {service}' ) CLI.info(f'Renaming old container [{container}_old]...') if container in self.get_containers(): os.system( f'docker {self.docker_ssh} container rename {container} {container}_old' ) else: CLI.info(f'{container}_old was not running') CLI.info(f'Renaming new container [{container}]...') os.system( f'docker {self.docker_ssh} container rename {container}_new {container}' ) step += 1 CLI.step(step, steps, 'Collecting static files') os.system( f'docker {self.docker_ssh} exec -i {self.CONTAINER_APP} python manage.py collectstatic --noinput --verbosity 0' ) step += 1 CLI.step(step, steps, 'Reloading webserver...') os.system( f'docker {self.docker_ssh} exec -it {self.CONTAINER_WEBSERVER} {self.WEBSERVER} -s reload' ) step += 1 CLI.step( step, steps, f'Stopping old zero downtime services: {zero_downtime_services}') for service in zero_downtime_services: container = self.get_container_name(service) if container in self.get_containers(): CLI.info(f'Stopping old container [{container}_old]...') os.system( f'docker {self.docker_ssh} container stop {container}_old') CLI.info(f'Removing old container [{container}_old]...') os.system( f'docker {self.docker_ssh} container rm {container}_old') else: CLI.info(f'{container}_old was not running') step += 1 CLI.step(step, steps, f'Restart services: {restart_services}') for service in restart_services: container = self.get_container_name(service) CLI.underline(f'Recreating {service} container ({container})...') if container in self.get_containers(): CLI.info(f'Stopping container [{container}]...') os.system( f'docker {self.docker_ssh} container stop {container}') CLI.info(f'Removing container [{container}]...') os.system(f'docker {self.docker_ssh} container rm {container}') CLI.info(f'Creating new container [{container}]...') os.system( f'docker-compose {self.docker_ssh} -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.yml -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml --project-name={self.PROJECT_NAME} run -d --service-ports --name={container} {service}' ) else: CLI.info(f'{container} was not running')
def exec(self, params): container, command = params.split(' ', maxsplit=1) CLI.info(f'Executing command "{command}" in container {container}...') os.system(f'docker {self.docker_ssh} exec -it {container} {command}')
def psql(self): CLI.info('Starting psql...') env = self.load_environment() os.system( f'docker {self.docker_ssh} exec -it {self.CONTAINER_DB} psql -h {env["POSTGRES_HOST"]} -U {env["POSTGRES_USER"]} -d {env["POSTGRES_DBNAME"]} -W' )
def clean(self): # todo clean on all nodes CLI.info('Cleaning...') steps = 1 CLI.step(1, steps, 'Prune Docker images and volumes') os.system(f'docker {self.docker_ssh} system prune --volumes --force')
def ssh(self, params): CLI.info('Logging to container...') os.system(f'docker {self.docker_ssh} exec -it {params} /bin/sh')
def shell(self): CLI.info('Connecting to Django shell...') os.system( f'docker {self.docker_ssh} exec -i {self.CONTAINER_APP} python manage.py shell' )
def pull(self): CLI.info('Pulling docker image...') os.system( f'docker-compose {self.docker_ssh} -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.yml -f {self.configs_path}/docker/{self.COMPOSE_PREFIX}.{self.environment_id}.yml pull' )
def reload_webserver(self): CLI.info('Reloading webserver...') os.system( f'docker {self.docker_ssh} exec -it {self.CONTAINER_WEBSERVER} {self.WEBSERVER} -s reload' )
def upload(self, context='services'): CLI.info('Uploading...') steps = 1 if context == 'services': CLI.step( 1, steps, 'Uploading configs for context "services" [webserver, cache, htpasswd]' ) elif context == 'compose': CLI.step( 1, steps, 'Uploading configs for context "compose" [docker compose configs and environment]' ) elif context == 'mantis': CLI.step(1, steps, 'Uploading configs for mantis [mantis.json]') else: CLI.error(f'Unknown context "{context}"') if self.environment_id == 'dev': print('Skipping for dev...') elif self.mode == 'host': CLI.warning( 'Not uploading due to host mode! Be sure your configs on host are up to date!' ) else: if context == 'services': os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {self.cache_config} {self.user}@{self.host}:/home/{self.user}/public_html/web/configs/{self.CACHE}/' ) os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {self.webserver_config} {self.user}@{self.host}:/home/{self.user}/public_html/web/configs/{self.WEBSERVER}/' ) os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {self.webserver_config_proxy} {self.user}@{self.host}:/etc/nginx/conf.d/proxy/' ) os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {self.htpasswd} {self.user}@{self.host}:/etc/nginx/conf.d/' ) elif context == 'mantis': os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {self.config_file} {self.user}@{self.host}:/home/{self.user}/public_html/web/configs/' ) elif context == 'compose': os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {self.environment_file} {self.user}@{self.host}:/home/{self.user}/public_html/web/configs/environments/' ) for config in self.compose_configs: os.system( f'rsync -arvz -e \'ssh -p {self.port}\' -rvzh --progress {config} {self.user}@{self.host}:/home/{self.user}/public_html/web/configs/docker/' )
def execute(manager, command, params=None): if manager.environment_id is None: CLI.error('Missing environment') else: manager_method = { '--build': 'build', '-b': 'build', '--push': 'push', '--pull': 'pull', '-p': 'pull', '--upload': 'upload', '--upload-docker-configs': 'upload_docker_configs', '-u': 'upload', '--reload': 'reload', '--restart': 'restart', '--deploy': 'deploy', '-d': 'deploy', '--stop': 'stop', '--start': 'start', '--clean': 'clean', '-c': 'clean', '--remove': 'remove', '--reload-webserver': 'reload_webserver', '--restart-proxy': 'restart_proxy', '--status': 'status', '-s': 'status', '--networks': 'networks', '-n': 'networks', '--logs': 'logs', '-l': 'logs', '--shell': 'shell', '--ssh': 'ssh', '--manage': 'manage', '--exec': 'exec', '--psql': 'psql', '--pg-dump': 'pg_dump', '--pg-restore': 'pg_restore', '--send-test-email': 'send_test_email', }.get(command) methods_with_params = [ 'build', 'ssh', 'exec', 'manage', 'pg_restore', 'start', 'stop', 'logs', 'remove', 'upload' ] if manager_method is None or not hasattr(manager, manager_method): CLI.error( f'Invalid command "{command}" \n\nUsage: mantis <ENVIRONMENT> ' '\n--no-ssh |' '\n--build/-b |' '\n--push |' '\n--pull/-p |' '\n--upload/-u | ' '\n--deploy/-d | ' '\n--stop | ' '\n--start | ' '\n--reload | ' '\n--restart | ' '\n--remove | ' '\n--clean/-c | ' '\n--status/-s | ' '\n--networks/-n | ' '\n--logs/-l | ' '\n--reload-webserver | ' '\n--restart-proxy | ' '\n--manage | ' '\n--shell | ' '\n--ssh | ' '\n--exec | ' '\n--psql | ' '\n--pg-dump | ' '\n--pg-restore | ' '\n--send-test-email') else: if manager_method in methods_with_params and params: getattr(manager, manager_method)(params) else: getattr(manager, manager_method)()
def manage(self, params): CLI.info('Django manage...') os.system( f'docker {self.docker_ssh} exec -ti {self.CONTAINER_APP} python manage.py {params}' )