Commit b9adfa6a authored by Maarten de Waard's avatar Maarten de Waard

Merge branch '1-move-cosmos-py-out-of-openappstack-into-this' into 'master'

Resolve "Move cosmos.py out of openappstack into this"

Closes #1

See merge request !1
parents f56c1ddf 5fc6125d
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
env?/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
.pybuild/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
*.swp
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# Project-specific
This diff is collapsed.
name = "greenhost_cloud"
from greenhost_cloud.cosmos import *
#!/usr/bin/env python3
"""Python module with helper functions to use the cosmos API."""
import json
from datetime import datetime
from datetime import timedelta
import logging
import os
import re
import socket
from time import sleep
import requests
from tabulate import tabulate
from pytz import timezone
# Helper functions
def request_api(resource: str, request_type: str = 'GET',
data: str = ''):
"""Query the cosmos API."""
if 'COSMOS_API_TOKEN' in os.environ:
api_token = os.environ['COSMOS_API_TOKEN']
else:
raise ValueError('Please export the COSMOS_API_TOKEN '
'environment variable.')
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer {0}'.format(api_token)}
api_url_base = 'https://service.greenhost.net/api/v2'
api_url = '{0}/{1}'.format(api_url_base, resource)
if request_type == 'GET':
response = requests.get(api_url, headers=headers)
elif request_type == 'DELETE':
response = requests.delete(api_url, headers=headers)
elif request_type == 'POST':
response = requests.post(
api_url, headers=headers, data=json.dumps(data))
elif request_type == 'PUT':
response = requests.put(
api_url, headers=headers, data=json.dumps(data))
else:
raise ValueError('Specify one of GET/DELETE/POST/PUT as request_type.')
log.debug('Request: %s, %s, data: %s',
response.url, request_type, data)
log.debug('Response code: %s', response.status_code)
status_code_ok = [200, 201, 202, 204]
if response.status_code in status_code_ok:
if response.content:
log.debug('Response: %s\n', response.json())
return json.loads(response.content.decode('utf-8'))
else:
return None
else:
raise requests.HTTPError('WARNING: Got response code ',
response.status_code, response.text)
# API calls
def create_domain_record(domain: str, name: str, data: str,
record_type: str = 'A', update: bool = False):
"""Create domain record.
If 'update' is set to True, the record will be updated if it exists.
"""
log.info('Creating domain record')
record = {
'name': name,
'data': data,
'type': record_type
}
# Check if record exists
existing_record = get_domain_record_by_name(domain=domain, name=name,
record_type=record_type)
if existing_record:
if update:
log.info('Domain record exists - Updating the record.')
response = request_api(
'domains/%s/records/%s' % (domain, existing_record['id']),
'PUT', record)
else:
raise ValueError('Domain record exists - Doing nothing,'
'please use "update=True" to update existing'
'records.')
else:
log.info('Creating new record.')
response = request_api('domains/%s/records/' % domain, 'POST', record)
return response['domain_record']
def create_droplet(name: str, ssh_key_id: int, region: str = 'ams1',
size: int = 2048, disk: int = 20, image: int = 18):
"""Create a droplet.
Required values:
- name (str): Name of the droplet
- ssh_key_id (int): ssh key id to add
Optional values with their default values:
- image (str): 18 (Ubuntu 18.04 x64)
- region (str): 'ams1' (Amsterdam 1)
- size (int): 2048 (2GB RAM)
- disk (int): 20 (20GB disk space)
"""
log.info('Creating droplet')
data = {
"name": name,
"region": region,
"size": size,
"disk": disk,
"image": image,
"ssh_keys": ssh_key_id
}
response = request_api('droplet', 'POST', data)
return response
def delete_domain_record(domain: str, id: int):
"""Delete a domain record."""
log.info('Deleting domain record %s', id)
response = request_api('domains/{0}/records/{1}'.format(domain, id),
'DELETE')
return response
def delete_domain_records_by_name(domain: str, name_regex: str):
"""Delete all domain records in a given domain matching a regex.
Examples:
delete_domain_records_by_name('openappstack.net', '^\*.ci-')
delete_domain_records_by_name('openappstack.net', '^ci-')
"""
all = get_domain_records_by_name(domain, name_regex)
for record in all:
delete_domain_record(domain, record['id'])
def delete_droplet(id: int):
"""Delete a droplet. Droplet needs to be stopped first."""
log.info('Deleting %s', id)
response = request_api('droplets/{0}'.format(id), 'DELETE')
return response
def get_domain_record(domain: str, id: int):
"""Get details for given domain record."""
response = request_api('domains/{0}/records/{1}'.format(domain, id))
return response['domain_record']
def get_domain_records(domain: str):
"""Get domain records for given domain."""
response = request_api('domains/{0}/records'.format(domain))
return response['domain_records']
def get_domain_record_by_name(domain: str, name: str,
record_type: str = 'A'):
"""
Get domain record for given name and type.
Example:
get_domain_record_by_name(domain='openappstack.net', name='varac-oas')
"""
records = get_domain_records(domain=domain)
matching = None
for record in records:
if record['name'] == name and record['type'] == record_type:
matching = record
break
if not matching:
log.info('No domain record found.')
return matching
def get_domain_records_by_name(domain: str, name_regex: str):
r"""
Get all information about domain records matching a regex in their names.
Example:
get_domain_records_by_name(name_regex='^ci\d+')
"""
all_records = get_domain_records(domain)
matching = [record for record in all_records
if re.match(name_regex, record['name'])]
return matching
def get_droplets():
"""Get all information about all droplets."""
response = request_api('droplets')
return response['droplets']
def get_droplets_by_name(name_regex: str):
r"""
Get all information about droplets matching a regex in their names.
Example:
get_droplets_by_name(name_regex='^ci\d+')
"""
all_droplets = get_droplets()
matching = [droplet for droplet in all_droplets
if re.match('^ci+', droplet['name'])]
return matching
def get_droplet(id: int):
"""Get information about specified droplet."""
response = request_api('droplets/{0}'.format(id))
return response['droplet']
def list_domain_records(domain: str):
"""List domain records for given domain."""
records = get_domain_records(domain)
log.debug(json.dumps(records, sort_keys=True, indent=2))
table_records = [[
record['id'], record['name'], record['type'], record['data']]
for record in records]
log.info(tabulate(table_records,
headers=['ID', 'Name', 'Type', 'Data']))
def list_droplets():
"""List all droplets by their ID, Name, IP and state."""
droplets = get_droplets()
log.debug(json.dumps(droplets, sort_keys=True, indent=2))
table_droplets = [
[
droplet['id'],
droplet['name'],
', '.join([x['ip_address'] for x in droplet['networks']['v4']]),
droplet['status']
]
for droplet in droplets]
log.info(tabulate(table_droplets,
headers=['ID', 'Name', 'IPv4', 'Status']))
def shutdown_droplet(id: int):
"""Shut down specified droplet (through a power_off call)."""
log.info('Shutting down %s', id)
data = {"type": "power_off"}
response = request_api('droplets/{0}/actions'.format(id), 'POST', data)
return response
def status_droplet(id: int):
"""Get status of specified droplet."""
response = get_droplet(id)
return response['status']
def terminate_droplet(id: int):
"""Terminate a droplet by powering it down and deleting it."""
shutdown_droplet(id)
wait_for_state(id, 'stopped')
delete_droplet(id)
def terminate_droplets_by_name(name_regex: str, ndays: int = 0, domain: str = 'openappstack.net'):
r"""
Terminate droplets matching a regex and for x days older than current day.
Droplets defined on the env variable NO_TERMINATE_DROPLETS will not be delated
Example how to terminate all CI instances:
terminate_old_droplets(name_regex='^ci\d+', ndays=5)
will match i.e 'ci1234' , 'ci1', with a creation time older than 5 days
"""
threshold_time = (datetime.now(tz=timezone('Europe/Stockholm')) - timedelta(days=ndays)).strftime("%Y-%m-%dT%H:%M:%S+00:00")
all = get_droplets()
noterminate_droplets = []
if 'NO_TERMINATE_DROPLETS' in os.environ:
noterminate_droplets = os.environ['NO_TERMINATE_DROPLETS'].split(',')
for droplet in all:
if droplet['name'] not in noterminate_droplets:
if re.match(name_regex, droplet['name']):
if droplet['created_at'] < threshold_time:
delete_domain_records_by_name(domain, '^\*.'+droplet['name'])
delete_domain_records_by_name(domain, '^'+droplet['name'])
terminate_droplet(droplet['id'])
def wait_for_ssh(ip: str):
"""Wait for ssh to be reachable on port 22."""
log.info('Waiting for ssh to become available on ip %s', ip)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while sock.connect_ex((ip, 22)) != 0:
sleep(1)
def wait_for_state(id: int, state):
"""Wait for a droplet to reach a certain state."""
log.info('Waiting for droplet %s to reach %s state...', id, state)
status = status_droplet(id)
log.debug(status)
while status != state:
sleep(1)
status = status_droplet(id)
# When called from from ipython, setup
# logging to console
try:
__IPYTHON__
log = logging.getLogger()
log.addHandler(logging.StreamHandler())
log.setLevel(logging.INFO)
except NameError:
log = logging.getLogger(__name__)
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="greenhost_cloud",
version="0.1.0",
author="Greenhost",
author_email="info@greenhost.net",
description="Create and manipulate Greenhost VPSs via the command line or python functions.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://open.greenhost.net/greenhost/cloud-api",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: AGPL",
"Operating System :: OS Independent",
],
install_requires=['requests', 'tabulate', 'pytz']
)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment