@@ -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