Skip to content

Commit 9d55b91

Browse files
committed
Merge pull request #12 from cloud-hero/develop
v0.2
2 parents 558d20a + ac09bc0 commit 9d55b91

File tree

7 files changed

+90
-57
lines changed

7 files changed

+90
-57
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ $./hero provider ls
8585
### Environment
8686
An environment is a group of servers (nodes). You can give them any name, but we usualy call them production, staging, development.
8787

88-
##### Create
88+
##### Add
8989
```bash
90-
$./hero env create -p provider_id -l location -n name
90+
$./hero env add -p provider_id -l location -n name
9191
```
9292

9393
| Parameter | Description

constants.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
VERBOSE = False
2-
CLI_VERSION = '0.1'
3-
SSH_KEY_PATH = '~/.ssh/id_rsa_cloudhero'
2+
CLI_VERSION = '0.2'
43

4+
# API-related constants.
5+
# https://docs.cloudhero.io
56
CLOUD_HERO_URI = 'http://d.cloudhero.io'
67
CLOUD_HERO_TOKEN_ENV_VARIABLE = 'CLOUD_HERO_TOKEN'
8+
CLOUD_HERO_USER_AGENT = 'cloudhero-cli'
79

8-
CLOUD_HERO_DIR = '~/.herorc'
9-
CLOUD_HERO_TOKEN = CLOUD_HERO_DIR + '/token'
10-
CLOUD_HERO_CACHE_NODES = CLOUD_HERO_DIR + '/cache/nodes'
11-
CLOUD_HERO_CACHE_ENVIRONMENTS = CLOUD_HERO_DIR + '/cache/enviornments'
12-
CLOUD_HERO_CACHE_OPTIONS = CLOUD_HERO_DIR + '/cache/options'
10+
# Local directory-related constants.
11+
CLOUD_HERO_DIR = '~/.herorc/'
12+
CLOUD_HERO_SSH_KEY = CLOUD_HERO_DIR + '.ssh/id_rsa_cloudhero'
13+
CLOUD_HERO_TOKEN = CLOUD_HERO_DIR + 'token'
14+
CLOUD_HERO_CACHE_NODES = CLOUD_HERO_DIR + 'cache/nodes'
15+
CLOUD_HERO_CACHE_ENVIRONMENTS = CLOUD_HERO_DIR + 'cache/enviornments'
16+
CLOUD_HERO_CACHE_OPTIONS = CLOUD_HERO_DIR + 'cache/options'
17+
18+
# Exceptions.
19+
class NotFound(Exception): pass

docs/WEB-EXAMPLE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Step 4.
2727
###### Create a new environment where the nodes will be launched.
2828
In this step we will select which cloud provider we want to work with, which location to use and the name of our environment
2929
```
30-
$ ./hero environment create -p 56fe7d5910d39669c06a5276 -l eu-west-1 -n bigsite-prod
30+
$ ./hero environment add -p 56fe7d5910d39669c06a5276 -l eu-west-1 -n bigsite-prod
3131
```
3232
Step 5:
3333
-------

hero

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33
import os
4+
import shutil
45
import sys
56
import paramiko
67
import traceback
@@ -12,7 +13,7 @@ import requests
1213
from constants import *
1314
from interactive import interactive_shell
1415
from pyhero import Client
15-
from prompter import PROMPTER_KWARGS
16+
from prompter import *
1617
from utils import (write_to_file, set_keys_to_empty_values,
1718
get_docker_ip_for_environment)
1819

@@ -24,7 +25,7 @@ def persist_token(user_token):
2425
Set token environment variable.
2526
"""
2627
os.environ['CLOUD_HERO_TOKEN'] = user_token
27-
write(user_token)
28+
write_token(user_token)
2829

2930

3031
def forget_token():
@@ -33,10 +34,9 @@ def forget_token():
3334
"""
3435
env_token = os.environ.get(CLOUD_HERO_TOKEN_ENV_VARIABLE, None)
3536
del env_token
36-
delete_token()
3737

3838

39-
def write(content):
39+
def write_token(content):
4040
"""
4141
Write token to config file.
4242
"""
@@ -57,17 +57,6 @@ def read_token():
5757
return token_from_file.strip().replace('\n', '').replace('\t', '')
5858

5959

60-
def delete_token():
61-
"""
62-
Remove token.
63-
"""
64-
config_file_path = os.path.expanduser(CLOUD_HERO_TOKEN)
65-
try:
66-
os.remove(config_file_path)
67-
except OSError:
68-
print('You are already logged out!')
69-
70-
7160
def exception_handler(api_exception):
7261
if isinstance(api_exception, requests.HTTPError):
7362
response = api_exception.response
@@ -96,7 +85,8 @@ if not token:
9685
token = read_token()
9786
cloud_hero = Client(base_url=CLOUD_HERO_URI, token=token,
9887
exception_callback=exception_handler,
99-
clean_up_arguments=True)
88+
clean_up_arguments=True,
89+
user_agent=CLOUD_HERO_USER_AGENT)
10090

10191

10292
def verify_one_of(kwargs, *args):
@@ -112,7 +102,7 @@ def verify_one_of(kwargs, *args):
112102

113103
def tags_for_pprint(tags):
114104
if not tags:
115-
return '-'
105+
return NOTHING_TO_SHOW
116106
return ', '.join(['{}:{}'.format(key, value)
117107
for key, value in tags.items()])
118108

@@ -146,7 +136,7 @@ def log_response(response):
146136
# ------------------------------------------------------------------------
147137
class OrderedHeroCommands(click.Group):
148138

149-
COMMANDS_ORDER = ['login', 'register', 'provider',
139+
COMMANDS_ORDER = ['login', 'register', 'provider', 'integration',
150140
'environment', 'node', 'docker', 'ssh']
151141

152142
COMMANDS_ALIASES = {
@@ -238,9 +228,9 @@ def scale_node(**kwargs):
238228
@node.command('ls', short_help='List all your nodes',
239229
help='List all your nodes')
240230
def list_nodes():
241-
node_format = ('{node[name]:<25}{environment[name]:<20}'
242-
'{node[public_ip]:<17}{node[private_ip]:<17}'
243-
'{node[status]:<10}{node[provider]:<10}{node[id]:<25}'
231+
node_format = ('{node[name]:<20}{environment[name]:<20}'
232+
'{node[public_ip]:<15}{node[private_ip]:<15}'
233+
'{node[status]:<10}{node[provider]:<15}{node[id]:<25}'
244234
'{environment[id]:<25}{node[tags]:<15}{node[packages]:<15}')
245235
print node_format.format(**PROMPTER_KWARGS)
246236

@@ -251,17 +241,17 @@ def list_nodes():
251241
'node': {
252242
'id': node['id'],
253243
'name': node['name'],
254-
'status': 'TBD',
244+
'status': node.get('status', NOT_AVAILABLE),
255245
'provider': environment['provider']['name'],
256-
'public_ip': node.get('public_ip', '-'),
257-
'private_ip': node.get('private_ip', '-'),
246+
'public_ip': node.get('public_ip', NOT_AVAILABLE),
247+
'private_ip': node.get('private_ip', NOT_AVAILABLE),
258248
'packages': ','.join(node['packages']),
259249
'tags': tags_for_pprint(node['tags'])
260250
},
261251
'environment': {
262252
'id': environment['id'],
263253
'name': environment['name'],
264-
}
254+
},
265255
}
266256
print(node_format.format(**node_data))
267257

@@ -284,19 +274,18 @@ def environment():
284274
pass
285275

286276

287-
@environment.command('create', short_help='Create environment',
288-
help='Create environment')
277+
@environment.command('add', short_help='Add environment', help='Add environment')
289278
@click.option('-p', '--provider_id', prompt=True, help='Select the provider ID')
290279
@click.option('-l', '--location', prompt=True,
291280
help='Cloud provider location/region for environment')
292281
@click.option('-n', '--name', prompt=True, help='Environment name')
293-
def create_environment(**kwargs):
282+
def add_environment(**kwargs):
294283
data = {
295284
'region': kwargs['location'],
296285
'environment': kwargs['name'],
297286
'provider_id': kwargs['provider_id']
298287
}
299-
log_response(cloud_hero.create_environment(data))
288+
log_response(cloud_hero.add_environment(data))
300289

301290

302291
@environment.command('rm', short_help='Remove an existing environment',
@@ -315,7 +304,8 @@ def remove_environment(**kwargs):
315304
def list_environments():
316305
format_string = ('{node[name]:<25}{environment[location]:<15}'
317306
'{nodes_count:<15}{environment[name]:<20}'
318-
'{environment[id]:<30}')
307+
'{environment[id]:<30}'
308+
'{provider[name]:<30}{provider[id]:<30}')
319309
print format_string.format(**PROMPTER_KWARGS)
320310

321311
environments_list = cloud_hero.list_environments()
@@ -326,9 +316,14 @@ def list_environments():
326316
'name': environment['name'],
327317
'location': environment['os_region'],
328318
},
329-
'nodes_count': len(environment['nodes'])
319+
'nodes_count': len(environment['nodes']),
320+
'provider': {
321+
'id': environment['provider']['id'],
322+
'name': environment['provider']['name']
323+
}
330324
}
331-
for index, node in enumerate(environment['nodes'] or [{'name': '-'}]):
325+
nodes_list = environment['nodes'] or [{'name': NOTHING_TO_SHOW}]
326+
for index, node in enumerate(nodes_list):
332327
if index == 0:
333328
print format_string.format(node=node, **environment_data)
334329
continue
@@ -446,7 +441,14 @@ def remove_provider(**kwargs):
446441
def export_docker_environment(**kwargs):
447442
environment = kwargs['environment']
448443
node_details = cloud_hero.get_all_details()
449-
docker_ip = get_docker_ip_for_environment(node_details, environment)
444+
try:
445+
docker_ip = get_docker_ip_for_environment(node_details, environment)
446+
except NotFound as exception:
447+
sys.exit(exception.message)
448+
if docker_ip is None:
449+
sys.exit('Node is still being created, could not find the IP to '
450+
'connect to')
451+
450452
print('export DOCKER_HOST=tcp://{}:4000'.format(docker_ip))
451453
print('# Run this command to configure your shell:\n'
452454
'# eval "$(hero docker {})"'.format(environment))
@@ -520,7 +522,7 @@ def ssh_to_vm(**kwargs):
520522
remote_user = node['provider']['username']
521523

522524
# Get key and write it to the local path
523-
expanded_file_path = os.path.expanduser(SSH_KEY_PATH)
525+
expanded_file_path = os.path.expanduser(CLOUD_HERO_SSH_KEY)
524526
if not os.path.exists(expanded_file_path):
525527
ssh_key_content = cloud_hero.list_key()['content']
526528
write_to_file(ssh_key_content, expanded_file_path)
@@ -558,6 +560,15 @@ def ssh_to_vm(**kwargs):
558560
def logout():
559561
forget_token()
560562

563+
# Remove everything from the CLOUD_HERO_PATH.
564+
cloud_hero_path = os.path.expanduser(CLOUD_HERO_DIR)
565+
try:
566+
shutil.rmtree(cloud_hero_path)
567+
except OSError as os_exception:
568+
print(os_exception.strerror)
569+
570+
print('Logout successful!')
571+
561572

562573
@hero.command(short_help='Login user', help='Login user')
563574
@click.option('-e', '--email', prompt=True, help='Email')

prompter.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PROMPTER_KWARGS = {
2-
'node':{
2+
'node': {
33
'id': 'NODE-ID',
44
'name': 'NODE-NAME',
55
'public_ip': 'PUBLIC-IP',
@@ -14,10 +14,18 @@
1414
'name': 'ENVIRONMENT-NAME',
1515
'location': 'LOCATION'
1616
},
17+
'provider': {
18+
'id': 'PROVIDER-ID',
19+
'name': 'PROVIDER-NAME',
20+
'type': 'PROVIDER-TYPE',
21+
},
1722
'application': {
1823
'id': 'APPLICATION-ID',
1924
'name': 'APPLICATION-NAME'
2025
},
2126
'index': 'OPTION',
2227
'nodes_count': 'NODES-COUNT'
2328
}
29+
30+
NOT_AVAILABLE = 'N/A'
31+
NOTHING_TO_SHOW = '-'

pyhero.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@
88

99
DEFAULT_CLIENT_VERSION = '1.0'
1010
DEFAULT_TIMEOUT_SECONDS = 60
11-
CLIENT_CACHE_TTL = 60 * 5
11+
CLIENT_CACHE_TTL = 60
1212
CLIENT_CACHE_OPTIONS_TTL = 60 * 60
1313

14-
class NotFound(Exception): pass
15-
class APIError(Exception): pass
16-
1714

1815
ENDPOINTS = {
1916
'register': '/accounts/register',
@@ -35,10 +32,12 @@ class Client(object):
3532
def __init__(self, base_url=None, token=None,
3633
timeout=DEFAULT_TIMEOUT_SECONDS,
3734
exception_callback=None,
38-
clean_up_arguments=False):
35+
clean_up_arguments=False,
36+
user_agent=None):
3937
self.timeout = timeout
4038
self.token = token
4139
self.base_url = base_url
40+
self.user_agent = user_agent
4241
self.exception_callback = exception_callback
4342
self.clean_up_arguments = clean_up_arguments
4443
if self.base_url is None:
@@ -96,7 +95,7 @@ def delete_integration(self, item_id):
9695
item_id))
9796

9897
@invalidate_cache(CLOUD_HERO_CACHE_ENVIRONMENTS, CLOUD_HERO_CACHE_NODES)
99-
def create_environment(self, data):
98+
def add_environment(self, data):
10099
return self._result(self.post_json(ENDPOINTS['environments'],
101100
data=data))
102101

@@ -127,8 +126,8 @@ def get_all_details(self):
127126
'node': {
128127
'id': node['id'],
129128
'name': node['name'],
130-
'private_ip': node['private_ip'],
131-
'public_ip': node['public_ip'],
129+
'private_ip': node.get('private_ip'),
130+
'public_ip': node.get('public_ip'),
132131
'packages': node['packages'],
133132
'size': node['size'],
134133
'tags': node['tags']
@@ -232,6 +231,9 @@ def _update_request(self, kwargs):
232231
if self.token:
233232
kwargs['headers']['Authentication-Token'] = self.token
234233

234+
if self.user_agent:
235+
kwargs['headers']['User-Agent'] = self.user_agent
236+
235237
return kwargs
236238

237239
def _result(self, response, json=True):

utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import time
66

7-
from constants import CLOUD_HERO_CACHE_OPTIONS
7+
from constants import CLOUD_HERO_CACHE_OPTIONS, NotFound
88

99

1010
def write_to_file(content, file_path, is_json=False):
@@ -114,8 +114,13 @@ def set_keys_to_empty_values(obj):
114114

115115

116116
def get_docker_ip_for_environment(node_details, environment_id):
117+
environment_found = False
117118
for node, nodes_data in node_details.items():
118119
for node_data in nodes_data:
119-
if (node_data['environment']['id'] == environment_id and
120-
node_data['node']['public_ip']):
121-
return node_data['node']['public_ip']
120+
if node_data['environment']['id'] == environment_id:
121+
environment_found = True
122+
if node_data['node'].get('public_ip'):
123+
return node_data['node']['public_ip']
124+
125+
if environment_found is False:
126+
raise NotFound('Environment {} not found!'.format(environment_id))

0 commit comments

Comments
 (0)