{"action":"created","comment":{"url":"https://api.github.com/repos/floydhub/floyd-cli/pulls/comments/222896534","pull_request_review_id":161893267,"id":222896534,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyMjg5NjUzNA==","diff_hunk":"@@ -9,22 +11,125 @@\n from floyd.model.credentials import Credentials\n from floyd.log import logger as floyd_logger\n \n+import threading\n+import webbrowser\n+import subprocess\n+if sys.version_info[0] < 3:\n+    import urlparse\n+    from BaseHTTPServer import (\n+        BaseHTTPRequestHandler,\n+        HTTPServer,\n+    )\n+    from Queue import Queue\n+else:\n+    from urllib import parse as urlparse\n+    from http.server import (\n+        BaseHTTPRequestHandler,\n+        HTTPServer,\n+    )\n+    from queue import Queue\n+\n+\n+class LoginServer(HTTPServer, object):\n+    def __init__(self, server_address, RequestHandlerClass, key_queue):\n+        super(LoginServer, self).__init__(server_address, RequestHandlerClass)\n+        self.key_queue = key_queue\n+\n+\n+class LoginHttpRequestHandler(BaseHTTPRequestHandler):\n+    def do_OPTIONS(self):\n+        self.send_response(200, \"ok\")\n+        self.send_header('Access-Control-Allow-Origin', '*')\n+        self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')\n+        self.end_headers()\n+\n+    def do_GET(self):\n+        params = urlparse.parse_qs(urlparse.urlparse(self.path).query)\n+        key = params.get('apikey')\n+        if not key:\n+            self.send_response(400)\n+            return\n+\n+        self.server.key_queue.put(key[0])\n+\n+        self.send_response(200)\n+        self.send_header('Access-Control-Allow-Origin', '*')\n+        self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')\n+        self.send_header('Content-type', 'text/html')\n+        self.end_headers()\n+        self.wfile.write(\"OK\".encode('utf-8'))\n+\n+    def log_message(self, fmt, *args):\n+        return\n+\n+\n+@contextlib.contextmanager\n+def get_free_port():\n+    try:\n+        s = socket.socket()\n+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n+        s.bind(('localhost', 0))\n+        yield s.getsockname()[1]\n+        s.close()\n+    except socket.error:\n+        yield None\n+\n+\n+def wait_for_apikey():\n+    key_queue = Queue()\n+    with get_free_port() as port:\n+        if not port:\n+            return\n+        server = LoginServer(('', port), LoginHttpRequestHandler, key_queue)\n+\n+    t = threading.Thread(\n+        target=server.serve_forever)\n+    t.daemon = True\n+    t.start()\n+\n+    cli_host = 'http://127.0.0.1'\n+    url = '%s/cli_login?callback=%s:%s' % (floyd.floyd_web_host, cli_host, port)\n+    subprocess.check_output(\n+        [sys.executable, '-m', 'webbrowser', url], stderr=subprocess.STDOUT)\n+\n+    floyd_logger.info('waiting for login from browser...')\n+    while True:\n+        try:\n+            apikey = key_queue.get(timeout=500)\n+            break\n+        except Queue.Empty:\n+            pass\n+\n+    server.shutdown()\n+    return apikey\n+\n+\n+def has_browser():\n+    try:\n+        webbrowser.get()\n+        return True\n+    except webbrowser.Error:\n+        return False\n+\n \n @click.command()\n @click.option('--token', is_flag=True, default=False, help='Just enter token')\n-@click.option('--apikey', '-k', default=None, help='Api Key')\n-@click.option('--username', '-u', default=None, help='FloydHub username')\n-@click.option('--password', '-p', default=None, help='FloydHub password')\n+@click.option('--apikey', '-k', help='Api Key')\n+@click.option('--username', '-u', help='FloydHub username')\n+@click.option('--password', '-p', help='FloydHub password')\n def login(token, apikey, username, password):\n     \"\"\"\n     Login to FloydHub.\n     \"\"\"\n+    if not apikey:\n+        if has_browser():\n+            apikey = wait_for_apikey()","path":"floyd/cli/auth.py","position":128,"original_position":128,"commit_id":"262c3d28377509ea8008c871cfd627d1ad1e9298","original_commit_id":"262c3d28377509ea8008c871cfd627d1ad1e9298","user":{"login":"narenst","id":460436,"node_id":"MDQ6VXNlcjQ2MDQzNg==","avatar_url":"https://avatars2.githubusercontent.com/u/460436?v=4","gravatar_id":"","url":"https://api.github.com/users/narenst","html_url":"https://github.com/narenst","followers_url":"https://api.github.com/users/narenst/followers","following_url":"https://api.github.com/users/narenst/following{/other_user}","gists_url":"https://api.github.com/users/narenst/gists{/gist_id}","starred_url":"https://api.github.com/users/narenst/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/narenst/subscriptions","organizations_url":"https://api.github.com/users/narenst/orgs","repos_url":"https://api.github.com/users/narenst/repos","events_url":"https://api.github.com/users/narenst/events{/privacy}","received_events_url":"https://api.github.com/users/narenst/received_events","type":"User","site_admin":false},"body":"Is it possible to exit the `wait_for_apikey` call and continue this function? So that the user could login with token or username / pwd?","created_at":"2018-10-05T05:44:35Z","updated_at":"2018-10-05T05:49:05Z","html_url":"https://github.com/floydhub/floyd-cli/pull/209#discussion_r222896534","pull_request_url":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/floydhub/floyd-cli/pulls/comments/222896534"},"html":{"href":"https://github.com/floydhub/floyd-cli/pull/209#discussion_r222896534"},"pull_request":{"href":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209"}}},"pull_request":{"url":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209","id":220217717,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIwMjE3NzE3","html_url":"https://github.com/floydhub/floyd-cli/pull/209","diff_url":"https://github.com/floydhub/floyd-cli/pull/209.diff","patch_url":"https://github.com/floydhub/floyd-cli/pull/209.patch","issue_url":"https://api.github.com/repos/floydhub/floyd-cli/issues/209","number":209,"state":"open","locked":false,"title":"new login flow","user":{"login":"houqp","id":670302,"node_id":"MDQ6VXNlcjY3MDMwMg==","avatar_url":"https://avatars0.githubusercontent.com/u/670302?v=4","gravatar_id":"","url":"https://api.github.com/users/houqp","html_url":"https://github.com/houqp","followers_url":"https://api.github.com/users/houqp/followers","following_url":"https://api.github.com/users/houqp/following{/other_user}","gists_url":"https://api.github.com/users/houqp/gists{/gist_id}","starred_url":"https://api.github.com/users/houqp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houqp/subscriptions","organizations_url":"https://api.github.com/users/houqp/orgs","repos_url":"https://api.github.com/users/houqp/repos","events_url":"https://api.github.com/users/houqp/events{/privacy}","received_events_url":"https://api.github.com/users/houqp/received_events","type":"User","site_admin":false},"body":"todo:\r\n\r\n- [ ] help text on headless machine","created_at":"2018-10-04T00:26:11Z","updated_at":"2018-10-05T05:49:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f78f0cf1b22b7b396f1a9f5bbff3880d9ff0ff54","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209/commits","review_comments_url":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209/comments","review_comment_url":"https://api.github.com/repos/floydhub/floyd-cli/pulls/comments{/number}","comments_url":"https://api.github.com/repos/floydhub/floyd-cli/issues/209/comments","statuses_url":"https://api.github.com/repos/floydhub/floyd-cli/statuses/262c3d28377509ea8008c871cfd627d1ad1e9298","head":{"label":"houqp:login","ref":"login","sha":"262c3d28377509ea8008c871cfd627d1ad1e9298","user":{"login":"houqp","id":670302,"node_id":"MDQ6VXNlcjY3MDMwMg==","avatar_url":"https://avatars0.githubusercontent.com/u/670302?v=4","gravatar_id":"","url":"https://api.github.com/users/houqp","html_url":"https://github.com/houqp","followers_url":"https://api.github.com/users/houqp/followers","following_url":"https://api.github.com/users/houqp/following{/other_user}","gists_url":"https://api.github.com/users/houqp/gists{/gist_id}","starred_url":"https://api.github.com/users/houqp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houqp/subscriptions","organizations_url":"https://api.github.com/users/houqp/orgs","repos_url":"https://api.github.com/users/houqp/repos","events_url":"https://api.github.com/users/houqp/events{/privacy}","received_events_url":"https://api.github.com/users/houqp/received_events","type":"User","site_admin":false},"repo":{"id":86200815,"node_id":"MDEwOlJlcG9zaXRvcnk4NjIwMDgxNQ==","name":"floyd-cli","full_name":"houqp/floyd-cli","private":false,"owner":{"login":"houqp","id":670302,"node_id":"MDQ6VXNlcjY3MDMwMg==","avatar_url":"https://avatars0.githubusercontent.com/u/670302?v=4","gravatar_id":"","url":"https://api.github.com/users/houqp","html_url":"https://github.com/houqp","followers_url":"https://api.github.com/users/houqp/followers","following_url":"https://api.github.com/users/houqp/following{/other_user}","gists_url":"https://api.github.com/users/houqp/gists{/gist_id}","starred_url":"https://api.github.com/users/houqp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houqp/subscriptions","organizations_url":"https://api.github.com/users/houqp/orgs","repos_url":"https://api.github.com/users/houqp/repos","events_url":"https://api.github.com/users/houqp/events{/privacy}","received_events_url":"https://api.github.com/users/houqp/received_events","type":"User","site_admin":false},"html_url":"https://github.com/houqp/floyd-cli","description":"Command line tool for Floyd","fork":true,"url":"https://api.github.com/repos/houqp/floyd-cli","forks_url":"https://api.github.com/repos/houqp/floyd-cli/forks","keys_url":"https://api.github.com/repos/houqp/floyd-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/houqp/floyd-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/houqp/floyd-cli/teams","hooks_url":"https://api.github.com/repos/houqp/floyd-cli/hooks","issue_events_url":"https://api.github.com/repos/houqp/floyd-cli/issues/events{/number}","events_url":"https://api.github.com/repos/houqp/floyd-cli/events","assignees_url":"https://api.github.com/repos/houqp/floyd-cli/assignees{/user}","branches_url":"https://api.github.com/repos/houqp/floyd-cli/branches{/branch}","tags_url":"https://api.github.com/repos/houqp/floyd-cli/tags","blobs_url":"https://api.github.com/repos/houqp/floyd-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/houqp/floyd-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/houqp/floyd-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/houqp/floyd-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/houqp/floyd-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/houqp/floyd-cli/languages","stargazers_url":"https://api.github.com/repos/houqp/floyd-cli/stargazers","contributors_url":"https://api.github.com/repos/houqp/floyd-cli/contributors","subscribers_url":"https://api.github.com/repos/houqp/floyd-cli/subscribers","subscription_url":"https://api.github.com/repos/houqp/floyd-cli/subscription","commits_url":"https://api.github.com/repos/houqp/floyd-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/houqp/floyd-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/houqp/floyd-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/houqp/floyd-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/houqp/floyd-cli/contents/{+path}","compare_url":"https://api.github.com/repos/houqp/floyd-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/houqp/floyd-cli/merges","archive_url":"https://api.github.com/repos/houqp/floyd-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/houqp/floyd-cli/downloads","issues_url":"https://api.github.com/repos/houqp/floyd-cli/issues{/number}","pulls_url":"https://api.github.com/repos/houqp/floyd-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/houqp/floyd-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/houqp/floyd-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/houqp/floyd-cli/labels{/name}","releases_url":"https://api.github.com/repos/houqp/floyd-cli/releases{/id}","deployments_url":"https://api.github.com/repos/houqp/floyd-cli/deployments","created_at":"2017-03-26T01:55:27Z","updated_at":"2018-09-25T17:23:50Z","pushed_at":"2018-10-04T18:02:40Z","git_url":"git://github.com/houqp/floyd-cli.git","ssh_url":"git@github.com:houqp/floyd-cli.git","clone_url":"https://github.com/houqp/floyd-cli.git","svn_url":"https://github.com/houqp/floyd-cli","homepage":null,"size":509,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"floydhub:master","ref":"master","sha":"1e3fe305b982cc696f63b849948d959a8c68d90a","user":{"login":"floydhub","id":21301057,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxMzAxMDU3","avatar_url":"https://avatars1.githubusercontent.com/u/21301057?v=4","gravatar_id":"","url":"https://api.github.com/users/floydhub","html_url":"https://github.com/floydhub","followers_url":"https://api.github.com/users/floydhub/followers","following_url":"https://api.github.com/users/floydhub/following{/other_user}","gists_url":"https://api.github.com/users/floydhub/gists{/gist_id}","starred_url":"https://api.github.com/users/floydhub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/floydhub/subscriptions","organizations_url":"https://api.github.com/users/floydhub/orgs","repos_url":"https://api.github.com/users/floydhub/repos","events_url":"https://api.github.com/users/floydhub/events{/privacy}","received_events_url":"https://api.github.com/users/floydhub/received_events","type":"Organization","site_admin":false},"repo":{"id":79263287,"node_id":"MDEwOlJlcG9zaXRvcnk3OTI2MzI4Nw==","name":"floyd-cli","full_name":"floydhub/floyd-cli","private":false,"owner":{"login":"floydhub","id":21301057,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxMzAxMDU3","avatar_url":"https://avatars1.githubusercontent.com/u/21301057?v=4","gravatar_id":"","url":"https://api.github.com/users/floydhub","html_url":"https://github.com/floydhub","followers_url":"https://api.github.com/users/floydhub/followers","following_url":"https://api.github.com/users/floydhub/following{/other_user}","gists_url":"https://api.github.com/users/floydhub/gists{/gist_id}","starred_url":"https://api.github.com/users/floydhub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/floydhub/subscriptions","organizations_url":"https://api.github.com/users/floydhub/orgs","repos_url":"https://api.github.com/users/floydhub/repos","events_url":"https://api.github.com/users/floydhub/events{/privacy}","received_events_url":"https://api.github.com/users/floydhub/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/floydhub/floyd-cli","description":"Command line tool for FloydHub - the fastest way to build, train, and deploy deep learning models","fork":false,"url":"https://api.github.com/repos/floydhub/floyd-cli","forks_url":"https://api.github.com/repos/floydhub/floyd-cli/forks","keys_url":"https://api.github.com/repos/floydhub/floyd-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/floydhub/floyd-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/floydhub/floyd-cli/teams","hooks_url":"https://api.github.com/repos/floydhub/floyd-cli/hooks","issue_events_url":"https://api.github.com/repos/floydhub/floyd-cli/issues/events{/number}","events_url":"https://api.github.com/repos/floydhub/floyd-cli/events","assignees_url":"https://api.github.com/repos/floydhub/floyd-cli/assignees{/user}","branches_url":"https://api.github.com/repos/floydhub/floyd-cli/branches{/branch}","tags_url":"https://api.github.com/repos/floydhub/floyd-cli/tags","blobs_url":"https://api.github.com/repos/floydhub/floyd-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/floydhub/floyd-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/floydhub/floyd-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/floydhub/floyd-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/floydhub/floyd-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/floydhub/floyd-cli/languages","stargazers_url":"https://api.github.com/repos/floydhub/floyd-cli/stargazers","contributors_url":"https://api.github.com/repos/floydhub/floyd-cli/contributors","subscribers_url":"https://api.github.com/repos/floydhub/floyd-cli/subscribers","subscription_url":"https://api.github.com/repos/floydhub/floyd-cli/subscription","commits_url":"https://api.github.com/repos/floydhub/floyd-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/floydhub/floyd-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/floydhub/floyd-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/floydhub/floyd-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/floydhub/floyd-cli/contents/{+path}","compare_url":"https://api.github.com/repos/floydhub/floyd-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/floydhub/floyd-cli/merges","archive_url":"https://api.github.com/repos/floydhub/floyd-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/floydhub/floyd-cli/downloads","issues_url":"https://api.github.com/repos/floydhub/floyd-cli/issues{/number}","pulls_url":"https://api.github.com/repos/floydhub/floyd-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/floydhub/floyd-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/floydhub/floyd-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/floydhub/floyd-cli/labels{/name}","releases_url":"https://api.github.com/repos/floydhub/floyd-cli/releases{/id}","deployments_url":"https://api.github.com/repos/floydhub/floyd-cli/deployments","created_at":"2017-01-17T19:26:02Z","updated_at":"2018-10-02T21:13:29Z","pushed_at":"2018-10-05T05:47:21Z","git_url":"git://github.com/floydhub/floyd-cli.git","ssh_url":"git@github.com:floydhub/floyd-cli.git","clone_url":"https://github.com/floydhub/floyd-cli.git","svn_url":"https://github.com/floydhub/floyd-cli","homepage":"https://floydhub.com","size":494,"stargazers_count":101,"watchers_count":101,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":24,"mirror_url":null,"archived":false,"open_issues_count":10,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":24,"open_issues":10,"watchers":101,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209"},"html":{"href":"https://github.com/floydhub/floyd-cli/pull/209"},"issue":{"href":"https://api.github.com/repos/floydhub/floyd-cli/issues/209"},"comments":{"href":"https://api.github.com/repos/floydhub/floyd-cli/issues/209/comments"},"review_comments":{"href":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209/comments"},"review_comment":{"href":"https://api.github.com/repos/floydhub/floyd-cli/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/floydhub/floyd-cli/pulls/209/commits"},"statuses":{"href":"https://api.github.com/repos/floydhub/floyd-cli/statuses/262c3d28377509ea8008c871cfd627d1ad1e9298"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"narenst"}}