0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-30 19:41:46 +01:00
posthog/bin/hobby-ci.py

168 lines
5.5 KiB
Python

#!/usr/local/bin/python
import datetime
import os
import random
import re
import signal
import string
import sys
import time
import digitalocean
import requests
letters = string.ascii_lowercase
random_bit = "".join(random.choice(letters) for i in range(4))
name = f"do-ci-hobby-deploy-{random_bit}"
region = "sfo3"
image = "ubuntu-22-04-x64"
size = "s-4vcpu-8gb"
release_tag = "latest-release"
branch_regex = re.compile("release-*.*")
branch = sys.argv[1]
if branch_regex.match(branch):
release_tag = f"{branch}-unstable"
hostname = f"{name}.posthog.cc"
user_data = (
f"#!/bin/bash \n"
"mkdir hobby \n"
"cd hobby \n"
"sed -i \"s/#\\$nrconf{restart} = 'i';/\\$nrconf{restart} = 'a';/g\" /etc/needrestart/needrestart.conf \n"
"git clone https://github.com/PostHog/posthog.git \n"
"cd posthog \n"
f"git checkout {branch} \n"
"cd .. \n"
f"chmod +x posthog/bin/deploy-hobby \n"
f"./posthog/bin/deploy-hobby {release_tag} {hostname} 1 \n"
)
token = os.getenv("DIGITALOCEAN_TOKEN")
class HobbyTester:
def __init__(self, domain, droplet, record):
# Placeholders for DO resources
self.domain = domain
self.droplet = droplet
self.record = record
@staticmethod
def block_until_droplet_is_started(droplet):
actions = droplet.get_actions()
up = False
while not up:
for action in actions:
action.load()
if action.status == "completed":
up = True
print(action.status)
else:
print("Droplet not booted yet - waiting a bit")
time.sleep(5)
@staticmethod
def get_public_ip(droplet):
ip = None
while not ip:
time.sleep(1)
droplet.load()
ip = droplet.ip_address
print(f"Public IP found: {ip}") # type: ignore
return ip
@staticmethod
def create_droplet(ssh_enabled=False):
keys = None
if ssh_enabled:
manager = digitalocean.Manager(token=token)
keys = manager.get_all_sshkeys()
droplet = digitalocean.Droplet(
token=token,
name=name,
region=region,
image=image,
size_slug=size,
user_data=user_data,
ssh_keys=keys,
tags=["ci"],
)
droplet.create()
return droplet
@staticmethod
def wait_for_instance(hostname, timeout=20, retry_interval=15):
# timeout in minutes
# return true if success or false if failure
print("Attempting to reach the instance")
print(f"We will time out after {timeout} minutes")
url = f"https://{hostname}/_health"
start_time = datetime.datetime.now()
while datetime.datetime.now() < start_time + datetime.timedelta(minutes=timeout):
try:
# verify is set False here because we are hitting the staging endoint for Let's Encrypt
# This endpoint doesn't have the strict rate limiting that the production endpoint has
# This mitigates the chances of getting throttled or banned
r = requests.get(url, verify=False)
except Exception as e:
print(f"Host is probably not up. Received exception\n{e}")
time.sleep(retry_interval)
continue
if r.status_code == 200:
print("Success - received heartbeat from the instance")
return True
print("Instance not ready - sleeping")
time.sleep(retry_interval)
print("Failure - we timed out before receiving a heartbeat")
return False
@staticmethod
def destroy_environment(droplet, domain, record, retries=3):
print("Destroying the droplet")
attempts = 0
while attempts <= retries:
attempts += 1
try:
droplet.destroy()
break
except Exception as e:
print(f"Could not destroy droplet because\n{e}")
print("Destroying the DNS entry")
attempts = 0
while attempts <= retries:
attempts += 1
try:
domain.delete_domain_record(id=record["domain_record"]["id"])
break
except Exception as e:
print(f"Could not destroy the dns entry because\n{e}")
def handle_sigint(self):
self.destroy_environment(self.droplet, self.domain, self.record)
def main():
print("Creating droplet on Digitalocean for testing Hobby Deployment")
droplet = HobbyTester.create_droplet(ssh_enabled=True)
HobbyTester.block_until_droplet_is_started(droplet)
public_ip = HobbyTester.get_public_ip(droplet)
domain = digitalocean.Domain(token=token, name="posthog.cc")
record = domain.create_new_domain_record(type="A", name=name, data=public_ip)
hobby_tester = HobbyTester(domain, droplet, record)
signal.signal(signal.SIGINT, hobby_tester.handle_sigint) # type: ignore
signal.signal(signal.SIGHUP, hobby_tester.handle_sigint) # type: ignore
print("Instance has started. You will be able to access it here after PostHog boots (~15 minutes):")
print(f"https://{hostname}")
health_success = HobbyTester.wait_for_instance(hostname)
HobbyTester.destroy_environment(droplet, domain, record)
if health_success:
print("We succeeded")
exit()
else:
print("We failed")
exit(1)
if __name__ == "__main__":
main()