From 898e9d9291639108319057d0ab7d2bb2d55a42d3 Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Tue, 3 Oct 2023 17:38:19 -0400 Subject: [PATCH 01/12] Init Vultr API commit --- Automation/psi_vultr.py | 157 ++++++++++++++++++++++++++++++ Automation/vultr/README.md | 1 + Automation/vultr/__init__.py | 0 Automation/vultr/vultr.py | 180 +++++++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 Automation/psi_vultr.py create mode 100644 Automation/vultr/README.md create mode 100644 Automation/vultr/__init__.py create mode 100644 Automation/vultr/vultr.py diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py new file mode 100644 index 00000000..c33c3871 --- /dev/null +++ b/Automation/psi_vultr.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# +# Copyright (c) 2021, Psiphon Inc. +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import os +import sys +import json +import random +import string +import time +import psi_ssh +import psi_utils + +# Import Vultr Python Library +# Requirement: vultr directory with library file +from vultr import vultr + +# VARIABLE +TCS_BASE_IMAGE_NAME = '' +TCS_VULTR_DEFAULT_PLAN = 'vc2-2c-4gb' # default 2vCore 4G RAM 'vc2-2c-4gb', Sao Paulo 'vc2-2c-4gb-sc1' + +#============================================================================== +### +# +# General API Interaction functions +# +### +class PsiVultr: + def __init__(self, vultr_account, debug=False): + self.api_key = vultr_account.api_key + self.plan = TCS_VULTR_DEFAULT_PLAN + self.client = vultr.Vultr(api_key=self.api_key) + + def get_region(self, select_region=None): + # Load region from API + # region_id required for create_instance + all_regions = self.client.list_regions() + if select_region == None: + regions = [r for r in all_regions if r['country'] == select_region] + else: + regions = all_regions + + region = random.choice(regions) + + return region['id'], region['country'], region['city'] + + def create_instance(self, host_id): + # Launch Instnace + self.client.create_instance(region=region, plan=self.plan) + + def remove_instance(self, instance_id): + # Delete instance + pass + +### +# +# Server side SSH Interaction functions (Migrated from old code) +# +### +def refresh_credentials(oracle_account, ip_address, new_root_password, new_stats_password, stats_username): + ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, + 'root', None, None, + host_auth_key=oracle_account.base_image_rsa_private_key) + try: + ssh.exec_command('echo "root:%s" | chpasswd' % (new_root_password,)) + ssh.exec_command('useradd -M -d /var/log -s /bin/sh -g adm %s' % (stats_username)) + ssh.exec_command('echo "%s:%s" | chpasswd' % (stats_username, new_stats_password)) + ssh.exec_command('rm /etc/ssh/ssh_host_*') + ssh.exec_command('rm -rf /root/.ssh') + ssh.exec_command('export DEBIAN_FRONTEND=noninteractive && dpkg-reconfigure openssh-server') + return ssh.exec_command('cat /etc/ssh/ssh_host_rsa_key.pub') + finally: + ssh.close() + +def set_allowed_users(oracle_account, ip_address, stats_username): + ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, + 'root', None, None, + host_auth_key=oracle_account.base_image_rsa_private_key) + try: + user_exists = ssh.exec_command('grep %s /etc/ssh/sshd_config' % stats_username) + if not user_exists: + ssh.exec_command('sed -i "s/^AllowUsers.*/& %s/" /etc/ssh/sshd_config' % stats_username) + ssh.exec_command('service ssh restart') + finally: + ssh.close() + +def get_host_name(oracle_account, ip_address): + # Note: using base image credentials; call before changing credentials + ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, + 'root',None, None, + host_auth_key=oracle_account.base_image_rsa_private_key) + try: + return ssh.exec_command('hostname').strip() + finally: + ssh.close() + +def set_host_name(oracle_account, ip_address, new_hostname): + # Note: hostnamectl is for systemd servers + ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, + 'root', None, None, + host_auth_key=oracle_account.base_image_rsa_private_key) + try: + ssh.exec_command('hostnamectl set-hostname %s' % new_hostname) + finally: + ssh.close() + +def add_swap_file(oracle_account, ip_address): + ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, 'root', None, None, host_auth_key=oracle_account.base_image_rsa_private_key) + try: + has_swap = ssh.exec_command('grep swap /etc/fstab') + if not has_swap: + ssh.exec_command('dd if=/dev/zero of=/swapfile bs=1024 count=1048576 && mkswap /swapfile && chown root:root /swapfile && chmod 0600 /swapfile') + ssh.exec_command('echo "/swapfile swap swap defaults 0 0" >> /etc/fstab') + ssh.exec_command('swapon -a') + finally: + ssh.close() + +### +# +# Main function +# +### +def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): + + vultr_api = PsiVultr(vultr_account) # Use API interface + + try: + region = vultr_api.get_region() + except Exception as ex: + if instance: + vultr_api.remove_instance(instance.id) + raise ex + + return (host_id, is_TCS, 'NATIVE' if is_TCS else None, None, + instance.id, instance_ip_address, + oracle_account.base_image_ssh_port, 'root', new_root_password, + ' '.join(new_host_public_key.split(' ')[:2]), + new_stats_username, new_stats_password, + datacenter_name, region, egress_ip_address if multi_ip else None, instance_internal_ip_address) + +if __name__ == '__main__': + print(launch_new_server) diff --git a/Automation/vultr/README.md b/Automation/vultr/README.md new file mode 100644 index 00000000..6d0c938b --- /dev/null +++ b/Automation/vultr/README.md @@ -0,0 +1 @@ +The code downloaded from [Github `cssnr/vultr-python`](https://github.com/cssnr/vultr-python/blob/master/vultr.py) with some modifications diff --git a/Automation/vultr/__init__.py b/Automation/vultr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Automation/vultr/vultr.py b/Automation/vultr/vultr.py new file mode 100644 index 00000000..ca5d79fd --- /dev/null +++ b/Automation/vultr/vultr.py @@ -0,0 +1,180 @@ +import os +import typing +import requests + +class Vultr(object): + API_URL = 'https://api.vultr.com/v2/' + + def __init__(self, api_key: typing.Union[str, None]): + """ + :param str api_key: Vultr API Key or VULTR_API_KEY environment variable + """ + self.api_key = api_key or os.getenv('VULTR_API_KEY') + self.s = requests.session() + if self.api_key: + self.s.headers.update({'Authorization': f'Bearer {self.api_key}'}) + + # Remove all extra functions + # Only leave necessary functions + def list_os(self): + url = f'{self.API_URL}/os' + return self._get(url)['os'] + + def list_plans(self): + url = f'{self.API_URL}/plans' + return self._get(url)['plans'] + + def list_regions(self): + url = f'{self.API_URL}/regions' + return self._get(url)['regions'] + + def list_instances(self): + url = f'{self.API_URL}/instances' + return self._get(url)['instances'] + + def get_instance(self, instance: typing.Union[str, dict]): + instance_id = self._get_obj_key(instance) + url = f'{self.API_URL}/instances/{instance_id}' + return self._get(url)['instance'] + + def create_instance(self, region: str, plan: str, **kwargs): + data = {'region': region, 'plan': plan} + data.update(kwargs) + url = f'{self.API_URL}/instances' + return self._post(url, data)['instance'] + + def update_instance(self, instance: typing.Union[str, dict], **kwargs): + instance_id = self._get_obj_key(instance) + url = f'{self.API_URL}/instances/{instance_id}' + return self._patch(url, kwargs)['instance'] + + def delete_instance(self, instance: typing.Union[str, dict]): + instance_id = self._get_obj_key(instance) + url = f'{self.API_URL}/instances/{instance_id}' + return self._delete(url) + + def list_keys(self): + url = f'{self.API_URL}/ssh-keys' + return self._get(url)['ssh_keys'] + + def get_key(self, key: typing.Union[str, dict]): + key_id = self._get_obj_key(key) + url = f'{self.API_URL}/ssh-keys/{key_id}' + return self._get(url)['ssh_key'] + + def create_key(self, name: str, key: str, **kwargs): + data = {'name': name, 'ssh_key': key} + data.update(kwargs) + url = f'{self.API_URL}/ssh-keys' + return self._post(url, data)['ssh_key'] + + def update_key(self, key: typing.Union[str, dict], **kwargs): + key_id = self._get_obj_key(key) + url = f'{self.API_URL}/ssh-keys/{key_id}' + return self._patch(url, kwargs)['ssh_key'] + + def delete_key(self, key: typing.Union[str, dict]): + key_id = self._get_obj_key(key) + url = f'{self.API_URL}/ssh-keys/{key_id}' + return self._delete(url) + + def list_scripts(self): + url = f'{self.API_URL}/startup-scripts' + return self._get(url)['startup_scripts'] + + def get_script(self, script: typing.Union[str, dict]): + script_id = self._get_obj_key(script) + url = f'{self.API_URL}/startup-scripts/{script_id}' + return self._get(url)['startup_script'] + + def create_script(self, name: str, script: str, **kwargs): + data = {'name': name, 'script': script} + data.update(kwargs) + url = f'{self.API_URL}/startup-scripts' + return self._post(url, data)['startup_script'] + + def update_script(self, script: typing.Union[str, dict], **kwargs): + script_id = self._get_obj_key(script) + url = f'{self.API_URL}/startup-scripts/{script_id}' + return self._patch(url, kwargs)['startup_script'] + + def delete_script(self, script: typing.Union[str, dict]): + script_id = self._get_obj_key(script) + url = f'{self.API_URL}/startup-scripts/{script_id}' + return self._delete(url) + + def list_ipv4(self, instance: typing.Union[str, dict]): + instance_id = self._get_obj_key(instance) + url = f'{self.API_URL}/instances/{instance_id}/ipv4' + return self._get(url)['ipv4s'] + + def create_ipv4(self, instance: typing.Union[str, dict], **kwargs): + instance_id = self._get_obj_key(instance) + url = f'{self.API_URL}/instances/{instance_id}/ipv4' + return self._post(url, kwargs)['ipv4'] + + def delete_ipv4(self, instance: typing.Union[str, dict]): + instance_id = self._get_obj_key(instance) + url = f'{self.API_URL}/instances/{instance_id}/ipv4' + return self._delete(url) + + @staticmethod + def filter_keys(keys: list, name: str) -> dict: + try: + return next(d for d in keys if d['name'].lower() == name.lower()) + except StopIteration: + return {} + + @staticmethod + def filter_os(os_list: list, name: str) -> dict: + try: + return next(d for d in os_list if d['name'].lower() == name.lower()) + except StopIteration: + return {} + + @staticmethod + def filter_scripts(scripts: list, name: str) -> dict: + try: + return next(d for d in scripts if d['name'].lower() == name.lower()) + except StopIteration: + return {} + + @staticmethod + def filter_regions(regions: list, locations: list) -> list: + return [d for d in regions if d['id'] in locations] + + def _get(self, url): + r = self.s.get(url, timeout=10) + if not r.ok: + r.raise_for_status() + return r.json() + + def _post(self, url, data): + r = self.s.post(url, json=data, timeout=10) + if not r.ok: + r.raise_for_status() + return r.json() + + def _patch(self, url, data): + r = self.s.patch(url, json=data, timeout=10) + if not r.ok: + r.raise_for_status() + return r.json() + + def _delete(self, url): + r = self.s.delete(url, timeout=10) + if not r.ok: + r.raise_for_status() + return None + + @staticmethod + def _get_obj_key(obj, key='id'): + if isinstance(obj, str): + return obj + elif isinstance(obj, int): + return str(obj) + elif isinstance(obj, dict): + if key in obj: + return obj[key] + else: + raise ValueError(f'Unable to parse object: {key}') From 6246ad0a5862dbd4c100508d0de4660fe74501df Mon Sep 17 00:00:00 2001 From: iyoshinoya Date: Wed, 18 Oct 2023 14:57:58 -0400 Subject: [PATCH 02/12] new file: .psi_vultr.py.swp modified: psi_vultr.py --- Automation/.psi_vultr.py.swp | Bin 0 -> 1024 bytes Automation/new_servers.py | 17 ++++ Automation/psi_vultr.py | 180 +++++++++++++++++++++++++++++++---- 3 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 Automation/.psi_vultr.py.swp create mode 100644 Automation/new_servers.py diff --git a/Automation/.psi_vultr.py.swp b/Automation/.psi_vultr.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..a1031bb3198e59009ce9bcdd0fad794ad2e6294b GIT binary patch literal 1024 zcmYc?$V<%2S1{KzVn6})=?n~+mHEXPnR)q@i6~MSoXkqyl+-favRn)qRDpux%=og> WoRT8Ff=X1GQQl|>jE2BKga80RmJo#i literal 0 HcmV?d00001 diff --git a/Automation/new_servers.py b/Automation/new_servers.py new file mode 100644 index 00000000..446bd2b3 --- /dev/null +++ b/Automation/new_servers.py @@ -0,0 +1,17 @@ +import psi_ops +import psi_ssh +import sys +import os +import paramiko +path = "/home/iyoshinoya/psiphon/providers/" +csv_file = f'{path}hosts' +psinet = psi_ops.PsiphonNetwork.load(lock=False) +hosts = [host for host in psinet.get_hosts() if host.is_TCS] +df = open(csv_file, "w") +try: + for i in range(0,len(hosts)): + #df.write(hosts[i].id + "\t" + hosts[i].ip_address + "\t" + hosts[i].ssh_password + "\n") + df.write(hosts[i].id + "\t" + hosts[i].ip_address + "\t" + hosts[i].ssh_password + "\t" + hosts[i].provider + "\n") +except Exception as e: + print("Failed", hosts[i], str(e)) +df.close() diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index c33c3871..633b595f 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright (c) 2021, Psiphon Inc. +# Copyright (c) 2023, Psiphon Inc. # All rights reserved. # # This program is free software: you can redistribute it and/or modify @@ -42,6 +42,14 @@ ### class PsiVultr: def __init__(self, vultr_account, debug=False): + self.config = { + # ?? + } + self.api_key = vultr_account.api_key + self.plan = TCS_VULTR_DEFAULT_PLAN + self.client = vultr.Vultr(api_key=self.api_key) + + def reload(self): self.api_key = vultr_account.api_key self.plan = TCS_VULTR_DEFAULT_PLAN self.client = vultr.Vultr(api_key=self.api_key) @@ -59,9 +67,79 @@ def get_region(self, select_region=None): return region['id'], region['country'], region['city'] + def get_datacenter_names(self) : + datacenters = { + "ewr" : "VULT New Jersey, US", + "ord" : "VULT Chicago, US", + "dfw" : "VULT Dallas, US", + "sea" : "VULT Seattle, US", + "lax" : "VULT Los Angeles, US", + "atl" : "VULT Atlanta, US", + "ams" : "VULT Amsterdam, NL", + "lhr" : "VULT London, GB", + "fra" : "VULT Frankfurt, DE", + "sjc" : "VULT Silicon Valley, US", + "syd" : "VULT Sydney, AU", + "yto" : "VULT Toronto, CA", + "cdg" : "VULT Paris, FR", + "nrt" : "VULT Tokyo, JP", + "waw" : "VULT Warsaw, PL", + "mad" : "VULT Madrid, ES", + "icn" : "VULT Seoul, KR", + "mia" : "VULT Miami, US", + "sgp" : "VULT Singapore, SG", + "sto" : "VULT Stockholm, SE", + "mex" : "VULT Mexico City, MX", + "mel" : "VULT Melbourne, AU", + "bom" : "VULT Mumbai, IN", + "jnb" : "VULT Johannesburg, ZA", + "tlv" : "VULT Tel Aviv, IL", + "blr" : "VULT Bangalore, IN", + "del" : "VULT Delhi NCR, IN", + "scl" : "VULT Santiago, CL", + "itm" : "VULT Osaka, JP", + "man" : "VULT Manchester, GB", + "hnl" : "VULT Honolulu, US", #hnl and sao does not have vc2-2c-4gb available. + "sao" : "VULT S\u00e3o Paulo, BR" # this is a mistake/error as per their api: https://api.vultr.com/v2/regions; should be: Sao Paulo + } + + return datacenters.get(self.client["region"], '') + + def list_instances(self): + # TODO + all_instances = self.client.list_instances() + #pass + return all_instances + + def remove_instance(self, instance_id): + # TODO + pass + + def start_instance(self, instance_id): + # TODO + pass + + def stop_instance(self, instance_id): + # TODO + pass + + def restart_instance(self, instance_id): + # TODO + pass + def create_instance(self, host_id): # Launch Instnace - self.client.create_instance(region=region, plan=self.plan) + instance = self.client.create_instance( + region=region, + plan=self.plan, + label="Psiphon 3 Hosts Public Subnet", #host_id? + os_id=352, #352 -> Debian 10 x64 (buster) || 477 -> Debian 11 x64 (bullseye)" + user_data="QmFzZTY0IEV4YW1wbGUgRGF0YQ==", #The user-supplied, base64 encoded user data to attach to this instance. -> random.choice(string.ascii_lowercase) for x in range(24) + backups="disabled", + hostname=host_id, + tags = ["Psiphon 3 Hosts Public Subnet"]) + + return instance, self.get_datacenter_names(), self.get_region() def remove_instance(self, instance_id): # Delete instance @@ -72,10 +150,10 @@ def remove_instance(self, instance_id): # Server side SSH Interaction functions (Migrated from old code) # ### -def refresh_credentials(oracle_account, ip_address, new_root_password, new_stats_password, stats_username): - ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, +def refresh_credentials(vultr_account, ip_address, new_root_password, new_stats_password, stats_username): + ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, - host_auth_key=oracle_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_rsa_private_key) try: ssh.exec_command('echo "root:%s" | chpasswd' % (new_root_password,)) ssh.exec_command('useradd -M -d /var/log -s /bin/sh -g adm %s' % (stats_username)) @@ -87,10 +165,10 @@ def refresh_credentials(oracle_account, ip_address, new_root_password, new_stats finally: ssh.close() -def set_allowed_users(oracle_account, ip_address, stats_username): - ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, +def set_allowed_users(vultr_account, ip_address, stats_username): + ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, - host_auth_key=oracle_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_rsa_private_key) try: user_exists = ssh.exec_command('grep %s /etc/ssh/sshd_config' % stats_username) if not user_exists: @@ -99,30 +177,31 @@ def set_allowed_users(oracle_account, ip_address, stats_username): finally: ssh.close() -def get_host_name(oracle_account, ip_address): +def get_host_name(vultr_account, ip_address): # Note: using base image credentials; call before changing credentials - ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, + ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root',None, None, - host_auth_key=oracle_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_rsa_private_key) try: return ssh.exec_command('hostname').strip() finally: ssh.close() -def set_host_name(oracle_account, ip_address, new_hostname): +def set_host_name(vultr_account, ip_address, new_hostname): # Note: hostnamectl is for systemd servers - ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, + ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, - host_auth_key=oracle_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_rsa_private_key) try: ssh.exec_command('hostnamectl set-hostname %s' % new_hostname) finally: ssh.close() -def add_swap_file(oracle_account, ip_address): - ssh = psi_ssh.make_ssh_session(ip_address, oracle_account.base_image_ssh_port, 'root', None, None, host_auth_key=oracle_account.base_image_rsa_private_key) +def add_swap_file(vultr_account, ip_address): + ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, host_auth_key=vultr_account.base_image_rsa_private_key) try: has_swap = ssh.exec_command('grep swap /etc/fstab') + if not has_swap: ssh.exec_command('dd if=/dev/zero of=/swapfile bs=1024 count=1048576 && mkswap /swapfile && chown root:root /swapfile && chmod 0600 /swapfile') ssh.exec_command('echo "/swapfile swap swap defaults 0 0" >> /etc/fstab') @@ -135,12 +214,77 @@ def add_swap_file(oracle_account, ip_address): # Main function # ### +def get_servers(vultr_account): + # TODO + vultrs = [] + + for region in vultr_account.regions: + vultr_api.region = region + vultr_api.reload() + + instances = vultr_api.list_instances() + vultrs += instances + + # return id in the same format that we store it in Host.provider_id (see launch_new_server below) + return [(v['region'] + '_' + v['id'], v['label']) for v in vultrs] + pass + +def get_server(vultr_account, vultr_id): + # TODO + return vultr_api.get_instance(self, vultr_id, + pass + +def remove_server(vultr_account, vultr_id): + # TODO + pass + +def get_server_ip_addresses(vultr_account, vultr_id): #probably not needed? + vultr_api = PsiVultr(vultr_account) + vultr_api, vultr_id = reload_proper_api_client(vultr_api, vultr_id) + vultr = vultr_api.vultr_list(vultr_id) + + public_ip = vultr['main_ip'] + private_ip = vultr['internal_ip'] + + return (public_ip, private_ip) + + def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): - + + instance = None vultr_api = PsiVultr(vultr_account) # Use API interface try: + #Create a new Vultr instance region = vultr_api.get_region() + datacenter = vultr_api.get_datacenter_names() + # Is it vt or vl or ??? + host_id = "vt" + '-' + region.lower() + datacenter[0:3].lower() + ''.join(random.choice(string.ascii_lowercase) for x in range(8)) + instance, datacenter_name, region = vultr_api.create_instance(host_id) + +### + # Wait for job completion + wait_while_condition(lambda: vultr_api.compute_api.get_instance(instance.id).data.lifecycle_state != 'RUNNING', + 30, + 'Creating VULTR Instance') + + instance_ip_address = instance["main_ip"] + instance_internal_ip_address = instance["internal_ip"] + + new_stats_username = psi_utils.generate_stats_username() + set_host_name(vultr_account, instance_ip_address, host_id) + set_allowed_users(vultr_account, instance_ip_address, new_stats_username) + add_swap_file(vultr_account, instance_ip_address) + + # Change the new vultr instance's credentials + new_root_password = psi_utils.generate_password() + new_stats_password = psi_utils.generate_password() + new_host_public_key = refresh_credentials(vultr_account, instance_ip_address, + new_root_password, new_stats_password, + new_stats_username) + +### + except Exception as ex: if instance: vultr_api.remove_instance(instance.id) @@ -148,7 +292,7 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): return (host_id, is_TCS, 'NATIVE' if is_TCS else None, None, instance.id, instance_ip_address, - oracle_account.base_image_ssh_port, 'root', new_root_password, + vultr_account.base_image_ssh_port, 'root', new_root_password, ' '.join(new_host_public_key.split(' ')[:2]), new_stats_username, new_stats_password, datacenter_name, region, egress_ip_address if multi_ip else None, instance_internal_ip_address) From f2a178c370a35abb103b36c2e025e6d7b9ec3c40 Mon Sep 17 00:00:00 2001 From: iyoshinoya Date: Wed, 18 Oct 2023 14:58:56 -0400 Subject: [PATCH 03/12] modified: psi_vultr.py --- Automation/.psi_vultr.py.swp | Bin 1024 -> 1024 bytes Automation/psi_vultr.py | 2 ++ 2 files changed, 2 insertions(+) diff --git a/Automation/.psi_vultr.py.swp b/Automation/.psi_vultr.py.swp index a1031bb3198e59009ce9bcdd0fad794ad2e6294b..52454aa55cecef5a85b7a7444a1a5f21c34d2be2 100644 GIT binary patch delta 25 TcmZqRXyDlJo>?%I0Sy2ELA3)J delta 10 RcmZqRXyDlJo_XQ~K>!s+1LXh! diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index 633b595f..f70365a4 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -299,3 +299,5 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): if __name__ == '__main__': print(launch_new_server) + + From 5b86f0f9cc44e93ac3ef19c06b3ab2cdc43b7b9e Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 15:57:34 -0400 Subject: [PATCH 04/12] Remove extra stuff --- Automation/.psi_vultr.py.swp | Bin 1024 -> 0 bytes Automation/new_servers.py | 17 ----------------- 2 files changed, 17 deletions(-) delete mode 100644 Automation/.psi_vultr.py.swp delete mode 100644 Automation/new_servers.py diff --git a/Automation/.psi_vultr.py.swp b/Automation/.psi_vultr.py.swp deleted file mode 100644 index 52454aa55cecef5a85b7a7444a1a5f21c34d2be2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmYc?$V<%2S1{KzVn6})=?n~+mHEXPnR)q@i6~MSoXkqyl+-favRn)qRDpux%=og> ZoRT8Ff=X1GQQl|>jD`RmLLd~~4gg0x5ZV9$ diff --git a/Automation/new_servers.py b/Automation/new_servers.py deleted file mode 100644 index 446bd2b3..00000000 --- a/Automation/new_servers.py +++ /dev/null @@ -1,17 +0,0 @@ -import psi_ops -import psi_ssh -import sys -import os -import paramiko -path = "/home/iyoshinoya/psiphon/providers/" -csv_file = f'{path}hosts' -psinet = psi_ops.PsiphonNetwork.load(lock=False) -hosts = [host for host in psinet.get_hosts() if host.is_TCS] -df = open(csv_file, "w") -try: - for i in range(0,len(hosts)): - #df.write(hosts[i].id + "\t" + hosts[i].ip_address + "\t" + hosts[i].ssh_password + "\n") - df.write(hosts[i].id + "\t" + hosts[i].ip_address + "\t" + hosts[i].ssh_password + "\t" + hosts[i].provider + "\n") -except Exception as e: - print("Failed", hosts[i], str(e)) -df.close() From 2d059870c4cc794e9e8456bf850f7e54bdb5abce Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 16:02:21 -0400 Subject: [PATCH 05/12] Rename Sao Paulo --- Automation/psi_vultr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index f70365a4..f3a9e527 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -100,7 +100,7 @@ def get_datacenter_names(self) : "itm" : "VULT Osaka, JP", "man" : "VULT Manchester, GB", "hnl" : "VULT Honolulu, US", #hnl and sao does not have vc2-2c-4gb available. - "sao" : "VULT S\u00e3o Paulo, BR" # this is a mistake/error as per their api: https://api.vultr.com/v2/regions; should be: Sao Paulo + "sao" : "VULT Sao Paulo, BR" # this is a mistake/error as per their api: https://api.vultr.com/v2/regions; should be: Sao Paulo } return datacenters.get(self.client["region"], '') From 6adb838fb9d10bdb17b0c029c27b7096dc4fb48d Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 16:02:52 -0400 Subject: [PATCH 06/12] Fix a missing bruck --- Automation/psi_vultr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index f3a9e527..4c1c3d43 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -231,7 +231,7 @@ def get_servers(vultr_account): def get_server(vultr_account, vultr_id): # TODO - return vultr_api.get_instance(self, vultr_id, + return vultr_api.get_instance(self, vultr_id) pass def remove_server(vultr_account, vultr_id): From 7d29a56ac03f752c145feafaa99a17cfc1c0268c Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 16:38:35 -0400 Subject: [PATCH 07/12] Fix some issue with Vultr API --- Automation/psi_vultr.py | 51 ++++++++++++++++----------------------- Automation/vultr/vultr.py | 2 +- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index 4c1c3d43..f0029d8b 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -31,7 +31,7 @@ from vultr import vultr # VARIABLE -TCS_BASE_IMAGE_NAME = '' +TCS_BASE_IMAGE_ID = '5eaa342e-8b40-4236-b7d1-2fb5a7a34635' TCS_VULTR_DEFAULT_PLAN = 'vc2-2c-4gb' # default 2vCore 4G RAM 'vc2-2c-4gb', Sao Paulo 'vc2-2c-4gb-sc1' #============================================================================== @@ -42,32 +42,26 @@ ### class PsiVultr: def __init__(self, vultr_account, debug=False): - self.config = { - # ?? - } - self.api_key = vultr_account.api_key - self.plan = TCS_VULTR_DEFAULT_PLAN - self.client = vultr.Vultr(api_key=self.api_key) - - def reload(self): self.api_key = vultr_account.api_key + self.regions = vultr_account.regions self.plan = TCS_VULTR_DEFAULT_PLAN + self.base_image_id = TCS_BASE_IMAGE_ID self.client = vultr.Vultr(api_key=self.api_key) def get_region(self, select_region=None): # Load region from API # region_id required for create_instance all_regions = self.client.list_regions() - if select_region == None: - regions = [r for r in all_regions if r['country'] == select_region] + if select_region != None: + regions = [r for r in all_regions if r['id'] == select_region] else: regions = all_regions - region = random.choice(regions) + region = random>choice(regions) - return region['id'], region['country'], region['city'] + return region['country'], region['id'] - def get_datacenter_names(self) : + def get_datacenter_names(self, select_datacenter): datacenters = { "ewr" : "VULT New Jersey, US", "ord" : "VULT Chicago, US", @@ -103,7 +97,7 @@ def get_datacenter_names(self) : "sao" : "VULT Sao Paulo, BR" # this is a mistake/error as per their api: https://api.vultr.com/v2/regions; should be: Sao Paulo } - return datacenters.get(self.client["region"], '') + return datacenters.get(select_datacenter, '') def list_instances(self): # TODO @@ -127,19 +121,19 @@ def restart_instance(self, instance_id): # TODO pass - def create_instance(self, host_id): + def create_instance(self, host_id, datacenter_code): # Launch Instnace instance = self.client.create_instance( - region=region, - plan=self.plan, - label="Psiphon 3 Hosts Public Subnet", #host_id? - os_id=352, #352 -> Debian 10 x64 (buster) || 477 -> Debian 11 x64 (bullseye)" - user_data="QmFzZTY0IEV4YW1wbGUgRGF0YQ==", #The user-supplied, base64 encoded user data to attach to this instance. -> random.choice(string.ascii_lowercase) for x in range(24) - backups="disabled", - hostname=host_id, - tags = ["Psiphon 3 Hosts Public Subnet"]) + region=datacenter_code, + plan=self.plan, + label=host_id, + hostname=host_id, + backups="disabled", + snapshot_id=self.base_image_id, + tags=["psiphond"] + ) - return instance, self.get_datacenter_names(), self.get_region() + return instance, self.get_datacenter_names(instance['region']), self.get_region(instance['region']) def remove_instance(self, instance_id): # Delete instance @@ -256,13 +250,10 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): try: #Create a new Vultr instance - region = vultr_api.get_region() - datacenter = vultr_api.get_datacenter_names() - # Is it vt or vl or ??? - host_id = "vt" + '-' + region.lower() + datacenter[0:3].lower() + ''.join(random.choice(string.ascii_lowercase) for x in range(8)) + region, datacenter_code = vultr_api.get_region() + host_id = "vt" + '-' + region.lower() + datacenter_code.lower() + ''.join(random.choice(string.ascii_lowercase) for x in range(8)) instance, datacenter_name, region = vultr_api.create_instance(host_id) -### # Wait for job completion wait_while_condition(lambda: vultr_api.compute_api.get_instance(instance.id).data.lifecycle_state != 'RUNNING', 30, diff --git a/Automation/vultr/vultr.py b/Automation/vultr/vultr.py index ca5d79fd..dc75c385 100644 --- a/Automation/vultr/vultr.py +++ b/Automation/vultr/vultr.py @@ -3,7 +3,7 @@ import requests class Vultr(object): - API_URL = 'https://api.vultr.com/v2/' + API_URL = 'https://api.vultr.com/v2' def __init__(self, api_key: typing.Union[str, None]): """ From b973aaeed4e1de2cf24f652be59890a83a4750df Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 16:43:19 -0400 Subject: [PATCH 08/12] Fix some issue with Vultr API --- Automation/psi_vultr.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index f0029d8b..26193e5b 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -57,7 +57,7 @@ def get_region(self, select_region=None): else: regions = all_regions - region = random>choice(regions) + region = random.choice(regions) return region['country'], region['id'] @@ -224,9 +224,8 @@ def get_servers(vultr_account): pass def get_server(vultr_account, vultr_id): - # TODO + vultr_api = PsiVultr(vultr_account) return vultr_api.get_instance(self, vultr_id) - pass def remove_server(vultr_account, vultr_id): # TODO @@ -252,13 +251,18 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): #Create a new Vultr instance region, datacenter_code = vultr_api.get_region() host_id = "vt" + '-' + region.lower() + datacenter_code.lower() + ''.join(random.choice(string.ascii_lowercase) for x in range(8)) - instance, datacenter_name, region = vultr_api.create_instance(host_id) + instance, datacenter_name, region = vultr_api.create_instance(host_id, datacenter_code) # Wait for job completion wait_while_condition(lambda: vultr_api.compute_api.get_instance(instance.id).data.lifecycle_state != 'RUNNING', 30, 'Creating VULTR Instance') + print(instance) + print(datacenter_name) + print(region) + return + instance_ip_address = instance["main_ip"] instance_internal_ip_address = instance["internal_ip"] From 189907d1b89a6dd1b0e1132a0c3018bb8dc05828 Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 16:44:01 -0400 Subject: [PATCH 09/12] Add wait while condition --- Automation/psi_vultr.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index 26193e5b..588dd23f 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -34,6 +34,20 @@ TCS_BASE_IMAGE_ID = '5eaa342e-8b40-4236-b7d1-2fb5a7a34635' TCS_VULTR_DEFAULT_PLAN = 'vc2-2c-4gb' # default 2vCore 4G RAM 'vc2-2c-4gb', Sao Paulo 'vc2-2c-4gb-sc1' +### +# +# Helper functions +# +### +def wait_while_condition(condition, max_wait_seconds, description): + total_wait_seconds = 0 + wait_seconds = 5 + while condition() == True: + if total_wait_seconds > max_wait_seconds: + raise Exception('Took more than %d seconds to %s' % (max_wait_seconds, description)) + time.sleep(wait_seconds) + total_wait_seconds = total_wait_seconds + wait_seconds + #============================================================================== ### # From 07da9c9ecc5afbf8c3fa2a3857a95ff4cfac7a1f Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 17:36:54 -0400 Subject: [PATCH 10/12] Finish Vultr APi testing --- Automation/psi_vultr.py | 44 ++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index 588dd23f..8cc00c41 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -60,6 +60,7 @@ def __init__(self, vultr_account, debug=False): self.regions = vultr_account.regions self.plan = TCS_VULTR_DEFAULT_PLAN self.base_image_id = TCS_BASE_IMAGE_ID + self.ssh_key_id = vultr_account.base_image_ssh_key_id self.client = vultr.Vultr(api_key=self.api_key) def get_region(self, select_region=None): @@ -144,6 +145,7 @@ def create_instance(self, host_id, datacenter_code): hostname=host_id, backups="disabled", snapshot_id=self.base_image_id, + sshkey_id=[self.ssh_key_id], tags=["psiphond"] ) @@ -161,7 +163,7 @@ def remove_instance(self, instance_id): def refresh_credentials(vultr_account, ip_address, new_root_password, new_stats_password, stats_username): ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, - host_auth_key=vultr_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_ssh_private_key) try: ssh.exec_command('echo "root:%s" | chpasswd' % (new_root_password,)) ssh.exec_command('useradd -M -d /var/log -s /bin/sh -g adm %s' % (stats_username)) @@ -176,7 +178,7 @@ def refresh_credentials(vultr_account, ip_address, new_root_password, new_stats_ def set_allowed_users(vultr_account, ip_address, stats_username): ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, - host_auth_key=vultr_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_ssh_private_key) try: user_exists = ssh.exec_command('grep %s /etc/ssh/sshd_config' % stats_username) if not user_exists: @@ -189,7 +191,7 @@ def get_host_name(vultr_account, ip_address): # Note: using base image credentials; call before changing credentials ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root',None, None, - host_auth_key=vultr_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_ssh_private_key) try: return ssh.exec_command('hostname').strip() finally: @@ -199,14 +201,14 @@ def set_host_name(vultr_account, ip_address, new_hostname): # Note: hostnamectl is for systemd servers ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, - host_auth_key=vultr_account.base_image_rsa_private_key) + host_auth_key=vultr_account.base_image_ssh_private_key) try: ssh.exec_command('hostnamectl set-hostname %s' % new_hostname) finally: ssh.close() def add_swap_file(vultr_account, ip_address): - ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, host_auth_key=vultr_account.base_image_rsa_private_key) + ssh = psi_ssh.make_ssh_session(ip_address, vultr_account.base_image_ssh_port, 'root', None, None, host_auth_key=vultr_account.base_image_ssh_private_key) try: has_swap = ssh.exec_command('grep swap /etc/fstab') @@ -245,17 +247,6 @@ def remove_server(vultr_account, vultr_id): # TODO pass -def get_server_ip_addresses(vultr_account, vultr_id): #probably not needed? - vultr_api = PsiVultr(vultr_account) - vultr_api, vultr_id = reload_proper_api_client(vultr_api, vultr_id) - vultr = vultr_api.vultr_list(vultr_id) - - public_ip = vultr['main_ip'] - private_ip = vultr['internal_ip'] - - return (public_ip, private_ip) - - def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): instance = None @@ -265,20 +256,17 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): #Create a new Vultr instance region, datacenter_code = vultr_api.get_region() host_id = "vt" + '-' + region.lower() + datacenter_code.lower() + ''.join(random.choice(string.ascii_lowercase) for x in range(8)) - instance, datacenter_name, region = vultr_api.create_instance(host_id, datacenter_code) + instance_info, datacenter_name, region_info = vultr_api.create_instance(host_id, datacenter_code) # Wait for job completion - wait_while_condition(lambda: vultr_api.compute_api.get_instance(instance.id).data.lifecycle_state != 'RUNNING', + wait_while_condition(lambda: vultr_api.client.get_instance(instance_info['id'])['power_status'] != 'running', 30, 'Creating VULTR Instance') - - print(instance) - print(datacenter_name) - print(region) - return + # Wait for Restorying fron snapshot + time.sleep(30) + instance = vultr_api.client.get_instance(instance_info['id']) instance_ip_address = instance["main_ip"] - instance_internal_ip_address = instance["internal_ip"] new_stats_username = psi_utils.generate_stats_username() set_host_name(vultr_account, instance_ip_address, host_id) @@ -292,19 +280,17 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): new_root_password, new_stats_password, new_stats_username) -### - except Exception as ex: if instance: - vultr_api.remove_instance(instance.id) + vultr_api.remove_instance(instance['id']) raise ex return (host_id, is_TCS, 'NATIVE' if is_TCS else None, None, - instance.id, instance_ip_address, + instance['id'], instance_ip_address, vultr_account.base_image_ssh_port, 'root', new_root_password, ' '.join(new_host_public_key.split(' ')[:2]), new_stats_username, new_stats_password, - datacenter_name, region, egress_ip_address if multi_ip else None, instance_internal_ip_address) + datacenter_name, region, egress_ip_address if multi_ip else None, None) if __name__ == '__main__': print(launch_new_server) From 94f6a5a4fd8cca59d4c6224dea39f091463c98ce Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 17:49:49 -0400 Subject: [PATCH 11/12] Finish Testing Vultr --- Automation/psi_vultr.py | 51 +++++++++++------------------------------ 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/Automation/psi_vultr.py b/Automation/psi_vultr.py index 8cc00c41..9e417d8a 100644 --- a/Automation/psi_vultr.py +++ b/Automation/psi_vultr.py @@ -115,26 +115,16 @@ def get_datacenter_names(self, select_datacenter): return datacenters.get(select_datacenter, '') def list_instances(self): - # TODO all_instances = self.client.list_instances() - #pass return all_instances - def remove_instance(self, instance_id): - # TODO - pass - - def start_instance(self, instance_id): - # TODO - pass + def get_instance(self, instance_id): + instance = self.client.get_instance(instance_id) + return instance - def stop_instance(self, instance_id): - # TODO - pass - - def restart_instance(self, instance_id): - # TODO - pass + def remove_instance(self, instance_id): + print("Deleting Instances: {}".format(instance_id)) + self.client.delete_instance(instance_id) def create_instance(self, host_id, datacenter_code): # Launch Instnace @@ -151,10 +141,6 @@ def create_instance(self, host_id, datacenter_code): return instance, self.get_datacenter_names(instance['region']), self.get_region(instance['region']) - def remove_instance(self, instance_id): - # Delete instance - pass - ### # # Server side SSH Interaction functions (Migrated from old code) @@ -225,27 +211,18 @@ def add_swap_file(vultr_account, ip_address): # ### def get_servers(vultr_account): - # TODO - vultrs = [] - - for region in vultr_account.regions: - vultr_api.region = region - vultr_api.reload() - - instances = vultr_api.list_instances() - vultrs += instances - - # return id in the same format that we store it in Host.provider_id (see launch_new_server below) - return [(v['region'] + '_' + v['id'], v['label']) for v in vultrs] - pass + vultr_api = PsiVultr(vultr_account) + instances = vultr_api.list_instances() + #return [(v['region'] + '_' + v['id'], v['label']) for v in vultrs] + return instances def get_server(vultr_account, vultr_id): vultr_api = PsiVultr(vultr_account) - return vultr_api.get_instance(self, vultr_id) + return vultr_api.get_instance(vultr_id) def remove_server(vultr_account, vultr_id): - # TODO - pass + vultr_api = PsiVultr(vultr_account) + vultr_api.remove_instance(vultr_id) def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): @@ -294,5 +271,3 @@ def launch_new_server(vultr_account, is_TCS, plugins, multi_ip=False): if __name__ == '__main__': print(launch_new_server) - - From 9994dabb9c0a386d2c7b5d393dcc1279e89d415a Mon Sep 17 00:00:00 2001 From: Draven Johnson Date: Wed, 18 Oct 2023 17:56:15 -0400 Subject: [PATCH 12/12] Create a vultr account object --- Automation/psi_ops.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Automation/psi_ops.py b/Automation/psi_ops.py index 4cc6ac01..0721d238 100644 --- a/Automation/psi_ops.py +++ b/Automation/psi_ops.py @@ -373,6 +373,14 @@ def copy_server_capabilities(caps): 'base_image_ssh_public_keys, base_image_rsa_private_key', default=None) +VultrAccount = psi_utils.recordtype( + 'VultrAccount', + 'api_key, regions, ' + + 'base_image_root_password, base_image_ssh_private_key, ' + + 'base_image_ssh_public_key, base_image_ssh_port, ' + + 'base_image_ssh_key_id', + default=None) + ElasticHostsAccount = psi_utils.recordtype( 'ElasticHostsAccount', 'zone, uuid, api_key, base_drive_id, cpu, mem, base_host_public_key, ' + @@ -456,6 +464,7 @@ def __init__(self, initialize_plugins=True): self.__scaleway_account = ScalewayAccount() self.__ramnode_account = RamnodeAccount() self.__oci_account = OracleAccount() + self.__vultr_account = VultrAccount() self.__elastichosts_accounts = [] self.__deploy_implementation_required_for_hosts = set() self.__deploy_data_required_for_all = False @@ -923,6 +932,9 @@ def upgrade(self): host.ipmi_password = "" host.ipmi_vpn_profile_location = None self.version = '0.71' + if cmp(parse_version(self.version), parse_version('0.72')) < 0: + self.__vultr_account = VultrAccount() + self.version = '0.72' def initialize_plugins(self): for plugin in plugins: