Skip to content

Commit 97da47b

Browse files
Gabriel MonroyMatthew Fisher
authored andcommitted
feat(client): add deis build support
1 parent 3182ac8 commit 97da47b

1 file changed

Lines changed: 93 additions & 27 deletions

File tree

client/deis.py

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ def __init__(self):
9191
self.cookies.load()
9292
self.cookies.clear_expired_cookies()
9393
self.cookies.save()
94+
# local session state
95+
self.state_file = os.path.expanduser('~/.deis/apps.yml')
96+
if not os.path.isfile(self.state_file):
97+
with open(self.state_file, 'w') as f:
98+
f.write(yaml.safe_dump({}))
9499

95100
def git_root(self):
96101
"""
@@ -106,15 +111,13 @@ def git_root(self):
106111
raise EnvironmentError('Current directory is not a git repository')
107112
return git_root
108113

109-
def get_app(self):
114+
def _get_app_from_git_remote(self, git_root):
110115
"""
111-
Return the application name for the current directory
116+
Return the application name from a git repository root
112117
113118
The application is determined by parsing `git remote -v` output.
114119
If no application is found, raise an EnvironmentError.
115120
"""
116-
git_root = self.git_root()
117-
# try to match a deis remote
118121
remotes = subprocess.check_output(['git', 'remote', '-v'],
119122
cwd=git_root)
120123
m = re.search(r'^deis\W+(?P<url>\S+)\W+\(', remotes, re.MULTILINE)
@@ -127,7 +130,54 @@ def get_app(self):
127130
raise EnvironmentError("Could not parse: {url}".format(**locals()))
128131
return m.groupdict()['app']
129132

130-
app = property(get_app)
133+
def _get_app_from_state(self, current_dir):
134+
"""
135+
Return the application name from the current directory
136+
137+
Uses the local client's state to map to an application name.
138+
"""
139+
with open(self.state_file) as f:
140+
state = yaml.safe_load(f.read())
141+
app = state.get(current_dir, None)
142+
if app is None:
143+
raise EnvironmentError("Could not find app for {current_dir}".format(**locals()))
144+
return app
145+
146+
def _get_app(self):
147+
"""
148+
Return the application for the current directory
149+
150+
For backwards compatibility, the lookup will use:
151+
152+
1. Local client state stored in ~/.deis (preferred)
153+
2. A git remote for the current directory (for backwards compatibility)
154+
"""
155+
try:
156+
return self._get_app_from_state(os.getcwd())
157+
except EnvironmentError:
158+
return self._get_app_from_git_remote(self.git_root())
159+
160+
def _set_app(self, app):
161+
"""
162+
Set the application for the current directory
163+
"""
164+
with open(self.state_file, 'r') as f:
165+
state = yaml.safe_load(f.read())
166+
state[os.getcwd()] = app
167+
with open(self.state_file, 'w') as f:
168+
f.write(yaml.safe_dump(state))
169+
170+
def _del_app(self):
171+
"""
172+
Delete the application for the current directory
173+
"""
174+
with open(self.state_file, 'r') as f:
175+
state = yaml.safe_load(f.read())
176+
state.pop(os.getcwd())
177+
with open(self.state_file, 'w') as f:
178+
f.write(yaml.safe_dump(state))
179+
180+
app = property(_get_app, _set_app, _del_app)
131181

132182
def request(self, *args, **kwargs):
133183
"""
@@ -392,13 +442,8 @@ def apps_create(self, args):
392442
--no-remote do not create a 'deis' git remote
393443
"""
394444
try:
395-
self._session.git_root() # check for a git repository
396-
except EnvironmentError:
397-
print('No git repository found, use `git init` to create one')
398-
sys.exit(1)
399-
try:
400-
self._session.get_app()
401-
print('Deis remote already exists')
445+
self._session.app
446+
print('App already exists at {}'.format(os.getcwd()))
402447
sys.exit(1)
403448
except EnvironmentError:
404449
pass
@@ -423,8 +468,13 @@ def apps_create(self, args):
423468
data = response.json()
424469
app_id = data['id']
425470
print("done, created {}".format(app_id))
426-
# add a git remote
427-
# TODO: retrieve the hostname from service discovery
471+
# store session in local state
472+
self._session.app = app_id
473+
# set a git remote if necessary
474+
try:
475+
self._session.git_root()
476+
except EnvironmentError:
477+
return
428478
hostname = urlparse.urlparse(self._settings['controller']).netloc.split(':')[0]
429479
git_remote = "ssh://git@{hostname}:2222/{app_id}.git".format(**locals())
430480
if args.get('--no-remote'):
@@ -449,7 +499,11 @@ def apps_destroy(self, args):
449499
"""
450500
app = args.get('--app')
451501
if not app:
452-
app = self._session.app
502+
try:
503+
app = self._session.app
504+
except EnvironmentError:
505+
print('No app exists at {}'.format(os.getcwd()))
506+
sys.exit(1)
453507
confirm = args.get('--confirm')
454508
if confirm == app:
455509
pass
@@ -476,15 +530,16 @@ def apps_destroy(self, args):
476530
if response.status_code in (requests.codes.no_content, # @UndefinedVariable
477531
requests.codes.not_found): # @UndefinedVariable
478532
print('done in {}s'.format(int(time.time() - before)))
479-
# If the requested app is in the current dir, delete the git remote
480-
try:
481-
if app == self._session.app:
533+
# If the requested app is in the current dir, delete the git remote and local state
534+
if app == self._session.app:
535+
del self._session.app
536+
try:
482537
subprocess.check_call(
483538
['git', 'remote', 'rm', 'deis'],
484539
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
485540
print('Git remote deis removed')
486-
except (EnvironmentError, subprocess.CalledProcessError):
487-
pass # ignore error
541+
except (EnvironmentError, subprocess.CalledProcessError):
542+
pass # ignore error
488543
else:
489544
raise ResponseError(response)
490545

@@ -1178,12 +1233,13 @@ def ps_scale(self, args):
11781233
"""
11791234
app = args.get('--app')
11801235
if not app:
1181-
app = self._session.get_app()
1236+
app = self._session.app
11821237
body = {}
11831238
for type_num in args.get('<type=num>'):
11841239
typ, count = type_num.split('=')
11851240
body.update({typ: int(count)})
1186-
print('Scaling processes... but first, coffee!')
1241+
sys.stdout.write('Scaling processes... ')
1242+
sys.stdout.flush()
11871243
try:
11881244
progress = TextProgress()
11891245
progress.start()
@@ -1195,7 +1251,7 @@ def ps_scale(self, args):
11951251
progress.cancel()
11961252
progress.join()
11971253
if response.status_code == requests.codes.no_content: # @UndefinedVariable
1198-
print('done in {}s\n'.format(int(time.time() - before)))
1254+
print('done in {}s'.format(int(time.time() - before)))
11991255
self.ps_list({}, app)
12001256
else:
12011257
raise ResponseError(response)
@@ -1449,7 +1505,7 @@ def releases_list(self, args):
14491505
data = response.json()
14501506
for item in data['results']:
14511507
item['created'] = readable_datetime(item['created'])
1452-
print("v{version:<6} {created:<33} {summary}".format(**item))
1508+
print("v{version:<6} {created:<24} {summary}".format(**item))
14531509
else:
14541510
raise ResponseError(response)
14551511

@@ -1470,9 +1526,18 @@ def releases_rollback(self, args):
14701526
else:
14711527
body = {}
14721528
url = "/api/apps/{app}/releases/rollback".format(**locals())
1473-
response = self._dispatch('post', url, json.dumps(body))
1474-
if response.status_code == requests.codes.created:
1475-
print(response.json())
1529+
sys.stdout.write('Rollback to v{version}... '.format(**locals()))
1530+
sys.stdout.flush()
1531+
try:
1532+
progress = TextProgress()
1533+
progress.start()
1534+
response = self._dispatch('post', url, json.dumps(body))
1535+
finally:
1536+
progress.cancel()
1537+
progress.join()
1538+
if response.status_code == requests.codes.created: # @UndefinedVariable
1539+
new_version = response.json()['version']
1540+
print("done, v{}".format(new_version))
14761541
else:
14771542
raise ResponseError(response)
14781543

@@ -1499,6 +1564,7 @@ def shortcuts(self, args):
14991564
('register', 'auth:register'),
15001565
('login', 'auth:login'),
15011566
('logout', 'auth:logout'),
1567+
('build', 'builds:create'),
15021568
('scale', 'ps:scale'),
15031569
('rollback', 'releases:rollback'),
15041570
('sharing', 'perms:list'),

0 commit comments

Comments
 (0)