mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 03:53:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			172 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import os
 | |
| import subprocess
 | |
| import time
 | |
| from pathlib import Path
 | |
| from typing import List
 | |
| 
 | |
| import digitalocean
 | |
| import zulip
 | |
| from requests.adapters import HTTPAdapter
 | |
| from urllib3.util import Retry
 | |
| 
 | |
| manager = digitalocean.Manager(token=os.environ["DIGITALOCEAN_API_KEY"])
 | |
| # We just temporarily create the client now, to validate that we can
 | |
| # auth to the server; reusing it after the whole install fails because
 | |
| # the connection has been half-closed in a way that breaks it.
 | |
| zulip.Client()
 | |
| TEST_DROPLET_SUBDOMAIN = "do"
 | |
| 
 | |
| 
 | |
| def generate_ssh_keys() -> None:
 | |
|     subprocess.check_call(
 | |
|         ["ssh-keygen", "-f", str(Path.home()) + "/.ssh/id_ed25519", "-P", "", "-t", "ed25519"]
 | |
|     )
 | |
| 
 | |
| 
 | |
| def get_public_ssh_key() -> str:
 | |
|     try:
 | |
|         with open(str(Path.home()) + "/.ssh/id_ed25519.pub") as f:
 | |
|             return f.read()
 | |
|     except FileNotFoundError:
 | |
|         return ""
 | |
| 
 | |
| 
 | |
| def sleep_until_droplet_action_is_completed(
 | |
|     droplet: digitalocean.Droplet, action_type: str
 | |
| ) -> None:
 | |
|     incomplete = True
 | |
|     while incomplete:
 | |
|         for action in droplet.get_actions():
 | |
|             action.load()
 | |
|             print(f"...[{action.type}]: {action.status}")
 | |
|             if action.type == action_type and action.status == "completed":
 | |
|                 incomplete = False
 | |
|                 break
 | |
|         if incomplete:
 | |
|             time.sleep(5)
 | |
| 
 | |
|     # Sometimes the droplet does not yet have an .ip_address value
 | |
|     # (the attribute is None) after .load()ing the droplet. We cannot
 | |
|     # proceed without the IP, so we wait in a loop until the IP is
 | |
|     # returned to us.
 | |
|     while True:
 | |
|         droplet.load()
 | |
|         if droplet.ip_address:
 | |
|             break
 | |
|         time.sleep(5)
 | |
| 
 | |
| 
 | |
| def set_api_request_retry_limits(api_object: digitalocean.baseapi.BaseAPI) -> None:
 | |
|     retry = Retry(connect=5, read=5, backoff_factor=0.1)
 | |
|     adapter = HTTPAdapter(max_retries=retry)
 | |
|     api_object._session.mount("https://", adapter)
 | |
| 
 | |
| 
 | |
| def create_droplet(
 | |
|     name: str, ssh_keys: List[str], image: str = "ubuntu-20-04-x64"
 | |
| ) -> digitalocean.Droplet:
 | |
|     droplet = digitalocean.Droplet(
 | |
|         token=manager.token,
 | |
|         name=name,
 | |
|         region="nyc3",
 | |
|         size_slug="s-1vcpu-2gb",
 | |
|         image=image,
 | |
|         backups=False,
 | |
|         ssh_keys=ssh_keys,
 | |
|         tags=["github-action", "temporary"],
 | |
|     )
 | |
|     set_api_request_retry_limits(droplet)
 | |
|     droplet.create()
 | |
|     sleep_until_droplet_action_is_completed(droplet, "create")
 | |
|     return droplet
 | |
| 
 | |
| 
 | |
| def create_ssh_key(name: str, public_key: str) -> digitalocean.SSHKey:
 | |
|     action_public_ssh_key_object = digitalocean.SSHKey(
 | |
|         name=name, public_key=public_key, token=manager.token
 | |
|     )
 | |
|     set_api_request_retry_limits(action_public_ssh_key_object)
 | |
|     action_public_ssh_key_object.create()
 | |
|     return action_public_ssh_key_object
 | |
| 
 | |
| 
 | |
| def create_snapshot(droplet: digitalocean.Droplet, snapshot_name: str) -> None:
 | |
|     droplet.take_snapshot(snapshot_name, power_off=True)
 | |
|     droplet.load()
 | |
|     sleep_until_droplet_action_is_completed(droplet, "snapshot")
 | |
| 
 | |
| 
 | |
| def create_dns_records(droplet: digitalocean.Droplet) -> None:
 | |
|     domain = digitalocean.Domain(token=manager.token, name="oneclick.zulip.dev")
 | |
|     set_api_request_retry_limits(domain)
 | |
|     domain.load()
 | |
| 
 | |
|     oneclick_test_app_record_names = [TEST_DROPLET_SUBDOMAIN, f"*.{TEST_DROPLET_SUBDOMAIN}"]
 | |
|     for record in domain.get_records():
 | |
|         if (
 | |
|             record.name in oneclick_test_app_record_names
 | |
|             and record.domain == "oneclick.zulip.dev"
 | |
|             and record.type == "A"
 | |
|         ):
 | |
|             record.destroy()
 | |
| 
 | |
|     domain.load()
 | |
|     for record_name in oneclick_test_app_record_names:
 | |
|         domain.create_new_domain_record(type="A", name=record_name, data=droplet.ip_address)
 | |
| 
 | |
| 
 | |
| def setup_one_click_app_installer(droplet: digitalocean.Droplet) -> None:
 | |
|     subprocess.check_call(
 | |
|         [
 | |
|             "fab",
 | |
|             "build_image",
 | |
|             "-H",
 | |
|             droplet.ip_address,
 | |
|             "--keepalive",
 | |
|             "5",
 | |
|             "--connection-attempts",
 | |
|             "10",
 | |
|         ],
 | |
|         cwd="marketplace-partners/marketplace_docs/templates/Fabric",
 | |
|     )
 | |
| 
 | |
| 
 | |
| def send_message(content: str) -> None:
 | |
|     request = {
 | |
|         "type": "stream",
 | |
|         "to": os.environ["ONE_CLICK_ACTION_STREAM"],
 | |
|         "topic": "digitalocean installer",
 | |
|         "content": content,
 | |
|     }
 | |
|     zulip.Client().send_message(request)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     release_version = os.environ["RELEASE_VERSION"]
 | |
| 
 | |
|     generate_ssh_keys()
 | |
|     action_public_ssh_key_object = create_ssh_key(
 | |
|         f"oneclickapp-{release_version}-image-generator-public-key", get_public_ssh_key()
 | |
|     )
 | |
| 
 | |
|     image_generator_droplet = create_droplet(
 | |
|         f"oneclickapp-{release_version}-image-generator", [action_public_ssh_key_object]
 | |
|     )
 | |
| 
 | |
|     setup_one_click_app_installer(image_generator_droplet)
 | |
| 
 | |
|     oneclick_image_name = f"oneclickapp-{release_version}"
 | |
|     create_snapshot(image_generator_droplet, oneclick_image_name)
 | |
|     snapshot = image_generator_droplet.get_snapshots()[0]
 | |
|     send_message(f"One click app image `{oneclick_image_name}` created.")
 | |
| 
 | |
|     image_generator_droplet.destroy()
 | |
|     action_public_ssh_key_object.destroy()
 | |
| 
 | |
|     test_droplet_name = f"oneclickapp-{release_version}-test"
 | |
|     test_droplet = create_droplet(test_droplet_name, manager.get_all_sshkeys(), image=snapshot.id)
 | |
|     create_dns_records(test_droplet)
 | |
|     send_message(
 | |
|         f"Test droplet `{test_droplet_name}` created. SSH as root to {TEST_DROPLET_SUBDOMAIN}.oneclick.zulip.dev for testing."
 | |
|     )
 |