From bbe4cb07d143c8a8c2d4acbf082f77f1d4a536aa Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Wed, 21 Apr 2021 18:56:00 -0400 Subject: [PATCH 1/7] Get github token thru browser --- .../command_modules/appservice/_constants.py | 7 ++ .../appservice/_github_oauth.py | 85 +++++++++++++++++++ .../cli/command_modules/appservice/custom.py | 4 +- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py index e3edb1e1a34..973fb6f778e 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py @@ -37,6 +37,13 @@ "USSec West", "USSec East" } +GITHUB_OAUTH_CLIENT_ID = "8d8e1f6000648c575489" +GITHUB_OAUTH_REDIRECT_URI = "http://localhost:3000/TokenAuthorize" +GITHUB_OAUTH_SCOPES = [ + "admin:repo_hook", + "repo", + "workflow" +] class FUNCTIONS_STACKS_API_KEYS(): diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py new file mode 100644 index 00000000000..169c9a3ef5d --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py @@ -0,0 +1,85 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.util import CLIError +from knack.log import get_logger + +from ._client_factory import web_client_factory +from ._constants import (GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_REDIRECT_URI, GITHUB_OAUTH_SCOPES) + +logger = get_logger(__name__) + +def get_github_access_token(cmd, scope_list=None): + import os + random_state = os.urandom(5).hex() + + authorize_url = 'https://github.com/login/oauth/authorize?' \ + 'client_id={}&state={}&redirect_uri={}'.format( + GITHUB_OAUTH_CLIENT_ID, random_state, GITHUB_OAUTH_REDIRECT_URI) + if scope_list: + for scope in scope_list: + if scope not in GITHUB_OAUTH_SCOPES: + raise ValidationError("Requested github oauth scope is invalid") + _scope_string = "&scope=" + for i in range(len(scope_list)): + if i != 0: + _scope_string += "+" + _scope_string += scope_list[i] + authorize_url += _scope_string + logger.warning('Opening OAuth URL') + + # Get code to exchange for access token + access_code = _start_http_server(random_state, authorize_url) + + if access_code: + client = web_client_factory(cmd.cli_ctx) + response = client.generate_github_access_token_for_appservice_cli_async(access_code, random_state) + + if response.access_token: + return response.access_token + return None + + +def _start_http_server(random_state, authorize_url): + ip = '127.0.0.1' + port = 3000 + + import http.server + import webbrowser + import socketserver + import urllib.parse as urlparse + + class Server(socketserver.TCPServer): + allow_reuse_address = True + + class CallBackHandler(http.server.BaseHTTPRequestHandler): + access_token = None + + def do_GET(self): + parsed_params = urlparse.parse_qs(urlparse.urlparse(self.path).query) + received_state = parsed_params.get('state', [None])[0] + received_code = parsed_params.get('code', [None])[0] + + if (self.path.startswith('/TokenAuthorize') and received_state and received_code and (random_state == received_state)): + CallBackHandler.received_code = received_code + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write('GitHub account authenticated. You may close this tab'.encode('utf-8')) + else: + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write('Unable to authenticate GitHub account. Please close this tab'.encode('utf-8')) + + try: + server = Server((ip, port), CallBackHandler) + webbrowser.open(authorize_url) + logger.warning('Listening at port: {}'.format(port)) + server.handle_request() + except Exception as e: + raise CLIError('Socket error: {}. Please try again, or provide personal access token'.format(e)) + + return CallBackHandler.received_code if hasattr(CallBackHandler, 'received_code') else None diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index dc296e399e5..b7d2f8f25c1 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -61,7 +61,9 @@ detect_os_form_src, get_current_stack_from_runtime, generate_default_app_name) from ._constants import (FUNCTIONS_STACKS_API_JSON_PATHS, FUNCTIONS_STACKS_API_KEYS, FUNCTIONS_LINUX_RUNTIME_VERSION_REGEX, FUNCTIONS_WINDOWS_RUNTIME_VERSION_REGEX, - NODE_EXACT_VERSION_DEFAULT, RUNTIME_STACKS, FUNCTIONS_NO_V2_REGIONS, PUBLIC_CLOUD) + NODE_EXACT_VERSION_DEFAULT, RUNTIME_STACKS, FUNCTIONS_NO_V2_REGIONS, PUBLIC_CLOUD, + GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_REDIRECT_URI) +from ._github_oauth import (get_github_access_token) logger = get_logger(__name__) From 8ef6bfd3ef5f24a15e26d0e50d5f389927773890 Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Thu, 22 Apr 2021 13:53:50 -0400 Subject: [PATCH 2/7] Add comments --- .../azure/cli/command_modules/appservice/_github_oauth.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py index 169c9a3ef5d..6fad6bd4a60 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py @@ -11,6 +11,11 @@ logger = get_logger(__name__) +''' +This function opens a web browser to prompt for github login +then spins up a server on localhost:3000 to listen for callback +to receive access token +''' def get_github_access_token(cmd, scope_list=None): import os random_state = os.urandom(5).hex() @@ -30,8 +35,7 @@ def get_github_access_token(cmd, scope_list=None): authorize_url += _scope_string logger.warning('Opening OAuth URL') - # Get code to exchange for access token - access_code = _start_http_server(random_state, authorize_url) + access_code = _start_http_server(random_state, authorize_url) # use localhost to get code to exchange for access token if access_code: client = web_client_factory(cmd.cli_ctx) From cb846f4dcbf336b87c4babb751e708af48c6f1ac Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Thu, 22 Apr 2021 14:55:48 -0400 Subject: [PATCH 3/7] Use url way instead of opening browser --- .../command_modules/appservice/_constants.py | 1 - .../appservice/_github_oauth.py | 126 ++++++++---------- .../cli/command_modules/appservice/custom.py | 4 +- 3 files changed, 60 insertions(+), 71 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py index 973fb6f778e..ed1923cec35 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py @@ -38,7 +38,6 @@ "USSec East" } GITHUB_OAUTH_CLIENT_ID = "8d8e1f6000648c575489" -GITHUB_OAUTH_REDIRECT_URI = "http://localhost:3000/TokenAuthorize" GITHUB_OAUTH_SCOPES = [ "admin:repo_hook", "repo", diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py index 6fad6bd4a60..7166f7e3fbd 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py @@ -3,87 +3,79 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from knack.util import CLIError +from azure.cli.core.azclierror import (ValidationError, CLIInternalError, UnclassifiedUserFault) from knack.log import get_logger from ._client_factory import web_client_factory -from ._constants import (GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_REDIRECT_URI, GITHUB_OAUTH_SCOPES) +from ._constants import (GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_SCOPES) logger = get_logger(__name__) + ''' -This function opens a web browser to prompt for github login -then spins up a server on localhost:3000 to listen for callback -to receive access token +Get Github personal access token following Github oauth for command line tools +https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow ''' def get_github_access_token(cmd, scope_list=None): - import os - random_state = os.urandom(5).hex() - - authorize_url = 'https://github.com/login/oauth/authorize?' \ - 'client_id={}&state={}&redirect_uri={}'.format( - GITHUB_OAUTH_CLIENT_ID, random_state, GITHUB_OAUTH_REDIRECT_URI) if scope_list: for scope in scope_list: if scope not in GITHUB_OAUTH_SCOPES: raise ValidationError("Requested github oauth scope is invalid") - _scope_string = "&scope=" - for i in range(len(scope_list)): - if i != 0: - _scope_string += "+" - _scope_string += scope_list[i] - authorize_url += _scope_string - logger.warning('Opening OAuth URL') - - access_code = _start_http_server(random_state, authorize_url) # use localhost to get code to exchange for access token - - if access_code: - client = web_client_factory(cmd.cli_ctx) - response = client.generate_github_access_token_for_appservice_cli_async(access_code, random_state) - - if response.access_token: - return response.access_token - return None - - -def _start_http_server(random_state, authorize_url): - ip = '127.0.0.1' - port = 3000 - - import http.server - import webbrowser - import socketserver - import urllib.parse as urlparse - - class Server(socketserver.TCPServer): - allow_reuse_address = True - - class CallBackHandler(http.server.BaseHTTPRequestHandler): - access_token = None - - def do_GET(self): - parsed_params = urlparse.parse_qs(urlparse.urlparse(self.path).query) - received_state = parsed_params.get('state', [None])[0] - received_code = parsed_params.get('code', [None])[0] - - if (self.path.startswith('/TokenAuthorize') and received_state and received_code and (random_state == received_state)): - CallBackHandler.received_code = received_code - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - self.wfile.write('GitHub account authenticated. You may close this tab'.encode('utf-8')) - else: - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - self.wfile.write('Unable to authenticate GitHub account. Please close this tab'.encode('utf-8')) + scope_list = ' '.join(scope_list) + + authorize_url = 'https://github.com/login/device/code' + authorize_url_data = { + 'scope': scope_list, + 'client_id': GITHUB_OAUTH_CLIENT_ID + } + + import base64 + import json + import requests + import time + from urllib.parse import urlparse, parse_qs try: - server = Server((ip, port), CallBackHandler) - webbrowser.open(authorize_url) - logger.warning('Listening at port: {}'.format(port)) - server.handle_request() + response = requests.post(authorize_url, data=authorize_url_data) + parsed_response = parse_qs(response.content.decode('ascii')) + + device_code = parsed_response['device_code'][0] + user_code = parsed_response['user_code'][0] + verification_uri = parsed_response['verification_uri'][0] + interval = int(parsed_response['interval'][0]) + expires_in_seconds = int(parsed_response['expires_in'][0]) + + logger.warning('Please go to {} and enter the user code {} to activate and retrieve your github personal access token'.format(verification_uri, user_code)) + + timeout = time.time() + expires_in_seconds + logger.warning('Waiting up to {} minutes for activation'.format(str(expires_in_seconds//60))) + + confirmation_url = 'https://github.com/login/oauth/access_token' + confirmation_url_data = { + 'client_id': GITHUB_OAUTH_CLIENT_ID, + 'device_code': device_code, + 'grant_type': 'urn:ietf:params:oauth:grant-type:device_code' + } + + pending = True + while pending: + time.sleep(interval) + + if time.time() > timeout: + raise UnclassifiedUserFault('Activation did not happen in time. Please try again') + + confirmation_response = requests.post(confirmation_url, data=confirmation_url_data) + parsed_confirmation_response = parse_qs(confirmation_response.content.decode('ascii')) + + if 'error' in parsed_confirmation_response and parsed_confirmation_response['error'][0]: + if parsed_confirmation_response['error'][0] == 'slow_down': + interval += 5 # if slow_down error is received, 5 seconds is added to minimum polling interval + elif not(parsed_confirmation_response['error'][0] == 'authorization_pending'): + pending = False + + if 'access_token' in parsed_confirmation_response and parsed_confirmation_response['access_token'][0]: + return parsed_confirmation_response['access_token'][0] except Exception as e: - raise CLIError('Socket error: {}. Please try again, or provide personal access token'.format(e)) + raise CLIInternalError('Error: {}. Please try again, or retrieve personal access token from the Github website'.format(e)) - return CallBackHandler.received_code if hasattr(CallBackHandler, 'received_code') else None + raise UnclassifiedUserFault('Activation did not happen in time. Please try again') diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index b7d2f8f25c1..dc296e399e5 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -61,9 +61,7 @@ detect_os_form_src, get_current_stack_from_runtime, generate_default_app_name) from ._constants import (FUNCTIONS_STACKS_API_JSON_PATHS, FUNCTIONS_STACKS_API_KEYS, FUNCTIONS_LINUX_RUNTIME_VERSION_REGEX, FUNCTIONS_WINDOWS_RUNTIME_VERSION_REGEX, - NODE_EXACT_VERSION_DEFAULT, RUNTIME_STACKS, FUNCTIONS_NO_V2_REGIONS, PUBLIC_CLOUD, - GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_REDIRECT_URI) -from ._github_oauth import (get_github_access_token) + NODE_EXACT_VERSION_DEFAULT, RUNTIME_STACKS, FUNCTIONS_NO_V2_REGIONS, PUBLIC_CLOUD) logger = get_logger(__name__) From 94401859c2433e450cdbd50f7b7705591a6a948b Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Fri, 23 Apr 2021 14:42:54 -0400 Subject: [PATCH 4/7] Fix linter issues --- .../cli/command_modules/appservice/_github_oauth.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py index 7166f7e3fbd..0e4915c68e2 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py @@ -6,7 +6,6 @@ from azure.cli.core.azclierror import (ValidationError, CLIInternalError, UnclassifiedUserFault) from knack.log import get_logger -from ._client_factory import web_client_factory from ._constants import (GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_SCOPES) logger = get_logger(__name__) @@ -16,6 +15,8 @@ Get Github personal access token following Github oauth for command line tools https://docs.github.com/en/developers/apps/authorizing-oauth-apps#device-flow ''' + + def get_github_access_token(cmd, scope_list=None): if scope_list: for scope in scope_list: @@ -45,10 +46,11 @@ def get_github_access_token(cmd, scope_list=None): interval = int(parsed_response['interval'][0]) expires_in_seconds = int(parsed_response['expires_in'][0]) - logger.warning('Please go to {} and enter the user code {} to activate and retrieve your github personal access token'.format(verification_uri, user_code)) + logger.warning("Please go to '%s' and enter the user code '%s' to activate and retrieve your github personal access token", + verification_uri, user_code) timeout = time.time() + expires_in_seconds - logger.warning('Waiting up to {} minutes for activation'.format(str(expires_in_seconds//60))) + logger.warning("Waiting up to '%s' minutes for activation", str(expires_in_seconds // 60)) confirmation_url = 'https://github.com/login/oauth/access_token' confirmation_url_data = { @@ -70,12 +72,13 @@ def get_github_access_token(cmd, scope_list=None): if 'error' in parsed_confirmation_response and parsed_confirmation_response['error'][0]: if parsed_confirmation_response['error'][0] == 'slow_down': interval += 5 # if slow_down error is received, 5 seconds is added to minimum polling interval - elif not(parsed_confirmation_response['error'][0] == 'authorization_pending'): + elif parsed_confirmation_response['error'][0] != 'authorization_pending': pending = False if 'access_token' in parsed_confirmation_response and parsed_confirmation_response['access_token'][0]: return parsed_confirmation_response['access_token'][0] except Exception as e: - raise CLIInternalError('Error: {}. Please try again, or retrieve personal access token from the Github website'.format(e)) + raise CLIInternalError( + 'Error: {}. Please try again, or retrieve personal access token from the Github website'.format(e)) raise UnclassifiedUserFault('Activation did not happen in time. Please try again') From b7801d64087c7f4618456ebd77f33302f0e3b11c Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Mon, 26 Apr 2021 12:00:29 -0400 Subject: [PATCH 5/7] CLI linter fixes --- .../azure/cli/command_modules/appservice/_github_oauth.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py index 0e4915c68e2..b514311b444 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py @@ -46,8 +46,9 @@ def get_github_access_token(cmd, scope_list=None): interval = int(parsed_response['interval'][0]) expires_in_seconds = int(parsed_response['expires_in'][0]) - logger.warning("Please go to '%s' and enter the user code '%s' to activate and retrieve your github personal access token", - verification_uri, user_code) + navigate_to_msg = 'Please navigate to {0} and enter the user code {1} to activate and ' \ + 'retrieve your github personal access token'.format(verification_uri, user_code) + logger.warning(navigate_to_msg) timeout = time.time() + expires_in_seconds logger.warning("Waiting up to '%s' minutes for activation", str(expires_in_seconds // 60)) From 4473316da7aa0562e03305fcbe5637ff226dd857 Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Mon, 26 Apr 2021 12:01:13 -0400 Subject: [PATCH 6/7] Add PyGithub package for github actions use later on (it will be in CLI extensions repo) and github_action property in az webapp deployment source config command --- .../azure/cli/command_modules/appservice/_github_oauth.py | 6 ++---- .../azure/cli/command_modules/appservice/_params.py | 1 + .../azure/cli/command_modules/appservice/custom.py | 4 ++-- src/azure-cli/requirements.py3.Darwin.txt | 1 + src/azure-cli/requirements.py3.Linux.txt | 1 + src/azure-cli/requirements.py3.windows.txt | 1 + 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py index b514311b444..b1c28159f10 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_github_oauth.py @@ -45,10 +45,8 @@ def get_github_access_token(cmd, scope_list=None): verification_uri = parsed_response['verification_uri'][0] interval = int(parsed_response['interval'][0]) expires_in_seconds = int(parsed_response['expires_in'][0]) - - navigate_to_msg = 'Please navigate to {0} and enter the user code {1} to activate and ' \ - 'retrieve your github personal access token'.format(verification_uri, user_code) - logger.warning(navigate_to_msg) + logger.warning('Please navigate to %s and enter the user code %s to activate and ' + 'retrieve your github personal access token', verification_uri, user_code) timeout = time.time() + expires_in_seconds logger.warning("Waiting up to '%s' minutes for activation", str(expires_in_seconds // 60)) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_params.py b/src/azure-cli/azure/cli/command_modules/appservice/_params.py index 401f2110609..c2492eda865 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -289,6 +289,7 @@ def load_arguments(self, _): c.argument('repository_type', help='repository type', arg_type=get_enum_type(['git', 'mercurial', 'vsts', 'github', 'externalgit', 'localgit'])) c.argument('git_token', help='Git access token required for auto sync') + c.argument('github_action', options_list=['--github-action'], help='If using github action, default to False') with self.argument_context(scope + ' identity') as c: c.argument('scope', help="The scope the managed identity has access to") c.argument('role', help="Role name or id the managed identity will be assigned") diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index dc296e399e5..0e5901202e2 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -1561,7 +1561,7 @@ def config_source_control(cmd, resource_group_name, name, repo_url, repository_t manual_integration=None, git_token=None, slot=None, cd_app_type=None, app_working_dir=None, nodejs_task_runner=None, python_framework=None, python_version=None, cd_account_create=None, cd_project_url=None, test=None, - slot_swap=None, private_repo_username=None, private_repo_password=None): + slot_swap=None, private_repo_username=None, private_repo_password=None, github_action=None): client = web_client_factory(cmd.cli_ctx) location = _get_location_from_webapp(client, resource_group_name, name) @@ -1602,7 +1602,7 @@ def config_source_control(cmd, resource_group_name, name, repo_url, repository_t source_control = SiteSourceControl(location=location, repo_url=repo_url, branch=branch, is_manual_integration=manual_integration, - is_mercurial=(repository_type != 'git')) + is_mercurial=(repository_type != 'git'), is_git_hub_action=bool(github_action)) # SCC config can fail if previous commands caused SCMSite shutdown, so retry here. for i in range(5): diff --git a/src/azure-cli/requirements.py3.Darwin.txt b/src/azure-cli/requirements.py3.Darwin.txt index e6d92db98f2..1dee597174a 100644 --- a/src/azure-cli/requirements.py3.Darwin.txt +++ b/src/azure-cli/requirements.py3.Darwin.txt @@ -118,6 +118,7 @@ pbr==5.3.1 portalocker==1.7.1 psutil==5.8.0 pycparser==2.19 +PyGithub==1.38 PyJWT==1.7.1 PyNaCl==1.4.0 pyOpenSSL==19.0.0 diff --git a/src/azure-cli/requirements.py3.Linux.txt b/src/azure-cli/requirements.py3.Linux.txt index 1d8c9053d49..29c34cd7030 100644 --- a/src/azure-cli/requirements.py3.Linux.txt +++ b/src/azure-cli/requirements.py3.Linux.txt @@ -118,6 +118,7 @@ pbr==5.3.1 portalocker==1.7.1 psutil==5.8.0 pycparser==2.19 +PyGithub==1.38 PyJWT==1.7.1 PyNaCl==1.4.0 pyOpenSSL==19.0.0 diff --git a/src/azure-cli/requirements.py3.windows.txt b/src/azure-cli/requirements.py3.windows.txt index b6451eedd77..5c419d8c0bb 100644 --- a/src/azure-cli/requirements.py3.windows.txt +++ b/src/azure-cli/requirements.py3.windows.txt @@ -117,6 +117,7 @@ pbr==5.3.1 portalocker==1.7.1 psutil==5.8.0 pycparser==2.19 +PyGithub==1.38 PyJWT==1.7.1 PyNaCl==1.4.0 pyOpenSSL==19.0.0 From fda252b393389d9945b0f087ab5d2c8c6d8c0297 Mon Sep 17 00:00:00 2001 From: Calvin Chan Date: Thu, 29 Apr 2021 10:55:58 -0400 Subject: [PATCH 7/7] add PyGithub to setup.py --- src/azure-cli/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index d20db31a37c..d5b760952ed 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -137,6 +137,7 @@ 'jsmin~=2.2.2', 'jsondiff==1.2.0', 'packaging~=20.9', + 'PyGithub==1.38', 'pytz==2019.1', 'scp~=0.13.2', 'semver==2.13.0',