From b553827a7e692aed35f630ce1fdff8e73999178a Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Mon, 21 Oct 2024 12:32:14 -0400 Subject: [PATCH 1/8] implemented auto refresh for access token --- esiclient/v1/cluster/openshift.py | 41 ++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index f877481..a00fdc8 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -15,6 +15,7 @@ import os import requests import time +import asyncio from osc_lib.command import command from osc_lib.i18n import _ @@ -26,8 +27,42 @@ BASE_ASSISTED_INSTALLER_URL = \ 'https://api.openshift.com/api/assisted-install/v2/' +AUTHENTICATION_URL = \ + 'https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token' + + +async def check_and_refresh_token(): + access_token = os.getenv('API_TOKEN', '') + offline_token = os.getenv('OFFLINE_TOKEN', '') + + if access_token == '': + raise OSAIException('API_TOKEN not set in environment') + if offline_token == '': + raise OSAIException('OFFLINE_TOKEN not set in environment') + + token_check_url = BASE_ASSISTED_INSTALLER_URL + 'component-versions' + token_check_response = requests.get(token_check_url, headers={'Authorization': 'Bearer ' + access_token}) + + if token_check_response.status_code != 200: + payload = { + 'grant_type': 'refresh_token', + 'client_id': 'cloud-services', + 'refresh_token': offline_token + } + refresh_token_response = requests.post(AUTHENTICATION_URL, data=payload) + if refresh_token_response.status_code == 200: + response_json = refresh_token_response.json() + access_token = response_json['access_token'] + os.environ['API_TOKEN'] = access_token + return access_token + else: + raise OSAIException('Failed to refresh token') + else: + return access_token def call_assisted_installer_api(url, method, headers={}, data=None): + access_token = asyncio.run(check_and_refresh_token()) + headers['Authorization'] = 'Bearer ' + access_token full_url = BASE_ASSISTED_INSTALLER_URL + url if method == 'post': response = requests.post(full_url, headers=headers, json=data) @@ -93,8 +128,8 @@ def _print_failure_message(self, exception, cluster_config_file, if message: print(message) print("* %s" % str(exception)) - if isinstance(exception, OSAIException): - print("* YOU MAY NEED TO REFRESH YOUR OPENSHIFT API TOKEN") + # if isinstance(exception, OSAIException): + # print("* YOU MAY NEED TO REFRESH YOUR OPENSHIFT API TOKEN") print("Run this command to continue installation:") print("* %s" % command) return @@ -451,4 +486,4 @@ def take_action(self, parsed_args): print("-----------------") print("* node cleaning will take a while to complete") print("* run `openstack baremetal node list` to see if" - " they are in the `available` state") + " they are in the `available` state") \ No newline at end of file From befbba1e6cd76a681ca0fb669e06b383963ff743 Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Mon, 21 Oct 2024 12:40:00 -0400 Subject: [PATCH 2/8] fixed linting issues --- esiclient/v1/cluster/openshift.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index a00fdc8..c32fadd 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -10,12 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import asyncio import json import logging import os import requests import time -import asyncio from osc_lib.command import command from osc_lib.i18n import _ @@ -39,19 +39,20 @@ async def check_and_refresh_token(): raise OSAIException('API_TOKEN not set in environment') if offline_token == '': raise OSAIException('OFFLINE_TOKEN not set in environment') - + token_check_url = BASE_ASSISTED_INSTALLER_URL + 'component-versions' - token_check_response = requests.get(token_check_url, headers={'Authorization': 'Bearer ' + access_token}) + auth_headers = {'Authorization': 'Bearer ' + access_token} + check_response = requests.get(token_check_url, headers=auth_headers) - if token_check_response.status_code != 200: + if check_response.status_code != 200: payload = { 'grant_type': 'refresh_token', 'client_id': 'cloud-services', 'refresh_token': offline_token } - refresh_token_response = requests.post(AUTHENTICATION_URL, data=payload) - if refresh_token_response.status_code == 200: - response_json = refresh_token_response.json() + refresh_response = requests.post(AUTHENTICATION_URL, data=payload) + if refresh_response.status_code == 200: + response_json = refresh_response.json() access_token = response_json['access_token'] os.environ['API_TOKEN'] = access_token return access_token @@ -60,6 +61,7 @@ async def check_and_refresh_token(): else: return access_token + def call_assisted_installer_api(url, method, headers={}, data=None): access_token = asyncio.run(check_and_refresh_token()) headers['Authorization'] = 'Bearer ' + access_token @@ -486,4 +488,4 @@ def take_action(self, parsed_args): print("-----------------") print("* node cleaning will take a while to complete") print("* run `openstack baremetal node list` to see if" - " they are in the `available` state") \ No newline at end of file + " they are in the `available` state") From efdbf95aebe37d1cb303d363aab0a0fa16687b85 Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Mon, 21 Oct 2024 12:42:03 -0400 Subject: [PATCH 3/8] fixed linting issues --- esiclient/v1/cluster/openshift.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index c32fadd..1922b81 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -28,7 +28,8 @@ 'https://api.openshift.com/api/assisted-install/v2/' AUTHENTICATION_URL = \ - 'https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token' + 'https://sso.redhat.com/auth/realms/redhat-external/ \ + protocol/openid-connect/token' async def check_and_refresh_token(): @@ -45,19 +46,19 @@ async def check_and_refresh_token(): check_response = requests.get(token_check_url, headers=auth_headers) if check_response.status_code != 200: - payload = { - 'grant_type': 'refresh_token', - 'client_id': 'cloud-services', - 'refresh_token': offline_token - } - refresh_response = requests.post(AUTHENTICATION_URL, data=payload) - if refresh_response.status_code == 200: - response_json = refresh_response.json() - access_token = response_json['access_token'] - os.environ['API_TOKEN'] = access_token - return access_token - else: - raise OSAIException('Failed to refresh token') + payload = { + 'grant_type': 'refresh_token', + 'client_id': 'cloud-services', + 'refresh_token': offline_token + } + refresh_response = requests.post(AUTHENTICATION_URL, data=payload) + if refresh_response.status_code == 200: + response_json = refresh_response.json() + access_token = response_json['access_token'] + os.environ['API_TOKEN'] = access_token + return access_token + else: + raise OSAIException('Failed to refresh token') else: return access_token From d557613b798104862ca37e072f59e653c1c9e089 Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Mon, 21 Oct 2024 13:20:38 -0400 Subject: [PATCH 4/8] updated unit tests --- .../tests/unit/v1/cluster/test_openshift.py | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/esiclient/tests/unit/v1/cluster/test_openshift.py b/esiclient/tests/unit/v1/cluster/test_openshift.py index 3390e8a..82669b3 100644 --- a/esiclient/tests/unit/v1/cluster/test_openshift.py +++ b/esiclient/tests/unit/v1/cluster/test_openshift.py @@ -39,45 +39,69 @@ class TestCallAssistedInstallerAPI(TestCase): @mock.patch('requests.patch', autospec=True) @mock.patch('requests.get', autospec=True) @mock.patch('requests.post', autospec=True) - def test_call_assisted_installer_api_post(self, mock_post, + @mock.patch( + 'esiclient.v1.cluster.openshift.check_and_refresh_token', + autospec=True) + def test_call_assisted_installer_api_post(self, mock_cart, + mock_post, mock_get, mock_patch): mock_post.return_value = MockResponse() + mock_cart.return_value = 'access_token' + openshift.call_assisted_installer_api('test', 'post') mock_post.assert_called_once_with( openshift.BASE_ASSISTED_INSTALLER_URL + "test", - headers={}, json=None) + headers={ + 'Authorization': 'Bearer access_token' + }, json=None) mock_get.assert_not_called mock_patch.assert_not_called @mock.patch('requests.patch', autospec=True) @mock.patch('requests.get', autospec=True) @mock.patch('requests.post', autospec=True) - def test_call_assisted_installer_api_get(self, mock_post, + @mock.patch( + 'esiclient.v1.cluster.openshift.check_and_refresh_token', + autospec=True) + def test_call_assisted_installer_api_get(self, mock_cart, + mock_post, mock_get, mock_patch): mock_get.return_value = MockResponse() + mock_cart.return_value = 'access_token' + openshift.call_assisted_installer_api('test', 'get') mock_get.assert_called_once_with( openshift.BASE_ASSISTED_INSTALLER_URL + "test", - headers={}) + headers={ + 'Authorization': 'Bearer access_token' + }) mock_post.assert_not_called mock_patch.assert_not_called @mock.patch('requests.patch', autospec=True) @mock.patch('requests.get', autospec=True) @mock.patch('requests.post', autospec=True) - def test_call_assisted_installer_api_patch(self, mock_post, + @mock.patch( + 'esiclient.v1.cluster.openshift.check_and_refresh_token', + autospec=True) + def test_call_assisted_installer_api_patch(self, mock_cart, + mock_post, mock_get, mock_patch): mock_patch.return_value = MockResponse() + mock_cart.return_value = 'access_token' + openshift.call_assisted_installer_api('test', 'patch') mock_patch.assert_called_once_with( openshift.BASE_ASSISTED_INSTALLER_URL + "test", - headers={}, json=None) + headers={ + 'Authorization': 'Bearer access_token' + }, json=None) mock_get.assert_not_called mock_post.assert_not_called From 79eb15a11c088dc518489e62fdeba776f44e8145 Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Thu, 31 Oct 2024 15:47:48 -0400 Subject: [PATCH 5/8] removed unecessary async/await syntax + commented code --- esiclient/v1/cluster/openshift.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index 1922b81..564d953 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -32,20 +32,20 @@ protocol/openid-connect/token' -async def check_and_refresh_token(): +def check_and_refresh_token(): access_token = os.getenv('API_TOKEN', '') offline_token = os.getenv('OFFLINE_TOKEN', '') if access_token == '': raise OSAIException('API_TOKEN not set in environment') - if offline_token == '': - raise OSAIException('OFFLINE_TOKEN not set in environment') token_check_url = BASE_ASSISTED_INSTALLER_URL + 'component-versions' auth_headers = {'Authorization': 'Bearer ' + access_token} check_response = requests.get(token_check_url, headers=auth_headers) if check_response.status_code != 200: + if offline_token == '': + print("* YOU MAY NEED TO REFRESH YOUR OPENSHIFT API TOKEN") payload = { 'grant_type': 'refresh_token', 'client_id': 'cloud-services', @@ -64,7 +64,7 @@ async def check_and_refresh_token(): def call_assisted_installer_api(url, method, headers={}, data=None): - access_token = asyncio.run(check_and_refresh_token()) + access_token = check_and_refresh_token() headers['Authorization'] = 'Bearer ' + access_token full_url = BASE_ASSISTED_INSTALLER_URL + url if method == 'post': @@ -131,8 +131,6 @@ def _print_failure_message(self, exception, cluster_config_file, if message: print(message) print("* %s" % str(exception)) - # if isinstance(exception, OSAIException): - # print("* YOU MAY NEED TO REFRESH YOUR OPENSHIFT API TOKEN") print("Run this command to continue installation:") print("* %s" % command) return From ee3102763ab24f15754daeb4f47811c6f4ca25f8 Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Thu, 31 Oct 2024 16:09:24 -0400 Subject: [PATCH 6/8] removed unecessary module import --- esiclient/v1/cluster/openshift.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index 564d953..dce2fbd 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import asyncio import json import logging import os From f329f87aad4e73e5ae247d8e865197b06f304ccf Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Thu, 31 Oct 2024 16:27:48 -0400 Subject: [PATCH 7/8] added a message for offline_token not being in environemnt --- esiclient/v1/cluster/openshift.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index dce2fbd..dcff187 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -45,6 +45,7 @@ def check_and_refresh_token(): if check_response.status_code != 200: if offline_token == '': print("* YOU MAY NEED TO REFRESH YOUR OPENSHIFT API TOKEN") + print("* You can set OFFLINE_TOKEN in your environment to auto-refresh") payload = { 'grant_type': 'refresh_token', 'client_id': 'cloud-services', From 0413f78c2e9f5f51cbc2380116746cc57cee79cf Mon Sep 17 00:00:00 2001 From: Anshul Saha Date: Thu, 31 Oct 2024 16:30:31 -0400 Subject: [PATCH 8/8] changed message to fit lint reqs --- esiclient/v1/cluster/openshift.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esiclient/v1/cluster/openshift.py b/esiclient/v1/cluster/openshift.py index dcff187..85d8c74 100644 --- a/esiclient/v1/cluster/openshift.py +++ b/esiclient/v1/cluster/openshift.py @@ -45,7 +45,7 @@ def check_and_refresh_token(): if check_response.status_code != 200: if offline_token == '': print("* YOU MAY NEED TO REFRESH YOUR OPENSHIFT API TOKEN") - print("* You can set OFFLINE_TOKEN in your environment to auto-refresh") + print("* You can set OFFLINE_TOKEN to auto refresh API TOKEN") payload = { 'grant_type': 'refresh_token', 'client_id': 'cloud-services',