Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add start and stop commands #21

Merged
merged 6 commits into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 12 additions & 28 deletions bubbles/commands/deploy.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
import os
import subprocess
import time
from typing import Callable

from bubbles.commands import (
PROCESS_CHECK_COUNT,
PROCESS_CHECK_SLEEP_TIME,
SERVICES,
get_service_name,
)
from bubbles.config import PluginManager, COMMAND_PREFIXES
from bubbles.service_utils import verify_service_up, say_code
from bubbles.utils import get_branch_head


def _deploy_service(service: str, say: Callable) -> None:
say(f"Deploying {service} to production. This may take a moment...")
os.chdir(f"/data/{service}")

def saycode(command):
say(f"```{command.decode().strip()}```")

def migrate():
say("Migrating models...")
saycode(subprocess.check_output([PYTHON, "manage.py", "migrate"]))
say_code(say, subprocess.check_output([PYTHON, "manage.py", "migrate"]))

def pull_from_git():
say("Pulling latest code...")
saycode(subprocess.check_output(f"git pull origin {get_branch_head()}".split()))
say_code(
say, subprocess.check_output(f"git pull origin {get_branch_head()}".split())
)

def install_deps():
say("Installing dependencies...")
saycode(subprocess.check_output(["poetry", "install", "--no-dev"]))
say_code(say, subprocess.check_output(["poetry", "install", "--no-dev"]))

def bootstrap_site():
say("Verifying that initial data is present...")
Expand All @@ -42,7 +39,7 @@ def collect_static():
[PYTHON, "manage.py", "collectstatic", "--noinput", "-v", "0"]
)
if result:
saycode(result)
say_code(say, result)

def revert_and_recover(loc):
git_response = (
Expand All @@ -55,32 +52,19 @@ def revert_and_recover(loc):
say(f"Rolling back to previous state:\n```\n{git_response}```")
subprocess.check_output(["sudo", "systemctl", "restart", get_service_name(loc)])

def verify_service_up(loc):
say(
f"Pausing for {PROCESS_CHECK_SLEEP_TIME}s to verify that {loc} restarted"
f" correctly..."
)
try:
for attempt in range(PROCESS_CHECK_COUNT):
time.sleep(PROCESS_CHECK_SLEEP_TIME / PROCESS_CHECK_COUNT)
subprocess.check_call(
["systemctl", "is-active", "--quiet", get_service_name(loc)]
)
say(f"Check {attempt + 1}/{PROCESS_CHECK_COUNT} complete!")
say("Restarted successfully!")
except subprocess.CalledProcessError:
revert_and_recover(loc)

def restart_service(loc):
say(f"Restarting service for {loc}...")
systemctl_response = subprocess.check_output(
["sudo", "systemctl", "restart", get_service_name(loc)]
)
if systemctl_response.decode().strip() != "":
say("Something went wrong and could not restart.")
saycode(systemctl_response)
say_code(say, systemctl_response)
else:
verify_service_up(loc)
if verify_service_up(say, loc):
say("Restarted successfully!")
else:
revert_and_recover(loc)

pull_from_git()
try:
Expand Down
40 changes: 11 additions & 29 deletions bubbles/commands/restart.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,38 @@
import subprocess
import time
from typing import Callable

from bubbles.commands import (
PROCESS_CHECK_COUNT,
PROCESS_CHECK_SLEEP_TIME,
SERVICES,
get_service_name,
)
from bubbles.config import PluginManager, COMMAND_PREFIXES
from bubbles.service_utils import say_code, verify_service_up


def _restart_service(service: str, say: Callable) -> None:
say(f"Restarting {service} in production. This may take a moment...")

def saycode(command):
say(f"```{command.decode().strip()}```")

def verify_service_up(loc):
say(
f"Pausing for {PROCESS_CHECK_SLEEP_TIME}s to verify that {loc} restarted"
f" correctly..."
)
try:
for attempt in range(PROCESS_CHECK_COUNT):
time.sleep(PROCESS_CHECK_SLEEP_TIME / PROCESS_CHECK_COUNT)
subprocess.check_call(
["systemctl", "is-active", "--quiet", get_service_name(loc)]
)
say(f"Check {attempt + 1}/{PROCESS_CHECK_COUNT} complete!")
say("Restarted successfully!")
except subprocess.CalledProcessError:
say(
f"{loc} is not responding. Cannot recover from here -- please check the"
f" logs for more information."
)

def restart_service(loc):
say(f"Restarting service for {loc}...")
systemctl_response = subprocess.check_output(
["sudo", "systemctl", "restart", get_service_name(loc)]
)
if systemctl_response.decode().strip() != "":
say("Something went wrong and could not restart.")
saycode(systemctl_response)
say_code(say, systemctl_response)
else:
verify_service_up(loc)
if verify_service_up(say, loc):
say("Restarted successfully!")
else:
say(
f"{loc} is not responding. Cannot recover from here -- please check the"
f" logs for more information."
)

restart_service(service)


def deploy(payload):
def restart(payload):
args = payload.get("text").split()
say = payload["extras"]["say"]

Expand Down Expand Up @@ -81,7 +63,7 @@ def deploy(payload):


PluginManager.register_plugin(
deploy,
restart,
r"restart ?(.+)",
help=f"!restart [{', '.join(SERVICES)}] - restarts the requested bot.",
interactive_friendly=False,
Expand Down
66 changes: 66 additions & 0 deletions bubbles/commands/start.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import subprocess
from typing import Callable

from bubbles.commands import (
SERVICES,
get_service_name,
)
from bubbles.config import PluginManager, COMMAND_PREFIXES
from bubbles.service_utils import say_code, verify_service_up


def _start_service(service: str, say: Callable) -> None:
say(f"Starting {service} in production...")

systemctl_response = subprocess.check_output(
["sudo", "systemctl", "start", get_service_name(service)]
)
if systemctl_response.decode().strip() != "":
say("Something went wrong and could not start.")
say_code(say, systemctl_response)
else:
if verify_service_up(say, service):
say("Started successfully!")
else:
say(
f"{service} is not responding. Cannot recover from here -- please check the"
f" logs for more information."
)


def start(payload):
args = payload.get("text").split()
say = payload["extras"]["say"]

if len(args) > 1:
if args[0] in COMMAND_PREFIXES:
args.pop(0)

if len(args) == 1:
say(
"Need a service to start in production. Usage: @bubbles start [service]"
" -- example: `@bubbles start tor`"
)
return

service = args[1].lower().strip()
if service not in SERVICES:
say(
f"Received a request to start {args[1]}, but I'm not sure what that is.\n\n"
f"Available options: {', '.join(SERVICES)}"
)
return

if service == "all":
for system in [_ for _ in SERVICES if _ != "all"]:
_start_service(system, say)
else:
_start_service(service, say)


PluginManager.register_plugin(
start,
r"start ?(.+)",
help=f"!start [{', '.join(SERVICES)}] - starts the requested bot.",
interactive_friendly=False,
)
60 changes: 60 additions & 0 deletions bubbles/commands/stop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import subprocess
from typing import Callable

from bubbles.commands import (
SERVICES,
get_service_name,
)
from bubbles.config import PluginManager, COMMAND_PREFIXES
from bubbles.service_utils import say_code


def _stop_service(service: str, say: Callable) -> None:
say(f"Stopping {service} in production...")

systemctl_response = subprocess.check_output(
["sudo", "systemctl", "stop", get_service_name(service)]
)
if systemctl_response.decode().strip() != "":
say("Something went wrong and could not stop.")
say_code(say, systemctl_response)

say(f"Stopped {service}.")


def stop(payload):
args = payload.get("text").split()
say = payload["extras"]["say"]

if len(args) > 1:
if args[0] in COMMAND_PREFIXES:
args.pop(0)

if len(args) == 1:
say(
"Need a service to stop in production. Usage: @bubbles stop [service]"
" -- example: `@bubbles stop tor`"
)
return

service = args[1].lower().strip()
if service not in SERVICES:
say(
f"Received a request to stop {args[1]}, but I'm not sure what that is.\n\n"
f"Available options: {', '.join(SERVICES)}"
)
return

if service == "all":
for system in [_ for _ in SERVICES if _ != "all"]:
_stop_service(system, say)
else:
_stop_service(service, say)


PluginManager.register_plugin(
stop,
r"stop ?(.+)",
help=f"!stop [{', '.join(SERVICES)}] - stops the requested bot.",
interactive_friendly=False,
)
31 changes: 31 additions & 0 deletions bubbles/service_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import subprocess
import time

from bubbles import PROCESS_CHECK_SLEEP_TIME, PROCESS_CHECK_COUNT, get_service_name


def say_code(say, message: bytes) -> None:
"""Format a byte message as code and send it."""
say(f"```{message.decode().strip()}```")


def verify_service_up(say, service) -> bool:
"""Periodically check that the given system is still up.

:returns: True, if the service is still up, else False.
"""
say(
f"Pausing for {PROCESS_CHECK_SLEEP_TIME}s to verify that {service} restarted"
f" correctly..."
)
try:
for attempt in range(PROCESS_CHECK_COUNT):
time.sleep(PROCESS_CHECK_SLEEP_TIME / PROCESS_CHECK_COUNT)
subprocess.check_call(
["systemctl", "is-active", "--quiet", get_service_name(service)]
)
say(f"Check {attempt + 1}/{PROCESS_CHECK_COUNT} complete!")

return True
except subprocess.CalledProcessError:
return False